From a20d5ccc56856dd5bc4e2758ba31c9fcbe35f348 Mon Sep 17 00:00:00 2001 From: Weixuan Fu Date: Sat, 16 Mar 2024 14:36:18 +0800 Subject: [PATCH 1/2] autosave to changelog --- ui/src/App.tsx | 1 + .../WorkspaceSettingsModal.tsx | 2 + ui/src/components/DropdownTitle.tsx | 2 +- ui/src/components/VersionHistoryDrawer.tsx | 26 +++----- ui/src/db-tables/ChangelogsTable.ts | 23 +++---- ui/src/db-tables/UserSettingsTable.ts | 1 + ui/src/settings/MaximumChangelogSettings.tsx | 63 +++++++++++++++++++ ui/src/topbar/AppIsDirtyEventListener.tsx | 33 ++++++++-- ui/src/topbar/Topbar.tsx | 1 + ui/src/topbar/VersionNameTopbar.tsx | 9 ++- ui/src/types/dbTypes.ts | 1 + 11 files changed, 120 insertions(+), 42 deletions(-) create mode 100644 ui/src/settings/MaximumChangelogSettings.tsx diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 7be5572..fbf884b 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -92,6 +92,7 @@ export default function App() { changelogsTable?.create({ workflowID: curFlowID.current, json: graphJson, + isAutoSave: false, }), ]); userSettingsTable?.autoSave && diff --git a/ui/src/RecentFilesDrawer/WorkspaceSettingsModal.tsx b/ui/src/RecentFilesDrawer/WorkspaceSettingsModal.tsx index ed7d078..1eb48f1 100644 --- a/ui/src/RecentFilesDrawer/WorkspaceSettingsModal.tsx +++ b/ui/src/RecentFilesDrawer/WorkspaceSettingsModal.tsx @@ -14,6 +14,7 @@ import FolderOnTopSettings from "../settings/FolderOnTopSettings"; import TwoWaySyncSettings from "../settings/TwoWaySyncSettings"; import CommonCheckboxSettings from "../settings/CommonCheckboxSettings"; import SelectMyWorkflowsDir from "../settings/SelectMyWorkflowsDir"; +import { MaximumChangelog } from "../settings/MaximumChangelogSettings"; export default function WorkspaceSettingsModal({ onClose, @@ -47,6 +48,7 @@ export default function WorkspaceSettingsModal({ settingKey="overwriteCurWorkflowWhenDroppingFileToCanvas" text="Overwrite current workflow when drag and drop workflow file to canvas" /> + diff --git a/ui/src/components/DropdownTitle.tsx b/ui/src/components/DropdownTitle.tsx index 32869a5..34b9db6 100644 --- a/ui/src/components/DropdownTitle.tsx +++ b/ui/src/components/DropdownTitle.tsx @@ -136,7 +136,7 @@ export default function DropdownTitle() { height={TOPBAR_BUTTON_HEIGHT + "px"} aria-label="menu" size={"sm"} - // backgroundColor={"gray.700"} + // backgroundColor={"#4a4d6b"} backgroundColor={"teal.600"} > File diff --git a/ui/src/components/VersionHistoryDrawer.tsx b/ui/src/components/VersionHistoryDrawer.tsx index 4e63e03..0f0c161 100644 --- a/ui/src/components/VersionHistoryDrawer.tsx +++ b/ui/src/components/VersionHistoryDrawer.tsx @@ -34,7 +34,8 @@ import DeleteConfirm from "./DeleteConfirm"; export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) { const toast = useToast(); - const { curFlowID, isDirty, loadWorkflowID } = useContext(WorkspaceContext); + const { curFlowID, isDirty, loadWorkflowID, curVersion } = + useContext(WorkspaceContext); const [active, setActive] = useState(0); // 0: version、1: changelog const [selectedVersion, setSelectedVersion] = useState(null); const [changelogs, setChangelogs] = useState([]); @@ -55,11 +56,6 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) { const vers = (await workflowVersionsTable?.listByWorkflowID(curFlowID!)) ?? []; setVersions(vers); - - const graphJson = JSON.stringify(app.graph.serialize()); - const selectedVer = vers?.filter((c) => c.json === graphJson); - const selectedVerID = selectedVer?.[0]?.id; - selectedVerID && setSelectedVersion(selectedVerID); }; const onDelete = (id: string) => { @@ -88,7 +84,7 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) { return ( void }) { mb={1} justify={"space-between"} backgroundColor={ - version.id === selectedVersion ? "teal.300" : undefined + version.id === curVersion?.id ? "teal.300" : undefined } borderRadius={4} > @@ -154,12 +150,6 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) { } app.loadGraphData(JSON.parse(version.json)); loadWorkflowID(curFlowID!, version.id); - // toast({ - // title: `Switched to version "${version.name}"`, - // status: "success", - // duration: 3000, - // isClosable: true, - // }); onClose(); }} > @@ -182,7 +172,7 @@ export function VersionHistoryDrawer({ onClose }: { onClose: () => void }) { } spacing={2}> {changelogs?.map((c) => { return ( - + {c.isAutoSave ? ( - Auto save + + Auto save + ) : null} ); diff --git a/ui/src/db-tables/ChangelogsTable.ts b/ui/src/db-tables/ChangelogsTable.ts index dce0ae0..1f89e6c 100644 --- a/ui/src/db-tables/ChangelogsTable.ts +++ b/ui/src/db-tables/ChangelogsTable.ts @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from "uuid"; -import { Table } from "./WorkspaceDB"; +import { Table, userSettingsTable } from "./WorkspaceDB"; import { Changelog } from "../types/dbTypes"; import { TableBase } from "./TableBase"; import { indexdb } from "./indexdb"; @@ -29,6 +29,7 @@ export class ChangelogsTable extends TableBase { public async create(input: { json: string; workflowID: string; + isAutoSave: boolean; }): Promise { const latest = await this.getLastestByWorkflowID(input.workflowID); // only create when there is a change @@ -41,29 +42,23 @@ export class ChangelogsTable extends TableBase { json: input.json, workflowID: input.workflowID, createTime: Date.now(), + isAutoSave: input.isAutoSave, }; + this.deleteLogsExceedLimit(input.workflowID); await indexdb.changelogs.add(change); await this.saveDiskDB(); return change; } - async deleteLogsExceedLimit(workflowID: string, limit: number = LIMIT) { + async deleteLogsExceedLimit(workflowID: string) { const all = await indexdb.changelogs .where("workflowID") .equals(workflowID) .reverse() .sortBy("createTime"); - const autoSaved = all.filter((c) => c.isAutoSave === true); - const manualSaved = all.filter( - (c) => c.isAutoSave == null || c.isAutoSave == false, - ); - - if (autoSaved.length > limit) { - const toDelete = autoSaved.slice(limit); - await indexdb.changelogs.bulkDelete(toDelete.map((c) => c.id)); - await this.saveDiskDB(); - } - if (manualSaved.length > limit) { - const toDelete = manualSaved.slice(limit); + const limit = + (await userSettingsTable?.getSetting("maximumChangelogNumber")) ?? LIMIT; + if (all.length > limit) { + const toDelete = all.slice(limit); await indexdb.changelogs.bulkDelete(toDelete.map((c) => c.id)); await this.saveDiskDB(); } diff --git a/ui/src/db-tables/UserSettingsTable.ts b/ui/src/db-tables/UserSettingsTable.ts index 9e9f50e..809d75f 100644 --- a/ui/src/db-tables/UserSettingsTable.ts +++ b/ui/src/db-tables/UserSettingsTable.ts @@ -55,6 +55,7 @@ export class UserSettingsTable extends TableBase { foldersOnTop: false, cloudHost: "https://www.comfyspace.art", overwriteCurWorkflowWhenDroppingFileToCanvas: false, + maximumChangelogNumber: 200, }; } diff --git a/ui/src/settings/MaximumChangelogSettings.tsx b/ui/src/settings/MaximumChangelogSettings.tsx new file mode 100644 index 0000000..9e7695c --- /dev/null +++ b/ui/src/settings/MaximumChangelogSettings.tsx @@ -0,0 +1,63 @@ +import { Stack, Text, NumberInput, NumberInputField } from "@chakra-ui/react"; +import { useState, useEffect } from "react"; +import { userSettingsTable } from "../db-tables/WorkspaceDB"; + +export function MaximumChangelog() { + const [changelogNumber, setChangelogNumber] = useState(200); + + const onInputChange = (newValue: string) => { + if (!newValue.length) { + setChangelogNumber(0); + return; + } + setChangelogNumber(parseInt(newValue)); + }; + + const onBlur = async () => { + if (changelogNumber < 0) { + setChangelogNumber(0); + } + await userSettingsTable?.upsert({ + maximumChangelogNumber: Number(changelogNumber), + }); + }; + const getMaximumChangelogNumber = () => { + userSettingsTable?.getSetting("maximumChangelogNumber").then((res) => { + setChangelogNumber(res); + }); + }; + + useEffect(() => { + getMaximumChangelogNumber(); + }, []); + + return ( + + + Maximum number of save change history to store. This does not include + versions that you created by "Create Version", which are always stored + and has no limit. + + + onInputChange(e)} + onBlur={() => { + onBlur(); + }} + onKeyUp={(e) => { + if (e.key === "Enter") { + onBlur(); + } + }} + > + + + + ); +} diff --git a/ui/src/topbar/AppIsDirtyEventListener.tsx b/ui/src/topbar/AppIsDirtyEventListener.tsx index 4cdb385..7ac99e9 100644 --- a/ui/src/topbar/AppIsDirtyEventListener.tsx +++ b/ui/src/topbar/AppIsDirtyEventListener.tsx @@ -2,8 +2,12 @@ import { useContext, useEffect } from "react"; // @ts-expect-error import { app } from "/scripts/app.js"; import { WorkspaceContext } from "../WorkspaceContext"; -import { userSettingsTable, workflowsTable } from "../db-tables/WorkspaceDB"; -import { useToast } from "@chakra-ui/react"; +import { + changelogsTable, + userSettingsTable, + workflowsTable, +} from "../db-tables/WorkspaceDB"; +import { Box, useToast } from "@chakra-ui/react"; import { matchShortcut } from "../utils"; import { EShortcutKeys } from "../types/dbTypes"; import useDebounceFn from "../customHooks/useDebounceFn"; @@ -43,24 +47,43 @@ export default function AppIsDirtyEventListener() { }); document.addEventListener("keydown", async function (event) { if (document.visibilityState === "hidden") return; - const matchResult = await matchShortcut(event); if (matchResult) { shortcutListener(matchResult); + } else if ( // @ts-expect-error - } else if (event.target?.matches("input, textarea")) { + event.target?.matches("input, textarea") && + Object.keys(app.canvas.selected_nodes ?? {}).length + ) { onIsDirty(); } }); }, []); const autoSaveWorkflow = async () => { // autosave workflow if enabled + if (!workflowsTable?.curWorkflow?.id) { + return; + } const graphJson = JSON.stringify(app.graph.serialize()); graphJson != null && - (await workflowsTable?.updateFlow(workflowsTable.curWorkflow!.id, { + (await workflowsTable?.updateFlow(workflowsTable.curWorkflow.id, { json: graphJson, })); + changelogsTable?.create({ + workflowID: workflowsTable.curWorkflow.id, + isAutoSave: true, + json: graphJson, + }); setIsDirty(false); + toast({ + position: "bottom-left", + duration: 1000, + render: () => ( + + Auto saved + + ), + }); }; const [debounceAutoSaveWorkflow, _cancelDebounceAutoSaveWorkflow] = useDebounceFn(autoSaveWorkflow, 1000); diff --git a/ui/src/topbar/Topbar.tsx b/ui/src/topbar/Topbar.tsx index f4b3db2..72a5a44 100644 --- a/ui/src/topbar/Topbar.tsx +++ b/ui/src/topbar/Topbar.tsx @@ -85,6 +85,7 @@ export function Topbar({ curFlowName, setCurFlowName }: Props) { diff --git a/ui/src/model-manager/topbar/ModelManagerTopbar.tsx b/ui/src/model-manager/topbar/ModelManagerTopbar.tsx index 171636f..4683165 100644 --- a/ui/src/model-manager/topbar/ModelManagerTopbar.tsx +++ b/ui/src/model-manager/topbar/ModelManagerTopbar.tsx @@ -1,23 +1,18 @@ -import { Button, HStack } from "@chakra-ui/react"; -import { lazy, useEffect, useState, DragEvent } from "react"; +import { Button, Stack } from "@chakra-ui/react"; +import { lazy, useEffect, DragEvent, useContext } from "react"; import ModelsListDrawer from "../models-list-drawer/ModelsListDrawer"; -import { IconGripVertical } from "@tabler/icons-react"; -import Draggable from "../../components/Draggable"; // @ts-ignore import { app } from "/scripts/app.js"; import "./index.css"; -import { userSettingsTable } from "../../db-tables/WorkspaceDB"; -import { ModelManagerPosition } from "../../types/dbTypes"; +import { WorkspaceContext } from "../../WorkspaceContext"; const AddMissingModelsButton = lazy( () => import("./InstallMissingModelsButton"), ); export default function ModelManagerTopbar() { - const [showMyModels, setShowMyModels] = useState(false); - const [positionStyle, setPositionStyle] = useState(); - + const { setRoute, route } = useContext(WorkspaceContext); const handleModelDrop = async ( e: DragEvent & { canvasX: number; canvasY: number }, ) => { @@ -35,85 +30,29 @@ export default function ModelManagerTopbar() { }; useEffect(() => { - userSettingsTable?.getSetting("modelManagerTopBarStyle").then((res) => { - updatePosition(res, false); - }); app.canvasEl.addEventListener("drop", handleModelDrop); return () => { app.canvasEl.removeEventListener("drop", handleModelDrop); }; }, []); - const updatePosition = ( - position?: ModelManagerPosition, - needUpdateDB: boolean = false, - ) => { - const { top: curTop = 0, right: curRight = 0 } = positionStyle || {}; - const { top = 0, right = 0 } = position ?? {}; - let newTop = curTop + top; - let newRight = positionStyle === undefined ? right : curRight - right; - const clientWidth = document.documentElement.clientWidth; - const clientHeight = document.documentElement.clientHeight; - const panelElement = document.getElementById("modelManagerTopBar"); - const offsetWidth = panelElement?.offsetWidth || 224; - - if (newTop + 32 > clientHeight) newTop = clientHeight - 38; - if (newRight + offsetWidth > clientWidth) - newRight = clientWidth - offsetWidth - 4; - - setPositionStyle({ - top: Math.max(4, newTop), - right: Math.max(4, newRight), - }); - - needUpdateDB && - userSettingsTable?.upsert({ - modelManagerTopBarStyle: { top: newTop, right: newRight }, - }); - }; - return ( - { - updatePosition({ top: position.y, right: position.x }, true); - }} - dragIconId="dragModelManagerTopBarIcon" - > - {positionStyle ? ( - - - - - {showMyModels && ( - setShowMyModels(false)} /> - )} - - ) : ( - "" + + +
+ +
+ {route === "modelList" && ( + setRoute("root")} /> )} -
+
); } diff --git a/ui/src/topbar/Topbar.tsx b/ui/src/topbar/Topbar.tsx index 72a5a44..96e744c 100644 --- a/ui/src/topbar/Topbar.tsx +++ b/ui/src/topbar/Topbar.tsx @@ -10,7 +10,7 @@ import { IconLock, } from "@tabler/icons-react"; import DropdownTitle from "../components/DropdownTitle"; -import { useCallback, useContext, useEffect, useState } from "react"; +import { lazy, useCallback, useContext, useEffect, useState } from "react"; import EditFlowName from "../components/EditFlowName"; import { WorkspaceContext } from "../WorkspaceContext"; import { PanelPosition } from "../types/dbTypes"; @@ -19,6 +19,9 @@ import { SharedTopbarButton } from "../share/SharedTopbarButton"; import VersionNameTopbar from "./VersionNameTopbar"; import { userSettingsTable, workflowsTable } from "../db-tables/WorkspaceDB"; import { TOPBAR_BUTTON_HEIGHT } from "../const"; +const ModelManagerTopbar = lazy( + () => import("../model-manager/topbar/ModelManagerTopbar"), +); interface Props { curFlowName: string | null; @@ -94,16 +97,7 @@ export function Topbar({ curFlowName, setCurFlowName }: Props) { - +