diff --git a/src/App.vue b/src/App.vue index 1a7459fd..d1d3642e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,6 +15,7 @@ + @@ -33,6 +34,7 @@ import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue'; import MacOSTitleBar from './components/MacOSTitleBar.vue'; + import PromptDialogModal from './components/ui/dialog/PromptDialogModal.vue'; import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue'; import '@/styles/globals.css'; diff --git a/src/components/ui/dialog/PromptDialogModal.vue b/src/components/ui/dialog/PromptDialogModal.vue new file mode 100644 index 00000000..427851a2 --- /dev/null +++ b/src/components/ui/dialog/PromptDialogModal.vue @@ -0,0 +1,138 @@ + + + diff --git a/src/localization/en.json b/src/localization/en.json index 1de84874..dc1da735 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -916,6 +916,9 @@ "cancel": "Cancel", "confirm": "Confirm" }, + "prompt": { + "input_invalid": "Invalid input" + }, "user": { "status": { "active": "Active", diff --git a/src/stores/modal.js b/src/stores/modal.js index 40bf698b..85f4c8ef 100644 --- a/src/stores/modal.js +++ b/src/stores/modal.js @@ -33,6 +33,25 @@ function translate(key, fallback) { * @property {boolean=} dismissible */ +/** + * @typedef {Object} PromptResult + * @property {boolean} ok + * @property {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + * @property {string} value + */ + +/** + * @typedef {Object} PromptOptions + * @property {string} title + * @property {string} description + * @property {string=} confirmText + * @property {string=} cancelText + * @property {string=} inputValue + * @property {RegExp | string=} pattern + * @property {string=} errorMessage + * @property {boolean=} dismissible + */ + // TODO: Method chains for confirm export const useModalStore = defineStore('Modal', () => { @@ -44,13 +63,29 @@ export const useModalStore = defineStore('Modal', () => { const alertCancelText = ref(''); const alertDismissible = ref(true); + const promptOpen = ref(false); + const promptTitle = ref(''); + const promptDescription = ref(''); + const promptOkText = ref(''); + const promptCancelText = ref(''); + const promptDismissible = ref(true); + const promptInputValue = ref(''); + const promptPattern = ref(null); + const promptErrorMessage = ref(''); + /** @type {{ resolve: ((result: ConfirmResult) => void) | null } | null} */ let pending = null; + /** @type {{ resolve: ((result: PromptResult) => void) | null } | null} */ + let pendingPrompt = null; function closeDialog() { alertOpen.value = false; } + function closePromptDialog() { + promptOpen.value = false; + } + /** * @param {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason */ @@ -70,6 +105,27 @@ export const useModalStore = defineStore('Modal', () => { if (resolve) resolve({ ok: reason === 'ok', reason }); } + /** + * @param {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + * @param {string} value + */ + function finishPrompt(reason, value) { + const resolve = pendingPrompt?.resolve; + pendingPrompt = null; + closePromptDialog(); + if (resolve) resolve({ ok: reason === 'ok', reason, value }); + } + + /** + * @param {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + * @param {string} value + */ + function finishPromptWithoutClosing(reason, value) { + const resolve = pendingPrompt?.resolve; + pendingPrompt = null; + if (resolve) resolve({ ok: reason === 'ok', reason, value }); + } + /** * @param {'confirm' | 'alert'} mode * @param {any} options @@ -106,6 +162,44 @@ export const useModalStore = defineStore('Modal', () => { }); } + /** + * @param {PromptOptions} options + * @returns {Promise} + */ + function openPrompt(options) { + if (pendingPrompt) { + finishPromptWithoutClosing('replaced', promptInputValue.value); + } + + const inputValue = options.inputValue ?? ''; + const inputValueCopy = + typeof inputValue === 'string' + ? inputValue.slice() + : String(inputValue); + + promptTitle.value = options.title; + promptDescription.value = options.description; + promptDismissible.value = options.dismissible !== false; + promptInputValue.value = inputValueCopy; + promptPattern.value = options.pattern ?? null; + promptErrorMessage.value = + options.errorMessage || + translate('dialog.prompt.input_invalid', '输入错误'); + + promptOkText.value = + options.confirmText || + translate('dialog.alertdialog.confirm', 'Confirm'); + promptCancelText.value = + options.cancelText || + translate('dialog.alertdialog.cancel', 'Cancel'); + + promptOpen.value = true; + + return new Promise((resolve) => { + pendingPrompt = { resolve }; + }); + } + /** * confirm: always resolve({ok, reason}) * @param {ConfirmOptions} options @@ -124,11 +218,25 @@ export const useModalStore = defineStore('Modal', () => { return openBase('alert', options); } + /** + * prompt: always resolve({ok, reason, value}) + * @param {PromptOptions} options + * @returns {Promise} + */ + function prompt(options) { + return openPrompt(options); + } + function handleOk() { if (!pending) return; finish('ok'); } + function handlePromptOk(value) { + if (!pendingPrompt) return; + finishPrompt('ok', value ?? ''); + } + function handleCancel() { if (!pending) return; @@ -141,6 +249,11 @@ export const useModalStore = defineStore('Modal', () => { finish('cancel'); } + function handlePromptCancel(value) { + if (!pendingPrompt) return; + finishPrompt('cancel', value ?? ''); + } + function handleDismiss() { if (!pending) return; if (!alertDismissible.value) return; @@ -154,10 +267,20 @@ export const useModalStore = defineStore('Modal', () => { finish('dismiss'); } + function handlePromptDismiss(value) { + if (!pendingPrompt) return; + if (!promptDismissible.value) return; + finishPrompt('dismiss', value ?? ''); + } + function setAlertOpen(open) { alertOpen.value = !!open; } + function setPromptOpen(open) { + promptOpen.value = !!open; + } + return { alertOpen, alertMode, @@ -166,13 +289,27 @@ export const useModalStore = defineStore('Modal', () => { alertOkText, alertCancelText, alertDismissible, + promptOpen, + promptTitle, + promptDescription, + promptOkText, + promptCancelText, + promptDismissible, + promptInputValue, + promptPattern, + promptErrorMessage, confirm, alert, + prompt, handleOk, handleCancel, handleDismiss, - setAlertOpen + handlePromptOk, + handlePromptCancel, + handlePromptDismiss, + setAlertOpen, + setPromptOpen }; -}); +};);