Skip to content

Commit

Permalink
refactor alert component
Browse files Browse the repository at this point in the history
  • Loading branch information
ibastawisi committed May 4, 2024
1 parent 6a4cd4b commit 56b763c
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 95 deletions.
24 changes: 14 additions & 10 deletions src/components/DocumentActions/Delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useDispatch, actions } from "@/store";
import { UserDocument } from "@/types";
import { DeleteForever } from "@mui/icons-material";
import { IconButton, ListItemIcon, ListItemText, MenuItem } from "@mui/material";
import { v4 as uuid } from "uuid";

const DeleteDocument: React.FC<{ userDocument: UserDocument, variant?: 'menuitem' | 'iconbutton', closeMenu?: () => void }> = ({ userDocument, variant = 'iconbutton', closeMenu }) => {
const dispatch = useDispatch();
Expand All @@ -13,16 +14,19 @@ const DeleteDocument: React.FC<{ userDocument: UserDocument, variant?: 'menuitem
const name = localDocument?.name || cloudDocument?.name || "This Document";

const handleDelete = async () => {
if(closeMenu) closeMenu();
dispatch(actions.alert(
{
title: `Delete ${isLocal ? "Local" : "Cloud"} Document`,
content: `Are you sure you want to delete ${name}?`,
action: isLocal ?
`dispatch(actions.deleteLocalDocument("${id}"))` :
`dispatch(actions.deleteCloudDocument("${id}"))`
}
));
if (closeMenu) closeMenu();
const alert = {
title: `Delete ${isLocal ? "Local" : "Cloud"} Document`,
content: `Are you sure you want to delete ${name}?`,
actions: [
{ label: "Cancel", id: uuid() },
{ label: "Delete", id: uuid() },
]
};
const response = await dispatch(actions.alert(alert));
if (response.payload === alert.actions[1].id) {
dispatch(isLocal ? actions.deleteLocalDocument(id) : actions.deleteCloudDocument(id));
}
};

if (variant === 'menuitem') return (
Expand Down
18 changes: 13 additions & 5 deletions src/components/Documents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Box, Avatar, Button, Typography, Grid, Card, CardActionArea, CardHeader
import { PostAdd, UploadFile, Help, Storage, Science, Pageview } from '@mui/icons-material';
import DocumentSortControl, { sortDocuments } from './DocumentSortControl';
import DocumentFilterControl, { filterDocuments } from './DocumentFilterControl';
import { v4 as uuid } from 'uuid';

const Documents: React.FC = () => {
const user = useSelector(state => state.user);
Expand Down Expand Up @@ -56,13 +57,20 @@ const Documents: React.FC = () => {
}
}

function addDocument(document: BackupDocument, shouldNavigate?: boolean) {
async function addDocument(document: BackupDocument, shouldNavigate?: boolean) {
if (documents.find(d => d.id === document.id && d.local)) {
dispatch(actions.alert({
title: "Document already exists",
const alert = {
title: `Document already exists`,
content: `Do you want to overwrite ${document.name}?`,
action: `dispatch(actions.updateLocalDocument({id:"${document.id}",partial:${JSON.stringify(document)}})).then(() => {${shouldNavigate ? `navigate("/edit/${document.id}");` : ""}})`
}))
actions: [
{ label: "Cancel", id: uuid() },
{ label: "Overwrite", id: uuid() }
]
};
const response = await dispatch(actions.alert(alert));
if (response.payload === alert.actions[1].id) {
dispatch(actions.updateLocalDocument({ id: document.id, partial: document })).then(() => { if (shouldNavigate) navigate(`/edit/${document.id}`) });
}
} else {
dispatch(actions.createLocalDocument(document)).then(() => {
shouldNavigate && navigate(`/edit/${document.id}`);
Expand Down
20 changes: 15 additions & 5 deletions src/components/EditRevisionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { actions, useDispatch, useSelector } from '@/store';
import { CLEAR_HISTORY_COMMAND, type LexicalEditor } from '@/editor';
import useOnlineStatus from '@/hooks/useOnlineStatus';
import NProgress from 'nprogress';
import { v4 as uuid } from 'uuid';

const RevisionCard: React.FC<{
revision: UserDocumentRevision,
Expand Down Expand Up @@ -137,12 +138,21 @@ const RevisionCard: React.FC<{
await dispatch(actions.updateCloudDocument(payload));
}

const deleteRevision = () => {
const deleteRevision = async () => {
const variant = isLocalRevision ? 'Local' : 'Cloud';
const title = `Delete ${variant} Revision?`;
const content = `Are you sure you want to delete this ${variant} revision?`;
const action = `dispatch(actions.delete${variant}Revision({ id: "${revision.id}", documentId: "${revision.documentId}" }))`;
dispatch(actions.alert({ title, content, action }));
const alert = {
title: `Delete ${variant} Revision?`,
content: `Are you sure you want to delete this ${variant} revision?`,
actions: [
{ label: "Cancel", id: uuid() },
{ label: "Delete", id: uuid() },
]
};
const response = await dispatch(actions.alert(alert));
if (response.payload === alert.actions[1].id) {
if (isLocalRevision) dispatch(actions.deleteLocalRevision({ id: revision.id, documentId: revision.documentId }));
else dispatch(actions.deleteCloudRevision({ id: revision.id, documentId: revision.documentId }));
}
}

return (
Expand Down
48 changes: 29 additions & 19 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MutableRefObject } from 'react';
import { memo } from 'react';
import { EditorDocument } from '@/types';
import type { EditorState, LexicalEditor } from '@/editor/types';
import { COMMAND_PRIORITY_LOW, SET_ANNOUNCEMENT_COMMAND, UPDATE_DOCUMENT_COMMAND } from '@/editor';
import { COMMAND_PRIORITY_LOW, ANNOUNCE_COMMAND, UPDATE_DOCUMENT_COMMAND, ALERT_COMMAND, mergeRegister } from '@/editor';
import { actions, useDispatch } from '@/store';
import Editor from '@/editor/Editor';

Expand All @@ -15,24 +15,34 @@ const Container: React.FC<{
const dispatch = useDispatch();
const editorRefCallback = (editor: LexicalEditor) => {
if (editorRef) editorRef.current = editor;
editor.registerCommand(
SET_ANNOUNCEMENT_COMMAND,
(payload) => {
dispatch((actions.announce(payload)))
return false;
},
COMMAND_PRIORITY_LOW
);
editor.registerCommand(
UPDATE_DOCUMENT_COMMAND,
() => {
if (editorRef && editorRef.current) {
const editorState = editorRef.current.getEditorState();
onChange?.(editorState, editorRef.current, new Set());
}
return false;
},
COMMAND_PRIORITY_LOW
return mergeRegister(
editor.registerCommand(
ANNOUNCE_COMMAND,
(payload) => {
dispatch((actions.announce(payload)))
return false;
},
COMMAND_PRIORITY_LOW
),
editor.registerCommand(
ALERT_COMMAND,
(payload) => {
dispatch(actions.alert(payload));
return false;
},
COMMAND_PRIORITY_LOW
),
editor.registerCommand(
UPDATE_DOCUMENT_COMMAND,
() => {
if (editorRef && editorRef.current) {
const editorState = editorRef.current.getEditorState();
onChange?.(editorState, editorRef.current, new Set());
}
return false;
},
COMMAND_PRIORITY_LOW
),
);
};

Expand Down
25 changes: 3 additions & 22 deletions src/components/Layout/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
"use client"
import { useRouter } from 'next/navigation';
import { useDispatch, useSelector, actions } from '@/store';
import { useSelector } from '@/store';
import useFixedBodyScroll from '@/hooks/useFixedBodyScroll';
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material';
import { signIn } from 'next-auth/react';

export default function AlertDialog() {
const alert = useSelector(state => state.ui.alerts[0]);
const dispatch = useDispatch();
const router = useRouter();
const navigate = (path: string) => router.push(path);
const login = () => signIn("google", undefined, { prompt: "select_account" });

const handleClose = () => dispatch(actions.clearAlert());
const handleConfirm = () => {
const serializedAction = alert?.action;
if (serializedAction) {
const action = new Function("dispatch", "actions", "navigate", "login", serializedAction);
action.bind(null, dispatch, actions, navigate, login)();
}
dispatch(actions.clearAlert());
}

useFixedBodyScroll(!!alert);

if (!alert) return null;

return (
<Dialog open onClose={handleClose}>
<Dialog open>
<DialogTitle>{alert.title}</DialogTitle>
<DialogContent>
<DialogContentText>{alert.content}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleConfirm} autoFocus>OK</Button>
{alert.actions.map(({ label, id }) => <Button key={id} id={id} autoFocus>{label}</Button>)}
</DialogActions>
</Dialog>
);
Expand Down
7 changes: 3 additions & 4 deletions src/editor/commands.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client";
import { Announcement } from '@/types';
import { Alert, Announcement } from '@/types';
import { LexicalCommand, createCommand } from 'lexical';

export type SetAnnouncementPayload = Readonly<Announcement>;

export const SET_ANNOUNCEMENT_COMMAND: LexicalCommand<SetAnnouncementPayload> = createCommand();
export const ANNOUNCE_COMMAND: LexicalCommand<Readonly<Announcement>> = createCommand();
export const ALERT_COMMAND: LexicalCommand<Readonly<Alert>> = createCommand();
export const UPDATE_DOCUMENT_COMMAND: LexicalCommand<void> = createCommand();
1 change: 1 addition & 0 deletions src/editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "lexical";
export * from '@lexical/utils';
export * from "./commands";
4 changes: 2 additions & 2 deletions src/editor/plugins/ToolbarPlugin/Dialogs/OCRDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useState, useEffect, useCallback } from "react";
import Compressor from 'compressorjs';
import { SET_DIALOGS_COMMAND } from "./commands";
import { Announcement } from "@/types";
import { SET_ANNOUNCEMENT_COMMAND } from "@/editor/commands";
import { ANNOUNCE_COMMAND } from "@/editor/commands";

const FASTAPI_URL = process.env.NEXT_PUBLIC_FASTAPI_URL;

Expand Down Expand Up @@ -86,7 +86,7 @@ const OCRDialog = ({ open, editor }: { open: boolean, editor: LexicalEditor }) =
}, []);

const annouunce = useCallback((announcement: Announcement) => {
editor.dispatchCommand(SET_ANNOUNCEMENT_COMMAND, announcement);
editor.dispatchCommand(ANNOUNCE_COMMAND, announcement);
}, [editor]);

const closeDialog = () => {
Expand Down
88 changes: 70 additions & 18 deletions src/editor/plugins/ToolbarPlugin/Dialogs/Sketch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, de
import dynamic from 'next/dynamic';
import { ImageNode } from '@/editor/nodes/ImageNode';
import type { ExcalidrawElement, ExcalidrawImageElement, FileId } from '@excalidraw/excalidraw/types/element/types';
import { ALERT_COMMAND } from '@/editor/commands';
import { v4 as uuid } from 'uuid';

const Excalidraw = dynamic<ExcalidrawProps>(() => import('@excalidraw/excalidraw/dist/excalidraw.production.min.js').then((module) => ({ default: module.Excalidraw })), { ssr: false });
const AddLibraries = dynamic(() => import('./AddLibraries'), { ssr: false });
Expand Down Expand Up @@ -57,15 +59,15 @@ function SketchDialog({ editor, node, open }: { editor: LexicalEditor, node: Ima
const handleSubmit = async () => {
const elements = excalidrawAPI?.getSceneElements();
const files = excalidrawAPI?.getFiles();
const exportToSvg = await import('@excalidraw/excalidraw/dist/excalidraw.production.min.js').then((module) => module.exportToSvg).catch((e) => console.error(e));
const exportToSvg = await import('@excalidraw/excalidraw/dist/excalidraw.production.min.js').then((module) => module.exportToSvg).catch(console.error);
if (!elements || !files || !exportToSvg) return;
const element: SVGElement = await exportToSvg({
appState: {
exportEmbedScene: true,
},
elements: elements!,
files: files!,
exportPadding: 16,
exportPadding: (!node || $isSketchNode(node)) ? 16 : 0,
});

const serialized = new XMLSerializer().serializeToString(element);
Expand All @@ -91,31 +93,81 @@ function SketchDialog({ editor, node, open }: { editor: LexicalEditor, node: Ima
})
}

const handleClose = () => {
const unsavedScene = localStorage.getItem("excalidraw");
if (unsavedScene && confirm("discard unsaved changes?")) {
const handleClose = async () => {
function discard() {
clearLocalStorage();
closeDialog();
restoreSelection();
} else {
}
function cancel() {
closeDialog();
restoreSelection();
}
const unsavedScene = localStorage.getItem("excalidraw");
if (unsavedScene) {
const alert = {
title: "Discard unsaved Changes",
content: "Are you sure you want to discard unsaved changes?",
actions: [
{ label: "Cancel", id: uuid() },
{ label: "Discard", id: uuid() },
]
};
editor.dispatchCommand(ALERT_COMMAND, alert);
const id = await new Promise((resolve) => {
const handler = (event: MouseEvent): any => {
const target = event.target as HTMLElement;
const button = target.closest("button");
const paper = target.closest(".MuiDialog-paper");
if (paper && !button) return document.addEventListener("click", handler, { once: true });
resolve(button?.id ?? null);
};
setTimeout(() => { document.addEventListener("click", handler, { once: true }); }, 0);
});
if (id === alert.actions[1].id) discard();
} else cancel();
}

async function restoreSerializedScene(serialized: string) {
const scene = JSON.parse(serialized);
const files = Object.values(scene.files) as BinaryFileData[];
if (files.length) excalidrawAPI?.addFiles(files);
const { getNonDeletedElements, isLinearElement } = await import('@excalidraw/excalidraw/dist/excalidraw.production.min.js')
.then((module) => ({ getNonDeletedElements: module.getNonDeletedElements, isLinearElement: module.isLinearElement }));
const elements = getNonDeletedElements(scene.elements).map((element: ExcalidrawElement) =>
isLinearElement(element) ? { ...element, lastCommittedPoint: null } : element,
);
return excalidrawAPI?.updateScene({ elements, appState: { theme: theme.palette.mode } });
}

const loadSceneOrLibrary = async () => {
const unsavedScene = localStorage.getItem("excalidraw");
if (unsavedScene && confirm("restore unsaved scene from last session?")) {
const scene = JSON.parse(unsavedScene);
const files = Object.values(scene.files) as BinaryFileData[];
if (files.length) excalidrawAPI?.addFiles(files);
const { getNonDeletedElements, isLinearElement } = await import('@excalidraw/excalidraw/dist/excalidraw.production.min.js')
.then((module) => ({ getNonDeletedElements: module.getNonDeletedElements, isLinearElement: module.isLinearElement }));
const elements = getNonDeletedElements(scene.elements).map((element: ExcalidrawElement) =>
isLinearElement(element) ? { ...element, lastCommittedPoint: null } : element,
);
return excalidrawAPI?.updateScene({ elements, appState: { theme: theme.palette.mode } });
}
if (unsavedScene) {
const alert = {
title: "Restore last unsaved Changes",
content: "You've unsaved changes from last session. Do you want to restore them?",
actions: [
{ label: "Cancel", id: uuid() },
{ label: "Restore", id: uuid() },
]
};
editor.dispatchCommand(ALERT_COMMAND, alert);
const id = await new Promise((resolve) => {
const handler = (event: MouseEvent): any => {
const target = event.target as HTMLElement;
const button = target.closest("button");
const paper = target.closest(".MuiDialog-paper");
if (paper && !button) return document.addEventListener("click", handler, { once: true });
resolve(button?.id ?? null);
};
setTimeout(() => { document.addEventListener("click", handler, { once: true }); }, 0);
});
if (!id || id === alert.actions[0].id) tryLoadSceneFromNode();
if (id === alert.actions[1].id) restoreSerializedScene(unsavedScene);
} else tryLoadSceneFromNode();
};

async function tryLoadSceneFromNode() {
const src = node?.getSrc();
if (!src) return;
const blob = await (await fetch(src)).blob();
Expand Down Expand Up @@ -147,7 +199,7 @@ function SketchDialog({ editor, node, open }: { editor: LexicalEditor, node: Ima
} catch (error) {
console.error(error);
}
};
}

async function convertImagetoSketch(src: string) {
const now = Date.now();
Expand Down
4 changes: 2 additions & 2 deletions src/editor/plugins/ToolbarPlugin/Tools/AITools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { KeyboardArrowDown, AutoAwesome, UnfoldMore, UnfoldLess, PlayArrow, Imag
import { SxProps, Theme } from '@mui/material/styles';
import { useCompletion } from "ai/react";
import { SET_DIALOGS_COMMAND } from "../Dialogs/commands";
import { SET_ANNOUNCEMENT_COMMAND, UPDATE_DOCUMENT_COMMAND } from "@/editor/commands";
import { ANNOUNCE_COMMAND, UPDATE_DOCUMENT_COMMAND } from "@/editor/commands";
import { Announcement } from "@/types";

export default function AITools({ editor, sx }: { editor: LexicalEditor, sx?: SxProps<Theme> }): JSX.Element {
Expand All @@ -30,7 +30,7 @@ export default function AITools({ editor, sx }: { editor: LexicalEditor, sx?: Sx
});

const annouunce = useCallback((announcement: Announcement) => {
editor.dispatchCommand(SET_ANNOUNCEMENT_COMMAND, announcement);
editor.dispatchCommand(ANNOUNCE_COMMAND, announcement);
}, [editor]);

const [isCollapsed, setIsCollapsed] = useState(true);
Expand Down
Loading

0 comments on commit 56b763c

Please sign in to comment.