dialog.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * Native system dialogs for opening and saving files.
  6. *
  7. * This package is also accessible with `window.__TAURI__.dialog` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
  8. *
  9. * The APIs must be added to [`tauri.allowlist.dialog`](https://tauri.app/v1/api/config/#allowlistconfig.dialog) in `tauri.conf.json`:
  10. * ```json
  11. * {
  12. * "tauri": {
  13. * "allowlist": {
  14. * "dialog": {
  15. * "all": true, // enable all dialog APIs
  16. * "ask": true, // enable dialog ask API
  17. * "confirm": true, // enable dialog confirm API
  18. * "message": true, // enable dialog message API
  19. * "open": true, // enable file open API
  20. * "save": true // enable file save API
  21. * }
  22. * }
  23. * }
  24. * }
  25. * ```
  26. * It is recommended to allowlist only the APIs you use for optimal bundle size and security.
  27. * @module
  28. */
  29. import { invokeTauriCommand } from './helpers/tauri'
  30. /**
  31. * Extension filters for the file dialog.
  32. *
  33. * @since 1.0.0
  34. */
  35. interface DialogFilter {
  36. /** Filter name. */
  37. name: string
  38. /**
  39. * Extensions to filter, without a `.` prefix.
  40. * @example
  41. * ```typescript
  42. * extensions: ['svg', 'png']
  43. * ```
  44. */
  45. extensions: string[]
  46. }
  47. /**
  48. * Options for the open dialog.
  49. *
  50. * @since 1.0.0
  51. */
  52. interface OpenDialogOptions {
  53. /** The title of the dialog window. */
  54. title?: string
  55. /** The filters of the dialog. */
  56. filters?: DialogFilter[]
  57. /** Initial directory or file path. */
  58. defaultPath?: string
  59. /** Whether the dialog allows multiple selection or not. */
  60. multiple?: boolean
  61. /** Whether the dialog is a directory selection or not. */
  62. directory?: boolean
  63. /**
  64. * If `directory` is true, indicates that it will be read recursively later.
  65. * Defines whether subdirectories will be allowed on the scope or not.
  66. */
  67. recursive?: boolean
  68. }
  69. /**
  70. * Options for the save dialog.
  71. *
  72. * @since 1.0.0
  73. */
  74. interface SaveDialogOptions {
  75. /** The title of the dialog window. */
  76. title?: string
  77. /** The filters of the dialog. */
  78. filters?: DialogFilter[]
  79. /**
  80. * Initial directory or file path.
  81. * If it's a directory path, the dialog interface will change to that folder.
  82. * If it's not an existing directory, the file name will be set to the dialog's file name input and the dialog will be set to the parent folder.
  83. */
  84. defaultPath?: string
  85. }
  86. /**
  87. * @since 1.0.0
  88. */
  89. interface MessageDialogOptions {
  90. /** The title of the dialog. Defaults to the app name. */
  91. title?: string
  92. /** The type of the dialog. Defaults to `info`. */
  93. type?: 'info' | 'warning' | 'error'
  94. /** The label of the confirm button. */
  95. okLabel?: string
  96. }
  97. interface ConfirmDialogOptions {
  98. /** The title of the dialog. Defaults to the app name. */
  99. title?: string
  100. /** The type of the dialog. Defaults to `info`. */
  101. type?: 'info' | 'warning' | 'error'
  102. /** The label of the confirm button. */
  103. okLabel?: string
  104. /** The label of the cancel button. */
  105. cancelLabel?: string
  106. }
  107. /**
  108. * Open a file/directory selection dialog.
  109. *
  110. * The selected paths are added to the filesystem and asset protocol allowlist scopes.
  111. * When security is more important than the easy of use of this API,
  112. * prefer writing a dedicated command instead.
  113. *
  114. * Note that the allowlist scope change is not persisted, so the values are cleared when the application is restarted.
  115. * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
  116. * @example
  117. * ```typescript
  118. * import { open } from '@tauri-apps/api/dialog';
  119. * // Open a selection dialog for image files
  120. * const selected = await open({
  121. * multiple: true,
  122. * filters: [{
  123. * name: 'Image',
  124. * extensions: ['png', 'jpeg']
  125. * }]
  126. * });
  127. * if (Array.isArray(selected)) {
  128. * // user selected multiple files
  129. * } else if (selected === null) {
  130. * // user cancelled the selection
  131. * } else {
  132. * // user selected a single file
  133. * }
  134. * ```
  135. *
  136. * @example
  137. * ```typescript
  138. * import { open } from '@tauri-apps/api/dialog';
  139. * import { appDir } from '@tauri-apps/api/path';
  140. * // Open a selection dialog for directories
  141. * const selected = await open({
  142. * directory: true,
  143. * multiple: true,
  144. * defaultPath: await appDir(),
  145. * });
  146. * if (Array.isArray(selected)) {
  147. * // user selected multiple directories
  148. * } else if (selected === null) {
  149. * // user cancelled the selection
  150. * } else {
  151. * // user selected a single directory
  152. * }
  153. * ```
  154. *
  155. * @returns A promise resolving to the selected path(s)
  156. *
  157. * @since 1.0.0
  158. */
  159. async function open(
  160. options: OpenDialogOptions = {}
  161. ): Promise<null | string | string[]> {
  162. if (typeof options === 'object') {
  163. Object.freeze(options)
  164. }
  165. return invokeTauriCommand({
  166. __tauriModule: 'Dialog',
  167. message: {
  168. cmd: 'openDialog',
  169. options
  170. }
  171. })
  172. }
  173. /**
  174. * Open a file/directory save dialog.
  175. *
  176. * The selected path is added to the filesystem and asset protocol allowlist scopes.
  177. * When security is more important than the easy of use of this API,
  178. * prefer writing a dedicated command instead.
  179. *
  180. * Note that the allowlist scope change is not persisted, so the values are cleared when the application is restarted.
  181. * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
  182. * @example
  183. * ```typescript
  184. * import { save } from '@tauri-apps/api/dialog';
  185. * const filePath = await save({
  186. * filters: [{
  187. * name: 'Image',
  188. * extensions: ['png', 'jpeg']
  189. * }]
  190. * });
  191. * ```
  192. *
  193. * @returns A promise resolving to the selected path.
  194. *
  195. * @since 1.0.0
  196. */
  197. async function save(options: SaveDialogOptions = {}): Promise<string | null> {
  198. if (typeof options === 'object') {
  199. Object.freeze(options)
  200. }
  201. return invokeTauriCommand({
  202. __tauriModule: 'Dialog',
  203. message: {
  204. cmd: 'saveDialog',
  205. options
  206. }
  207. })
  208. }
  209. /**
  210. * Shows a message dialog with an `Ok` button.
  211. * @example
  212. * ```typescript
  213. * import { message } from '@tauri-apps/api/dialog';
  214. * await message('Tauri is awesome', 'Tauri');
  215. * await message('File not found', { title: 'Tauri', type: 'error' });
  216. * ```
  217. *
  218. * @param message The message to show.
  219. * @param options The dialog's options. If a string, it represents the dialog title.
  220. *
  221. * @returns A promise indicating the success or failure of the operation.
  222. *
  223. * @since 1.0.0
  224. *
  225. */
  226. async function message(
  227. message: string,
  228. options?: string | MessageDialogOptions
  229. ): Promise<void> {
  230. const opts = typeof options === 'string' ? { title: options } : options
  231. return invokeTauriCommand({
  232. __tauriModule: 'Dialog',
  233. message: {
  234. cmd: 'messageDialog',
  235. message: message.toString(),
  236. title: opts?.title?.toString(),
  237. type: opts?.type,
  238. buttonLabel: opts?.okLabel?.toString()
  239. }
  240. })
  241. }
  242. /**
  243. * Shows a question dialog with `Yes` and `No` buttons.
  244. * @example
  245. * ```typescript
  246. * import { ask } from '@tauri-apps/api/dialog';
  247. * const yes = await ask('Are you sure?', 'Tauri');
  248. * const yes2 = await ask('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' });
  249. * ```
  250. *
  251. * @param message The message to show.
  252. * @param options The dialog's options. If a string, it represents the dialog title.
  253. *
  254. * @returns A promise resolving to a boolean indicating whether `Yes` was clicked or not.
  255. *
  256. * @since 1.0.0
  257. */
  258. async function ask(
  259. message: string,
  260. options?: string | ConfirmDialogOptions
  261. ): Promise<boolean> {
  262. const opts = typeof options === 'string' ? { title: options } : options
  263. return invokeTauriCommand({
  264. __tauriModule: 'Dialog',
  265. message: {
  266. cmd: 'askDialog',
  267. message: message.toString(),
  268. title: opts?.title?.toString(),
  269. type: opts?.type,
  270. buttonLabels: [
  271. opts?.okLabel?.toString() ?? 'Yes',
  272. opts?.cancelLabel?.toString() ?? 'No'
  273. ]
  274. }
  275. })
  276. }
  277. /**
  278. * Shows a question dialog with `Ok` and `Cancel` buttons.
  279. * @example
  280. * ```typescript
  281. * import { confirm } from '@tauri-apps/api/dialog';
  282. * const confirmed = await confirm('Are you sure?', 'Tauri');
  283. * const confirmed2 = await confirm('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' });
  284. * ```
  285. *
  286. * @param message The message to show.
  287. * @param options The dialog's options. If a string, it represents the dialog title.
  288. *
  289. * @returns A promise resolving to a boolean indicating whether `Ok` was clicked or not.
  290. *
  291. * @since 1.0.0
  292. */
  293. async function confirm(
  294. message: string,
  295. options?: string | ConfirmDialogOptions
  296. ): Promise<boolean> {
  297. const opts = typeof options === 'string' ? { title: options } : options
  298. return invokeTauriCommand({
  299. __tauriModule: 'Dialog',
  300. message: {
  301. cmd: 'confirmDialog',
  302. message: message.toString(),
  303. title: opts?.title?.toString(),
  304. type: opts?.type,
  305. buttonLabels: [
  306. opts?.okLabel?.toString() ?? 'Ok',
  307. opts?.cancelLabel?.toString() ?? 'Cancel'
  308. ]
  309. }
  310. })
  311. }
  312. export type {
  313. DialogFilter,
  314. OpenDialogOptions,
  315. SaveDialogOptions,
  316. MessageDialogOptions,
  317. ConfirmDialogOptions
  318. }
  319. export { open, save, message, ask, confirm }