feat(viewer): add unsaved change confirm dialog

This commit is contained in:
Darren Yu 2025-12-17 22:34:06 +08:00
parent 0b388cc50a
commit 236ed936c7
No known key found for this signature in database
GPG Key ID: 2D69AA5646405984
14 changed files with 70 additions and 15 deletions

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>Die Datei wurde nach dem Öffnen von anderer Stelle mit einer neuen Version aktualisiert.</0><1>Wenn Sie unter einem neuen Dateinamen oder an einem neuen Ort gespeichert haben, existiert möglicherweise bereits eine Datei mit demselben Namen.</1>",
"saveAs": "Speichern unter",
"versionConflict": "Versionskonflikt",
"discardUnsavedConfirm": "Sie haben ungespeicherte Änderungen. Möchten Sie sie verwerfen?",
"overwrite": "Überschreiben",
"editShareLink": "Freigabe-Link bearbeiten",
"clearPermissions": "Berechtigungseinstellungen löschen",

View File

@ -424,6 +424,7 @@
"conflictDes2": "<0>The file was updated to a new version from elsewhere after you opened it.</0><1>If you saved it with a new name or a new location, the file name already exists.</1>",
"saveAs": "Save as",
"versionConflict": "Version conflict",
"discardUnsavedConfirm": "You have unsaved changes. Still discard?",
"overwrite": "Overwrite",
"editShareLink": "Edit share link",
"clearPermissions": "Clear permission settings",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>El archivo fue actualizado a una nueva versión desde otro lugar después de que lo abrieras.</0><1>Si lo guardaste con un nuevo nombre o nueva ubicación, el nombre de archivo ya existe.</1>",
"saveAs": "Guardar como",
"versionConflict": "Conflicto de versión",
"discardUnsavedConfirm": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
"overwrite": "Sobrescribir",
"editShareLink": "Editar enlace compartido",
"clearPermissions": "Limpiar configuración de permisos",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>Le fichier a été mis à jour vers une nouvelle version depuis ailleurs après que vous l'ayez ouvert.</0><1>Si vous l'avez enregistré avec un nouveau nom ou un nouvel emplacement, le nom de fichier existe déjà.</1>",
"saveAs": "Enregistrer sous",
"versionConflict": "Conflit de version",
"discardUnsavedConfirm": "Vous avez des modifications non enregistrées. Les abandonner ?",
"overwrite": "Écraser",
"editShareLink": "Modifier le lien de partage",
"clearPermissions": "Effacer les paramètres d'autorisation",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>Il file è stato aggiornato a una nuova versione da altrove dopo che l'hai aperto.</0><1>Se l'hai salvato con un nuovo nome o in una nuova posizione, il nome del file esiste già.</1>",
"saveAs": "Salva come",
"versionConflict": "Conflitto di versione",
"discardUnsavedConfirm": "Hai modifiche non salvate. Vuoi scartarle?",
"overwrite": "Sovrascrivi",
"editShareLink": "Modifica link di condivisione",
"clearPermissions": "Pulisci impostazioni permessi",

View File

@ -424,6 +424,7 @@
"conflictDes2": "<0>ファイルを開いた後、別の場所から新しいバージョンで更新されました。</0><1>新しいファイル名または新しい場所に保存した場合、同名のファイルが既に存在する可能性があります。</1>",
"saveAs": "名前を付けて保存",
"versionConflict": "バージョン競合",
"discardUnsavedConfirm": "保存されていない変更があります。破棄しますか?",
"overwrite": "上書き",
"editShareLink": "共有リンクの編集",
"clearPermissions": "権限設定のクリア",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>파일을 연 후 다른 곳에서 새 버전으로 업데이트되었습니다.</0><1>새 파일명이나 새 위치로 저장한 경우 동일한 이름의 파일이 이미 존재할 수 있습니다.</1>",
"saveAs": "다른 이름으로 저장",
"versionConflict": "버전 충돌",
"discardUnsavedConfirm": "저장되지 않은 변경 사항이 있습니다. 삭제하시겠습니까?",
"overwrite": "덮어쓰기",
"editShareLink": "공유 링크 편집",
"clearPermissions": "권한 설정 지우기",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>O arquivo foi atualizado para uma nova versão de outro lugar depois que você o abriu.</0><1>Se você o salvou com um novo nome ou novo local, o nome do arquivo já existe.</1>",
"saveAs": "Salvar como",
"versionConflict": "Conflito de versão",
"discardUnsavedConfirm": "Você tem alterações não salvas. Descartá-las?",
"overwrite": "Sobrescrever",
"editShareLink": "Editar link de compartilhamento",
"clearPermissions": "Limpar configurações de permissão",

View File

@ -459,6 +459,7 @@
"conflictDes2": "<0>Файл был обновлён до новой версии из другого места после того, как вы его открыли.</0><1>Если вы сохранили его с новым именем или в новом месте, возможно, файл с таким именем уже существует.</1>",
"saveAs": "Сохранить как",
"versionConflict": "Конфликт версий",
"discardUnsavedConfirm": "У вас есть несохранённые изменения. Отменить их?",
"overwrite": "Перезаписать",
"editShareLink": "Редактировать ссылку на публикацию",
"clearPermissions": "Очистить настройки разрешений",

View File

@ -424,6 +424,7 @@
"conflictDes2": "<0>该文件在你打开后被从它处更新了新版本。</0><1>如果你另存为了新文件名或新位置,可能已有同名文件存在。</1>",
"saveAs": "另存为",
"versionConflict": "版本冲突",
"discardUnsavedConfirm": "有未保存的更改,确定要关闭吗?",
"overwrite": "覆盖",
"editShareLink": "编辑分享链接",
"clearPermissions": "清除权限设置",

