Warn to save content
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
8ca4cf3260
commit
fe318126bd
@ -126,6 +126,8 @@ import DebugCanvas, {
|
||||
loadSavedDebugState,
|
||||
} from "./components/DebugCanvas";
|
||||
import { AIComponents } from "./components/AI";
|
||||
import type { SaveWarningRef } from "./components/SaveWarning";
|
||||
import { SaveWarning } from "./components/SaveWarning";
|
||||
|
||||
polyfill();
|
||||
|
||||
@ -331,6 +333,8 @@ const ExcalidrawWrapper = () => {
|
||||
|
||||
const [langCode, setLangCode] = useAppLangCode();
|
||||
|
||||
const activityRef = useRef<SaveWarningRef | null>(null);
|
||||
|
||||
// initial state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -615,6 +619,8 @@ const ExcalidrawWrapper = () => {
|
||||
collabAPI.syncElements(elements);
|
||||
}
|
||||
|
||||
activityRef.current?.activity();
|
||||
|
||||
// this check is redundant, but since this is a hot path, it's best
|
||||
// not to evaludate the nested expression every time
|
||||
if (!LocalData.isSavePaused()) {
|
||||
@ -856,6 +862,7 @@ const ExcalidrawWrapper = () => {
|
||||
setTheme={(theme) => setAppTheme(theme)}
|
||||
refresh={() => forceRefresh((prev) => !prev)}
|
||||
/>
|
||||
<SaveWarning ref={activityRef} />
|
||||
<AppWelcomeScreen
|
||||
onCollabDialogOpen={onCollabDialogOpen}
|
||||
isCollabEnabled={!isCollabDisabled}
|
||||
|
40
excalidraw-app/components/SaveWarning.tsx
Normal file
40
excalidraw-app/components/SaveWarning.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
import { t } from "../../packages/excalidraw/i18n";
|
||||
import { getShortcutKey } from "../../packages/excalidraw/utils";
|
||||
|
||||
export type SaveWarningRef = {
|
||||
activity: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const SaveWarning = forwardRef<SaveWarningRef, {}>((props, ref) => {
|
||||
const dialogRef = useRef<HTMLDivElement | null>(null);
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
/**
|
||||
* Call this API method via the ref to hide warning message
|
||||
* and start an idle timer again.
|
||||
*/
|
||||
activity: async () => {
|
||||
if (timerRef.current != null) {
|
||||
clearTimeout(timerRef.current);
|
||||
dialogRef.current?.classList.remove("animate");
|
||||
}
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
timerRef.current = null;
|
||||
dialogRef.current?.classList.add("animate");
|
||||
}, 5000);
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div ref={dialogRef} className="alert-save">
|
||||
<div className="dialog">
|
||||
{t("alerts.saveYourContent", {
|
||||
shortcut: getShortcutKey("CtrlOrCmd + S"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,3 +1,13 @@
|
||||
@mixin animate($animation,$duration,$method,$times){
|
||||
animation: $animation $duration $method $times;
|
||||
}
|
||||
|
||||
@mixin keyframes($name){
|
||||
@keyframes #{$name}{
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
.excalidraw {
|
||||
--color-primary-contrast-offset: #625ee0; // to offset Chubb illusion
|
||||
|
||||
@ -18,6 +28,43 @@
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
.alert-save {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 10vh;
|
||||
margin-inline: auto;
|
||||
width: fit-content;
|
||||
|
||||
opacity: 0;
|
||||
transition: all 0s;
|
||||
|
||||
&.animate {
|
||||
opacity: 1;
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
margin-inline: 10px;
|
||||
padding: 1rem;
|
||||
padding-inline: 1.25rem;
|
||||
|
||||
resize: none;
|
||||
white-space: pre-wrap;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--color-warning);
|
||||
border-radius: var(--border-radius-md);
|
||||
border: 1px solid var(--dialog-border-color);
|
||||
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.encrypted-icon {
|
||||
border-radius: var(--space-factor);
|
||||
color: var(--color-primary);
|
||||
|
@ -230,7 +230,8 @@
|
||||
"resetLibrary": "This will clear your library. Are you sure?",
|
||||
"removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
|
||||
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
|
||||
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!"
|
||||
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!",
|
||||
"saveYourContent": "Don't forget to save your content ({{shortcut}})!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Unsupported file type.",
|
||||
|
Loading…
x
Reference in New Issue
Block a user