App.svelte 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <script>
  2. import { writable } from 'svelte/store'
  3. import { open } from '@tauri-apps/api/shell'
  4. import { appWindow, getCurrent } from '@tauri-apps/api/window'
  5. import * as os from '@tauri-apps/api/os'
  6. import Welcome from './views/Welcome.svelte'
  7. import Cli from './views/Cli.svelte'
  8. import Communication from './views/Communication.svelte'
  9. import Dialog from './views/Dialog.svelte'
  10. import FileSystem from './views/FileSystem.svelte'
  11. import Http from './views/Http.svelte'
  12. import Notifications from './views/Notifications.svelte'
  13. import Window from './views/Window.svelte'
  14. import Shortcuts from './views/Shortcuts.svelte'
  15. import Shell from './views/Shell.svelte'
  16. import Updater from './views/Updater.svelte'
  17. import Clipboard from './views/Clipboard.svelte'
  18. import WebRTC from './views/WebRTC.svelte'
  19. import { onMount } from 'svelte'
  20. import { listen } from '@tauri-apps/api/event'
  21. import { ask } from '@tauri-apps/api/dialog'
  22. if (appWindow.label !== 'main') {
  23. appWindow.onCloseRequested(async (event) => {
  24. const confirmed = await confirm('Are you sure?')
  25. if (!confirmed) {
  26. // user did not confirm closing the window; let's prevent it
  27. event.preventDefault()
  28. }
  29. })
  30. }
  31. appWindow.onFileDropEvent((event) => {
  32. onMessage(`File drop: ${JSON.stringify(event.payload)}`)
  33. })
  34. const views = [
  35. {
  36. label: 'Welcome',
  37. component: Welcome,
  38. icon: 'i-ph-hand-waving'
  39. },
  40. {
  41. label: 'Communication',
  42. component: Communication,
  43. icon: 'i-codicon-radio-tower'
  44. },
  45. {
  46. label: 'CLI',
  47. component: Cli,
  48. icon: 'i-codicon-terminal'
  49. },
  50. {
  51. label: 'Dialog',
  52. component: Dialog,
  53. icon: 'i-codicon-multiple-windows'
  54. },
  55. {
  56. label: 'File system',
  57. component: FileSystem,
  58. icon: 'i-codicon-files'
  59. },
  60. {
  61. label: 'HTTP',
  62. component: Http,
  63. icon: 'i-ph-globe-hemisphere-west'
  64. },
  65. {
  66. label: 'Notifications',
  67. component: Notifications,
  68. icon: 'i-codicon-bell-dot'
  69. },
  70. {
  71. label: 'Window',
  72. component: Window,
  73. icon: 'i-codicon-window'
  74. },
  75. {
  76. label: 'Shortcuts',
  77. component: Shortcuts,
  78. icon: 'i-codicon-record-keys'
  79. },
  80. {
  81. label: 'Shell',
  82. component: Shell,
  83. icon: 'i-codicon-terminal-bash'
  84. },
  85. {
  86. label: 'Updater',
  87. component: Updater,
  88. icon: 'i-codicon-cloud-download'
  89. },
  90. {
  91. label: 'Clipboard',
  92. component: Clipboard,
  93. icon: 'i-codicon-clippy'
  94. },
  95. {
  96. label: 'WebRTC',
  97. component: WebRTC,
  98. icon: 'i-ph-broadcast'
  99. }
  100. ]
  101. let selected = views[0]
  102. function select(view) {
  103. selected = view
  104. }
  105. // Window controls
  106. let isWindowMaximized
  107. onMount(async () => {
  108. const window = getCurrent()
  109. isWindowMaximized = await window.isMaximized()
  110. listen('tauri://resize', async () => {
  111. isWindowMaximized = await window.isMaximized()
  112. })
  113. })
  114. function minimize() {
  115. getCurrent().minimize()
  116. }
  117. async function toggleMaximize() {
  118. const window = getCurrent()
  119. ;(await window.isMaximized()) ? window.unmaximize() : window.maximize()
  120. }
  121. let confirmed_close = false
  122. async function close() {
  123. if (!confirmed_close) {
  124. confirmed_close = await ask(
  125. 'Are you sure that you want to close this window?',
  126. {
  127. title: 'Tauri API'
  128. }
  129. )
  130. if (confirmed_close) {
  131. getCurrent().close()
  132. }
  133. }
  134. }
  135. // dark/light
  136. let isDark
  137. onMount(() => {
  138. isDark = localStorage.getItem('theme') == 'dark'
  139. applyTheme(isDark)
  140. })
  141. function applyTheme(isDark) {
  142. const html = document.querySelector('html')
  143. isDark ? html.classList.add('dark') : html.classList.remove('dark')
  144. localStorage.setItem('theme', isDark ? 'dark' : '')
  145. }
  146. function toggleDark() {
  147. isDark = !isDark
  148. applyTheme(isDark)
  149. }
  150. // Console
  151. let messages = writable([])
  152. function onMessage(value) {
  153. messages.update((r) => [
  154. {
  155. html:
  156. `<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
  157. (typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
  158. '</pre>'
  159. },
  160. ...r
  161. ])
  162. }
  163. // this function is renders HTML without sanitizing it so it's insecure
  164. // we only use it with our own input data
  165. function insecureRenderHtml(html) {
  166. messages.update((r) => [
  167. {
  168. html:
  169. `<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
  170. html +
  171. '</pre>'
  172. },
  173. ...r
  174. ])
  175. }
  176. function clear() {
  177. messages.update(() => [])
  178. }
  179. let consoleEl, consoleH, cStartY
  180. let minConsoleHeight = 50
  181. function startResizingConsole(e) {
  182. cStartY = e.clientY
  183. const styles = window.getComputedStyle(consoleEl)
  184. consoleH = parseInt(styles.height, 10)
  185. const moveHandler = (e) => {
  186. const dy = e.clientY - cStartY
  187. const newH = consoleH - dy
  188. consoleEl.style.height = `${
  189. newH < minConsoleHeight ? minConsoleHeight : newH
  190. }px`
  191. }
  192. const upHandler = () => {
  193. document.removeEventListener('mouseup', upHandler)
  194. document.removeEventListener('mousemove', moveHandler)
  195. }
  196. document.addEventListener('mouseup', upHandler)
  197. document.addEventListener('mousemove', moveHandler)
  198. }
  199. let isWindows
  200. onMount(async () => {
  201. isWindows = (await os.platform()) === 'win32'
  202. })
  203. </script>
  204. {#if isWindows}
  205. <div
  206. class="w-screen select-none h-8 pl-2 flex justify-between items-center absolute text-primaryText dark:text-darkPrimaryText"
  207. data-tauri-drag-region
  208. >
  209. <span class="text-darkPrimaryText">Tauri API Validation</span>
  210. <span
  211. class="
  212. h-100%
  213. children:h-100% children:w-12 children:inline-flex
  214. children:items-center children:justify-center"
  215. >
  216. <span
  217. title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
  218. class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
  219. on:click={toggleDark}
  220. >
  221. {#if isDark}
  222. <div class="i-ph-sun" />
  223. {:else}
  224. <div class="i-ph-moon" />
  225. {/if}
  226. </span>
  227. <span
  228. title="Minimize"
  229. class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
  230. on:click={minimize}
  231. >
  232. <div class="i-codicon-chrome-minimize" />
  233. </span>
  234. <span
  235. title={isWindowMaximized ? 'Restore' : 'Maximize'}
  236. class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
  237. on:click={toggleMaximize}
  238. >
  239. {#if isWindowMaximized}
  240. <div class="i-codicon-chrome-restore" />
  241. {:else}
  242. <div class="i-codicon-chrome-maximize" />
  243. {/if}
  244. </span>
  245. <span
  246. title="Close"
  247. class="hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText "
  248. on:click={close}
  249. >
  250. <div class="i-codicon-chrome-close" />
  251. </span>
  252. </span>
  253. </div>
  254. {/if}
  255. <div
  256. class="flex h-screen w-screen overflow-hidden children-pt8 children-pb-2 text-primaryText dark:text-darkPrimaryText"
  257. >
  258. <aside
  259. class="w-75 {isWindows
  260. ? 'bg-darkPrimaryLighter/60'
  261. : 'bg-darkPrimaryLighter'} transition-colors-250 overflow-hidden grid select-none px-2"
  262. >
  263. <img
  264. on:click={() => open('https://tauri.app/')}
  265. class="self-center p-7 cursor-pointer"
  266. src="tauri_logo.png"
  267. alt="Tauri logo"
  268. />
  269. {#if !isWindows}
  270. <a href="##" class="nv justify-between h-8" on:click={toggleDark}>
  271. {#if isDark}
  272. Switch to Light mode
  273. <div class="i-ph-sun" />
  274. {:else}
  275. Switch to Dark mode
  276. <div class="i-ph-moon" />
  277. {/if}
  278. </a>
  279. <br />
  280. <div class="bg-white/5 h-2px" />
  281. <br />
  282. {/if}
  283. <a
  284. class="nv justify-between h-8"
  285. target="_blank"
  286. href="https://tauri.app/v1/guides/"
  287. >
  288. Documentation
  289. <span class="i-codicon-link-external" />
  290. </a>
  291. <a
  292. class="nv justify-between h-8"
  293. target="_blank"
  294. href="https://github.com/tauri-apps/tauri"
  295. >
  296. Github
  297. <span class="i-codicon-link-external" />
  298. </a>
  299. <a
  300. class="nv justify-between h-8"
  301. target="_blank"
  302. href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
  303. >
  304. Source
  305. <span class="i-codicon-link-external" />
  306. </a>
  307. <br />
  308. <div class="bg-white/5 h-2px" />
  309. <br />
  310. <div
  311. class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
  312. >
  313. {#each views as view}
  314. <a
  315. href="##"
  316. class="nv {selected === view ? 'nv_selected' : ''}"
  317. on:click={() => select(view)}
  318. >
  319. <div class="{view.icon} mr-2" />
  320. <p>{view.label}</p></a
  321. >
  322. {/each}
  323. </div>
  324. </aside>
  325. <main
  326. class="flex-1 bg-primary dark:bg-darkPrimary transition-colors-250 grid grid-rows-[2fr_auto]"
  327. >
  328. <div class="px-5 overflow-hidden grid grid-rows-[auto_1fr]">
  329. <h1>{selected.label}</h1>
  330. <div class="overflow-y-auto">
  331. <div class="mr-2">
  332. <svelte:component
  333. this={selected.component}
  334. {onMessage}
  335. {insecureRenderHtml}
  336. />
  337. </div>
  338. </div>
  339. </div>
  340. <div
  341. bind:this={consoleEl}
  342. id="console"
  343. class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
  344. >
  345. <div
  346. on:mousedown={startResizingConsole}
  347. class="bg-black/20 h-2px cursor-ns-resize"
  348. />
  349. <div class="flex justify-between items-center px-2">
  350. <p class="font-semibold">Console</p>
  351. <div
  352. class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center
  353. hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
  354. active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
  355. "
  356. on:click={clear}
  357. >
  358. <div class="i-codicon-clear-all" />
  359. </div>
  360. </div>
  361. <div class="px-2 overflow-y-auto all:font-mono code-block all:text-xs">
  362. {#each $messages as r}
  363. {@html r.html}
  364. {/each}
  365. </div>
  366. </div>
  367. </main>
  368. </div>