View File

@ -424,6 +424,7 @@
"conflictDes2": "<0>該檔案在你開啟後被從它處更新了新版本。</0><1>如果你另存為了新檔名或新位置,可能已有同名檔案存在。</1>",
"saveAs": "另存為",
"versionConflict": "版本衝突",
"discardUnsavedConfirm": "有未儲存的變更,確定要關閉嗎?",
"overwrite": "覆蓋",
"editShareLink": "編輯分享連結",
"clearPermissions": "清除許可權設定",

View File

@ -15,6 +15,7 @@ import CaretDown from "../../Icons/CaretDown.tsx";
import Checkmark from "../../Icons/Checkmark.tsx";
import Setting from "../../Icons/Setting.tsx";
import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx";
import { confirmOperation } from "../../../redux/thunks/dialog.ts";
const MonacoEditor = lazy(() => import("./MonacoEditor.tsx"));
@ -109,6 +110,31 @@ const CodeViewer = () => {
const [wordWrap, setWordWrap] = useState<"off" | "on" | "wordWrapColumn" | "bounded">("off");
const saveFunction = useRef<() => void>(() => {});
const closeViewer = useCallback(() => {
dispatch(closeCodeViewer());
}, [dispatch]);
const handleDialogClose = useCallback(
(_: React.SyntheticEvent | object, reason?: "backdropClick" | "escapeKeyDown") => {
if (!saved && supportUpdate && (reason === "backdropClick" || reason === "escapeKeyDown")) {
dispatch(
confirmOperation(
t("application:modals.discardUnsavedConfirm"),
),
)
.then(() => {
closeViewer();
})
.catch(() => {});
return;
}
closeViewer();
},
[closeViewer, saved, supportUpdate, t, dispatch],
);
const loadContent = useCallback(
(charset?: string) => {
if (!viewerState || !viewerState.open) {
@ -123,10 +149,10 @@ const CodeViewer = () => {
setLoaded(true);
})
.catch(() => {
onClose();
closeViewer();
});
},
[viewerState],
[viewerState, closeViewer],
);
useEffect(() => {
@ -140,10 +166,6 @@ const CodeViewer = () => {
loadContent();
}, [viewerState?.open]);
const onClose = useCallback(() => {
dispatch(closeCodeViewer());
}, [dispatch]);
const openMore = useCallback(
(e: React.MouseEvent<any>) => {
setAnchorEl(e.currentTarget);
@ -227,7 +249,7 @@ const CodeViewer = () => {
fullScreenToggle
dialogProps={{
open: !!(viewerState && viewerState.open),
onClose: onClose,
onClose: handleDialogClose,
fullWidth: true,
maxWidth: "lg",
}}

View File

@ -11,6 +11,7 @@ import {
saveMarkdown,
uploadMarkdownImage,
} from "../../../redux/thunks/viewer.ts";
import { confirmOperation } from "../../../redux/thunks/dialog.ts";
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts";
import CaretDown from "../../Icons/CaretDown.tsx";
@ -35,6 +36,31 @@ const MarkdownViewer = () => {
const [optionAnchorEl, setOptionAnchorEl] = useState<null | HTMLElement>(null);
const saveFunction = useRef(() => {});
const closeViewer = useCallback(() => {
dispatch(closeMarkdownViewer());
}, [dispatch]);
const handleDialogClose = useCallback(
(_: React.SyntheticEvent | object, reason?: "backdropClick" | "escapeKeyDown") => {
if (!saved && supportUpdate && (reason === "backdropClick" || reason === "escapeKeyDown")) {
dispatch(
confirmOperation(
t("application:modals.discardUnsavedConfirm"),
),
)
.then(() => {
closeViewer();
})
.catch(() => {});
return;
}
closeViewer();
},
[closeViewer, saved, supportUpdate, t, dispatch],
);
const loadContent = useCallback(() => {
if (!viewerState || !viewerState.open) {
return;
@ -50,9 +76,9 @@ const MarkdownViewer = () => {
setLoaded(true);
})
.catch(() => {
onClose();
closeViewer();
});
}, [viewerState]);
}, [viewerState, closeViewer]);
useEffect(() => {
if (!viewerState || !viewerState.open) {
@ -70,10 +96,6 @@ const MarkdownViewer = () => {
return dispatch(markdownImageAutocompleteSuggestions());
}, [viewerState?.open]);
const onClose = useCallback(() => {
dispatch(closeMarkdownViewer());
}, [dispatch]);
const openMore = useCallback(
(e: React.MouseEvent<any>) => {
setAnchorEl(e.currentTarget);
@ -154,7 +176,7 @@ const MarkdownViewer = () => {
fullScreenToggle
dialogProps={{
open: !!(viewerState && viewerState.open),
onClose: onClose,
onClose: handleDialogClose,
fullWidth: true,
maxWidth: "lg",
}}

View File

@ -44,7 +44,7 @@ const ViewerDialog = (props: ViewerDialogProps) => {
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [fullScreen, setFullScreen] = useState(props.fullScreen || isMobile);
const onClose = useCallback(() => {
props.dialogProps.onClose && props.dialogProps.onClose({}, "backdropClick");
props.dialogProps.onClose && props.dialogProps.onClose({}, "closeButtonClick" as any);
}, [props.dialogProps.onClose]);
return (
<Dialog