Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dc3aa557c | ||
|
|
316f5c1def | ||
|
|
d80e791547 | ||
|
|
b37e9b6449 | ||
|
|
87b82d6f2d | ||
|
|
ce56822a69 | ||
|
|
1cf711e4fa | ||
|
|
b08c4e0079 | ||
|
|
b2052ff205 | ||
|
|
fb5fac0621 | ||
|
|
7b64353aba |
+79
-43
@@ -44,13 +44,15 @@ import { useStateRef } from "./customHooks/useStateRef";
|
|||||||
import { indexdb } from "./db-tables/indexdb";
|
import { indexdb } from "./db-tables/indexdb";
|
||||||
import EnableTwowaySyncConfirm from "./settings/EnableTwowaySyncConfirm";
|
import EnableTwowaySyncConfirm from "./settings/EnableTwowaySyncConfirm";
|
||||||
import { deepJsonDiffCheck } from "./utils/deepJsonDiffCheck";
|
import { deepJsonDiffCheck } from "./utils/deepJsonDiffCheck";
|
||||||
|
import {
|
||||||
|
OPEN_TAB_EVENT,
|
||||||
|
tabDataManager,
|
||||||
|
} from "./topbar/multipleTabs/TabDataManager";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [curFlowName, setCurFlowName] = useState<string | null>(null);
|
|
||||||
const [route, setRoute] = useState<WorkspaceRoute>("root");
|
const [route, setRoute] = useState<WorkspaceRoute>("root");
|
||||||
const [loadingDB, setLoadingDB] = useState(true);
|
const [loadingDB, setLoadingDB] = useState(true);
|
||||||
const [flowID, setFlowID] = useState<string | null>(null);
|
const [flowID, setFlowID] = useState<string | null>(null);
|
||||||
const curFlowID = useRef<string | null>(null);
|
|
||||||
|
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
const workspaceContainerRef = useRef(null);
|
const workspaceContainerRef = useRef(null);
|
||||||
@@ -59,9 +61,10 @@ export default function App() {
|
|||||||
const developmentEnvLoadFirst = useRef(false);
|
const developmentEnvLoadFirst = useRef(false);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [curVersion, setCurVersion] = useStateRef<WorkflowVersion | null>(null);
|
const [curVersion, setCurVersion] = useStateRef<WorkflowVersion | null>(null);
|
||||||
const saveCurWorkflow = useCallback(async (force = false) => {
|
const saveCurWorkflow = useCallback(async (newGraphJson?: string) => {
|
||||||
if (curFlowID.current) {
|
const flowId = workflowsTable?.curWorkflow?.id;
|
||||||
if (!force && workflowsTable?.curWorkflow?.saveLock) {
|
if (flowId) {
|
||||||
|
if (workflowsTable?.curWorkflow?.saveLock) {
|
||||||
toast({
|
toast({
|
||||||
title: "The workflow is locked and cannot be saved",
|
title: "The workflow is locked and cannot be saved",
|
||||||
status: "warning",
|
status: "warning",
|
||||||
@@ -69,13 +72,14 @@ export default function App() {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const graphJson = JSON.stringify(app.graph.serialize());
|
!newGraphJson && setIsDirty(false);
|
||||||
|
const graphJson = newGraphJson || JSON.stringify(app.graph.serialize());
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
workflowsTable?.updateFlow(curFlowID.current, {
|
workflowsTable?.updateFlow(flowId, {
|
||||||
json: graphJson,
|
json: graphJson,
|
||||||
}),
|
}),
|
||||||
changelogsTable?.create({
|
changelogsTable?.create({
|
||||||
workflowID: curFlowID.current,
|
workflowID: flowId,
|
||||||
json: graphJson,
|
json: graphJson,
|
||||||
isAutoSave: false,
|
isAutoSave: false,
|
||||||
}),
|
}),
|
||||||
@@ -87,7 +91,6 @@ export default function App() {
|
|||||||
duration: 1000,
|
duration: 1000,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
setIsDirty(false);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -122,9 +125,7 @@ export default function App() {
|
|||||||
// curID null is when you deleted current workflow
|
// curID null is when you deleted current workflow
|
||||||
const id = workflow?.id ?? null;
|
const id = workflow?.id ?? null;
|
||||||
workflowsTable?.updateCurWorkflow(workflow);
|
workflowsTable?.updateCurWorkflow(workflow);
|
||||||
curFlowID.current = id;
|
|
||||||
setFlowID(id);
|
setFlowID(id);
|
||||||
setCurFlowName(workflow?.name ?? "");
|
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
document.title = "ComfyUI";
|
document.title = "ComfyUI";
|
||||||
window.location.hash = "";
|
window.location.hash = "";
|
||||||
@@ -160,7 +161,7 @@ export default function App() {
|
|||||||
latestWfID = localStorage.getItem("curFlowID");
|
latestWfID = localStorage.getItem("curFlowID");
|
||||||
}
|
}
|
||||||
if (latestWfID) {
|
if (latestWfID) {
|
||||||
loadWorkflowIDImpl(latestWfID);
|
loadWorkflowIDImpl(latestWfID!, null, true);
|
||||||
}
|
}
|
||||||
fetch("/workspace/deduplicate_workflow_ids");
|
fetch("/workspace/deduplicate_workflow_ids");
|
||||||
const twoway = await userSettingsTable?.getSetting("twoWaySync");
|
const twoway = await userSettingsTable?.getSetting("twoWaySync");
|
||||||
@@ -208,7 +209,7 @@ export default function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkIsDirty = () => {
|
const checkIsDirty = () => {
|
||||||
if (curFlowID.current == null) return false;
|
if (workflowsTable?.curWorkflow?.id == null) return false;
|
||||||
const curflow = workflowsTable?.curWorkflow;
|
const curflow = workflowsTable?.curWorkflow;
|
||||||
if (curflow == null) return false;
|
if (curflow == null) return false;
|
||||||
if (curflow.json == null) return true;
|
if (curflow.json == null) return true;
|
||||||
@@ -223,7 +224,11 @@ export default function App() {
|
|||||||
return !equal;
|
return !equal;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadWorkflowIDImpl = async (id: string, versionID?: string | null) => {
|
const loadWorkflowIDImpl = async (
|
||||||
|
id: string,
|
||||||
|
versionID?: string | null,
|
||||||
|
isInit?: boolean,
|
||||||
|
) => {
|
||||||
if (app.graph == null) {
|
if (app.graph == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -238,27 +243,55 @@ export default function App() {
|
|||||||
? await workflowVersionsTable?.get(versionID)
|
? await workflowVersionsTable?.get(versionID)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
app.ui.dialog.close();
|
setCurVersion(version ?? null);
|
||||||
if (version) {
|
|
||||||
setCurVersion(version);
|
const tabIndex = tabDataManager.tabs.findIndex((tab) => tab.id === id);
|
||||||
setCurFlowIDAndName({
|
if (tabIndex !== -1) {
|
||||||
...flow,
|
tabDataManager.changeActiveTab(tabIndex);
|
||||||
json: version.json,
|
if (version) {
|
||||||
});
|
tabDataManager.updateTabData(tabIndex, { json: version.json });
|
||||||
app.loadGraphData(JSON.parse(version.json));
|
}
|
||||||
} else {
|
return;
|
||||||
setCurFlowIDAndName(flow);
|
|
||||||
setCurVersion(null);
|
|
||||||
app.loadGraphData(JSON.parse(flow.json));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.ui.dialog.close();
|
||||||
setRoute("root");
|
setRoute("root");
|
||||||
isDirty && setIsDirty(false);
|
isDirty && setIsDirty(false);
|
||||||
|
|
||||||
|
const newTabInfo = {
|
||||||
|
id: flow.id,
|
||||||
|
name: flow.name,
|
||||||
|
json: version ? version.json : flow.json,
|
||||||
|
isDirty: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During initialization,
|
||||||
|
* because the event listener of OPEN_TAB_EVENT has not been registered yet,
|
||||||
|
* the newly added tab will be invalid,
|
||||||
|
* so the tab is added manually during initialization.
|
||||||
|
*/
|
||||||
|
if (isInit) {
|
||||||
|
tabDataManager.addTabData(newTabInfo);
|
||||||
|
app.loadGraphData(JSON.parse(newTabInfo.json));
|
||||||
|
setCurFlowIDAndName({
|
||||||
|
...flow,
|
||||||
|
json: newTabInfo.json,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(OPEN_TAB_EVENT, {
|
||||||
|
detail: {
|
||||||
|
tabInfo: newTabInfo,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadWorkflowID = async (
|
const loadWorkflowID = async (
|
||||||
id: string | null,
|
id: string | null,
|
||||||
versionID?: string | null,
|
versionID?: string | null,
|
||||||
forceLoad: boolean = false,
|
|
||||||
) => {
|
) => {
|
||||||
// No current workflow, id is null when you deleted current workflow
|
// No current workflow, id is null when you deleted current workflow
|
||||||
if (id === null) {
|
if (id === null) {
|
||||||
@@ -266,18 +299,10 @@ export default function App() {
|
|||||||
app.graph.clear();
|
app.graph.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
!isDirty ||
|
loadWorkflowIDImpl(id, versionID);
|
||||||
forceLoad ||
|
|
||||||
userSettingsTable?.settings?.autoSave ||
|
|
||||||
workflowsTable?.curWorkflow == null
|
|
||||||
) {
|
|
||||||
loadWorkflowIDImpl(id, versionID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// prompt when auto save disabled and dirty
|
|
||||||
showSaveOrDiscardCurWorkflowDialog(id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showSaveOrDiscardCurWorkflowDialog = async (newIDToLoad?: string) => {
|
const showSaveOrDiscardCurWorkflowDialog = async (newIDToLoad?: string) => {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
newIDToLoad
|
newIDToLoad
|
||||||
@@ -358,13 +383,13 @@ export default function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onExecutedCreateMedia = useCallback((image: any) => {
|
const onExecutedCreateMedia = useCallback((image: any) => {
|
||||||
if (curFlowID.current == null) return;
|
if (workflowsTable?.curWorkflow?.id == null) return;
|
||||||
let path = image.filename;
|
let path = image.filename;
|
||||||
if (image.subfolder != null && image.subfolder !== "") {
|
if (image.subfolder != null && image.subfolder !== "") {
|
||||||
path = image.subfolder + "/" + path;
|
path = image.subfolder + "/" + path;
|
||||||
}
|
}
|
||||||
mediaTable?.create({
|
mediaTable?.create({
|
||||||
workflowID: curFlowID.current,
|
workflowID: workflowsTable?.curWorkflow?.id,
|
||||||
localPath: path,
|
localPath: path,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -403,7 +428,12 @@ export default function App() {
|
|||||||
createTime: Date.now(),
|
createTime: Date.now(),
|
||||||
};
|
};
|
||||||
setCurFlowIDAndName(newFlow);
|
setCurFlowIDAndName(newFlow);
|
||||||
await workflowsTable?.createFlow(newFlow);
|
const res = await workflowsTable?.createFlow(newFlow);
|
||||||
|
res &&
|
||||||
|
tabDataManager.updateTabData(tabDataManager.activeIndex, {
|
||||||
|
id: res.id,
|
||||||
|
name: res.name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileInput?.addEventListener("change", fileInputListener);
|
fileInput?.addEventListener("change", fileInputListener);
|
||||||
@@ -458,7 +488,12 @@ export default function App() {
|
|||||||
createTime: Date.now(),
|
createTime: Date.now(),
|
||||||
};
|
};
|
||||||
setCurFlowIDAndName(newFlow);
|
setCurFlowIDAndName(newFlow);
|
||||||
await workflowsTable?.createFlow(newFlow);
|
const res = await workflowsTable?.createFlow(newFlow);
|
||||||
|
res &&
|
||||||
|
tabDataManager.updateTabData(tabDataManager.activeIndex, {
|
||||||
|
id: res.id,
|
||||||
|
name: res.name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
app.canvasEl.addEventListener("drop", handleDrop);
|
app.canvasEl.addEventListener("drop", handleDrop);
|
||||||
@@ -491,6 +526,7 @@ export default function App() {
|
|||||||
route: route,
|
route: route,
|
||||||
curVersion: curVersion,
|
curVersion: curVersion,
|
||||||
setCurVersion: setCurVersion,
|
setCurVersion: setCurVersion,
|
||||||
|
setCurFlowIDAndName: setCurFlowIDAndName,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div ref={workspaceContainerRef} className="workspace_manager">
|
<div ref={workspaceContainerRef} className="workspace_manager">
|
||||||
@@ -505,7 +541,7 @@ export default function App() {
|
|||||||
zIndex={DRAWER_Z_INDEX}
|
zIndex={DRAWER_Z_INDEX}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
>
|
>
|
||||||
<Topbar curFlowName={curFlowName} setCurFlowName={setCurFlowName} />
|
<Topbar />
|
||||||
{loadChild && route === "recentFlows" && (
|
{loadChild && route === "recentFlows" && (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RecentFilesDrawer
|
<RecentFilesDrawer
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
|
|||||||
Array<Folder | Workflow>
|
Array<Folder | Workflow>
|
||||||
>([]);
|
>([]);
|
||||||
const allFlowsRef = useRef<Array<Workflow>>([]);
|
const allFlowsRef = useRef<Array<Workflow>>([]);
|
||||||
const { loadWorkflowID, curFlowID } = useContext(WorkspaceContext);
|
|
||||||
const [selectedTag, setSelectedTag] = useState<string>();
|
const [selectedTag, setSelectedTag] = useState<string>();
|
||||||
const [multipleState, setMultipleState] = useState(false);
|
const [multipleState, setMultipleState] = useState(false);
|
||||||
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
|
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
|
||||||
@@ -113,9 +112,6 @@ export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
|
|||||||
const onDelete = useCallback(
|
const onDelete = useCallback(
|
||||||
async (id: string) => {
|
async (id: string) => {
|
||||||
await workflowsTable?.deleteFlow(id);
|
await workflowsTable?.deleteFlow(id);
|
||||||
if (workflowsTable?.curWorkflow?.id === id) {
|
|
||||||
await loadWorkflowID?.(null);
|
|
||||||
}
|
|
||||||
await loadLatestWorkflows();
|
await loadLatestWorkflows();
|
||||||
},
|
},
|
||||||
[selectedTag, debounceSearchValue],
|
[selectedTag, debounceSearchValue],
|
||||||
@@ -155,12 +151,6 @@ export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
|
|||||||
const batchOperationCallback = (type: string, value: unknown) => {
|
const batchOperationCallback = (type: string, value: unknown) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "batchDelete":
|
case "batchDelete":
|
||||||
curFlowID &&
|
|
||||||
workflowsTable?.get(curFlowID).then((res) => {
|
|
||||||
if (!res) {
|
|
||||||
loadWorkflowID?.(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
loadLatestWorkflows();
|
loadLatestWorkflows();
|
||||||
setMultipleState(false);
|
setMultipleState(false);
|
||||||
setSelectedKeys([]);
|
setSelectedKeys([]);
|
||||||
|
|||||||
@@ -9,13 +9,9 @@ export type JsonDiff = {
|
|||||||
export const WorkspaceContext = createContext<{
|
export const WorkspaceContext = createContext<{
|
||||||
curFlowID: string | null;
|
curFlowID: string | null;
|
||||||
onDuplicateWorkflow?: (flowID: string, newFlowName?: string) => void;
|
onDuplicateWorkflow?: (flowID: string, newFlowName?: string) => void;
|
||||||
loadWorkflowID: (
|
loadWorkflowID: (id: string | null, versionID?: string | null) => void;
|
||||||
id: string | null,
|
|
||||||
versionID?: string | null,
|
|
||||||
forceLoad?: boolean,
|
|
||||||
) => void;
|
|
||||||
setIsDirty: (dirty: boolean) => void;
|
setIsDirty: (dirty: boolean) => void;
|
||||||
saveCurWorkflow: (force?: boolean) => Promise<void>;
|
saveCurWorkflow: (saveCurWorkflow?: string) => Promise<void>;
|
||||||
discardUnsavedChanges: () => Promise<void>;
|
discardUnsavedChanges: () => Promise<void>;
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
loadNewWorkflow: (input?: { json: string; name?: string }) => void;
|
loadNewWorkflow: (input?: { json: string; name?: string }) => void;
|
||||||
@@ -24,6 +20,7 @@ export const WorkspaceContext = createContext<{
|
|||||||
route: WorkspaceRoute;
|
route: WorkspaceRoute;
|
||||||
curVersion: WorkflowVersion | null;
|
curVersion: WorkflowVersion | null;
|
||||||
setCurVersion: (version: WorkflowVersion | null) => void;
|
setCurVersion: (version: WorkflowVersion | null) => void;
|
||||||
|
setCurFlowIDAndName: (workflow: Workflow) => void;
|
||||||
}>({
|
}>({
|
||||||
curFlowID: null,
|
curFlowID: null,
|
||||||
loadWorkflowID: () => {},
|
loadWorkflowID: () => {},
|
||||||
@@ -37,6 +34,7 @@ export const WorkspaceContext = createContext<{
|
|||||||
curVersion: null,
|
curVersion: null,
|
||||||
setIsDirty: () => {},
|
setIsDirty: () => {},
|
||||||
setCurVersion: () => {},
|
setCurVersion: () => {},
|
||||||
|
setCurFlowIDAndName: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RecentFilesContext = createContext<{
|
export const RecentFilesContext = createContext<{
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default function DropdownTitle() {
|
|||||||
parentFolderID: workflow?.parentFolderID,
|
parentFolderID: workflow?.parentFolderID,
|
||||||
});
|
});
|
||||||
|
|
||||||
flow && (await loadWorkflowID(flow.id, null, true));
|
flow && (await loadWorkflowID(flow.id, null));
|
||||||
handleOnCloseModal();
|
handleOnCloseModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,10 @@ import { WorkspaceContext } from "../WorkspaceContext";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
updateFlowName: (newName: string) => void;
|
isActive: boolean;
|
||||||
isDirty: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EditFlowName({
|
export default function EditFlowName({ displayName, isActive }: Props) {
|
||||||
displayName,
|
|
||||||
updateFlowName,
|
|
||||||
isDirty,
|
|
||||||
}: Props) {
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { curFlowID } = useContext(WorkspaceContext);
|
const { curFlowID } = useContext(WorkspaceContext);
|
||||||
const [editName, setEditName] = useState(displayName);
|
const [editName, setEditName] = useState(displayName);
|
||||||
@@ -63,7 +58,6 @@ export default function EditFlowName({
|
|||||||
await workflowsTable?.updateName(curFlowID, {
|
await workflowsTable?.updateName(curFlowID, {
|
||||||
name: trimEditName,
|
name: trimEditName,
|
||||||
});
|
});
|
||||||
updateFlowName(trimEditName);
|
|
||||||
onCloseModal();
|
onCloseModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,17 +73,15 @@ export default function EditFlowName({
|
|||||||
<Tooltip label={displayName} placement="bottom">
|
<Tooltip label={displayName} placement="bottom">
|
||||||
<Text
|
<Text
|
||||||
as="div"
|
as="div"
|
||||||
color="white"
|
color={isActive ? "white" : "#9d9d9d"}
|
||||||
onClick={startEdit}
|
onDoubleClick={startEdit}
|
||||||
maxW={200}
|
maxW="114px"
|
||||||
fontWeight={500}
|
marginRight="4px"
|
||||||
whiteSpace="nowrap"
|
whiteSpace="nowrap"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
textOverflow="ellipsis"
|
textOverflow="ellipsis"
|
||||||
|
userSelect="none"
|
||||||
>
|
>
|
||||||
<div style={{ width: 8, display: "inline-block" }}>
|
|
||||||
<Text as="span">{isDirty && "* "}</Text>
|
|
||||||
</div>
|
|
||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -219,12 +219,12 @@ export default function SpotlightSearch() {
|
|||||||
|
|
||||||
document.addEventListener("keyup", handleKeyUp);
|
document.addEventListener("keyup", handleKeyUp);
|
||||||
document.addEventListener("keydown", keydownListener);
|
document.addEventListener("keydown", keydownListener);
|
||||||
window.addEventListener(SHORTCUT_TRIGGER_EVENT, shortcutTrigger);
|
document.addEventListener(SHORTCUT_TRIGGER_EVENT, shortcutTrigger);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keyup", handleKeyUp);
|
document.removeEventListener("keyup", handleKeyUp);
|
||||||
document.removeEventListener("keydown", keydownListener);
|
document.removeEventListener("keydown", keydownListener);
|
||||||
window.removeEventListener(SHORTCUT_TRIGGER_EVENT, shortcutTrigger);
|
document.removeEventListener(SHORTCUT_TRIGGER_EVENT, shortcutTrigger);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
scanLocalFiles,
|
scanLocalFiles,
|
||||||
} from "../apis/TwowaySyncApi";
|
} from "../apis/TwowaySyncApi";
|
||||||
import { COMFYSPACE_TRACKING_FIELD_NAME } from "../const";
|
import { COMFYSPACE_TRACKING_FIELD_NAME } from "../const";
|
||||||
|
import { tabDataManager } from "../topbar/multipleTabs/TabDataManager";
|
||||||
|
|
||||||
export class WorkflowsTable extends TableBase<Workflow> {
|
export class WorkflowsTable extends TableBase<Workflow> {
|
||||||
static readonly TABLE_NAME = "workflows";
|
static readonly TABLE_NAME = "workflows";
|
||||||
@@ -219,6 +220,7 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
|||||||
before &&
|
before &&
|
||||||
(await TwowaySyncAPI.renameWorkflow(before, change.name + ".json"));
|
(await TwowaySyncAPI.renameWorkflow(before, change.name + ".json"));
|
||||||
}
|
}
|
||||||
|
tabDataManager.updateTabData(tabDataManager.activeIndex, change);
|
||||||
return await this.updateMetaInfo(id, change as any);
|
return await this.updateMetaInfo(id, change as any);
|
||||||
}
|
}
|
||||||
public async updateFlow(id: string, input: Pick<Workflow, "json">) {
|
public async updateFlow(id: string, input: Pick<Workflow, "json">) {
|
||||||
@@ -318,6 +320,12 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
|||||||
deleteJsonFileMyWorkflows({ ...workflow });
|
deleteJsonFileMyWorkflows({ ...workflow });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabIndex = tabDataManager.tabs.findIndex((tab) => tab.id === id);
|
||||||
|
if (tabIndex !== -1) {
|
||||||
|
tabDataManager?.deleteTabData(tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
//add to IndexDB
|
//add to IndexDB
|
||||||
await indexdb.workflows.delete(id);
|
await indexdb.workflows.delete(id);
|
||||||
this.saveDiskDB();
|
this.saveDiskDB();
|
||||||
@@ -336,6 +344,7 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await indexdb.workflows.bulkDelete(ids);
|
await indexdb.workflows.bulkDelete(ids);
|
||||||
|
tabDataManager.batchDeleteTabData(ids);
|
||||||
this.saveDiskDB();
|
this.saveDiskDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
IconPhoto,
|
IconPhoto,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconTriangleInvertedFilled,
|
IconTriangleInvertedFilled,
|
||||||
IconLock,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import DropdownTitle from "../components/DropdownTitle";
|
import DropdownTitle from "../components/DropdownTitle";
|
||||||
import {
|
import {
|
||||||
@@ -18,25 +17,21 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import EditFlowName from "../components/EditFlowName";
|
|
||||||
import { WorkspaceContext } from "../WorkspaceContext";
|
import { WorkspaceContext } from "../WorkspaceContext";
|
||||||
import { PanelPosition } from "../types/dbTypes";
|
import { PanelPosition } from "../types/dbTypes";
|
||||||
import "./Topbar.css";
|
import "./Topbar.css";
|
||||||
import { SharedTopbarButton } from "../share/SharedTopbarButton";
|
import { SharedTopbarButton } from "../share/SharedTopbarButton";
|
||||||
import VersionNameTopbar from "./VersionNameTopbar";
|
import VersionNameTopbar from "./VersionNameTopbar";
|
||||||
import { userSettingsTable, workflowsTable } from "../db-tables/WorkspaceDB";
|
import { userSettingsTable } from "../db-tables/WorkspaceDB";
|
||||||
import { TOPBAR_BUTTON_HEIGHT } from "../const";
|
import { TOPBAR_BUTTON_HEIGHT } from "../const";
|
||||||
const AppIsDirtyEventListener = lazy(() => import("./AppIsDirtyEventListener"));
|
const AppIsDirtyEventListener = lazy(() => import("./AppIsDirtyEventListener"));
|
||||||
|
import MultipleTabs from "./multipleTabs";
|
||||||
const ModelManagerTopbar = lazy(
|
const ModelManagerTopbar = lazy(
|
||||||
() => import("../model-manager/topbar/ModelManagerTopbar"),
|
() => import("../model-manager/topbar/ModelManagerTopbar"),
|
||||||
);
|
);
|
||||||
const SpotlightSearch = lazy(() => import("../components/SpotlightSearch"));
|
const SpotlightSearch = lazy(() => import("../components/SpotlightSearch"));
|
||||||
|
|
||||||
interface Props {
|
export function Topbar() {
|
||||||
curFlowName: string | null;
|
|
||||||
setCurFlowName: (newName: string) => void;
|
|
||||||
}
|
|
||||||
export function Topbar({ curFlowName, setCurFlowName }: Props) {
|
|
||||||
const {
|
const {
|
||||||
isDirty,
|
isDirty,
|
||||||
loadNewWorkflow,
|
loadNewWorkflow,
|
||||||
@@ -131,19 +126,6 @@ export function Topbar({ curFlowName, setCurFlowName }: Props) {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<EditFlowName
|
|
||||||
isDirty={isDirty}
|
|
||||||
displayName={curFlowName ?? ""}
|
|
||||||
updateFlowName={(newName) => {
|
|
||||||
setCurFlowName(newName);
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
updatePanelPosition();
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{workflowsTable?.curWorkflow?.saveLock && (
|
|
||||||
<IconLock color="#FFF" size={20} />
|
|
||||||
)}
|
|
||||||
{curFlowID && (
|
{curFlowID && (
|
||||||
<HStack gap={"4px"}>
|
<HStack gap={"4px"}>
|
||||||
<Tooltip label="Open gallery">
|
<Tooltip label="Open gallery">
|
||||||
@@ -172,6 +154,7 @@ export function Topbar({ curFlowName, setCurFlowName }: Props) {
|
|||||||
) : (
|
) : (
|
||||||
<div style={{ width: 1 }} />
|
<div style={{ width: 1 }} />
|
||||||
)}
|
)}
|
||||||
|
<MultipleTabs />
|
||||||
<SharedTopbarButton />
|
<SharedTopbarButton />
|
||||||
<VersionNameTopbar />
|
<VersionNameTopbar />
|
||||||
<AppIsDirtyEventListener />
|
<AppIsDirtyEventListener />
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
export const RE_RENDER_MULTIPLE_TABS_EVENT = "RE_RENDER_MULTIPLE_TABS_EVENT";
|
||||||
|
export const OPEN_TAB_EVENT = "OPEN_TAB_EVENT";
|
||||||
|
// @ts-ignore
|
||||||
|
import { app } from "/scripts/app.js";
|
||||||
|
export type TabData = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
json: string;
|
||||||
|
isDirty: boolean;
|
||||||
|
};
|
||||||
|
type TabUpdateInput = Partial<TabData>;
|
||||||
|
|
||||||
|
class TabDataManager {
|
||||||
|
tabs: TabData[] = [];
|
||||||
|
activeIndex: number = 0;
|
||||||
|
activeTab: TabData | null = null;
|
||||||
|
|
||||||
|
changeActiveTab(index: number, needLoadNewFlow: boolean = true) {
|
||||||
|
this.saveCurTabJson();
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeTab = this.tabs[index];
|
||||||
|
this.notifyChanges(needLoadNewFlow ? "loadNewFlow" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTabData(index: number, updateInput: TabUpdateInput) {
|
||||||
|
if (this.tabs[index]) {
|
||||||
|
this.tabs[index] = { ...this.tabs[index], ...updateInput };
|
||||||
|
this.activeTab = this.tabs[index];
|
||||||
|
this.notifyChanges(updateInput.json ? "loadNewFlow" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveCurTabJson() {
|
||||||
|
const graphJson = JSON.stringify(app.graph.serialize());
|
||||||
|
this.tabs[this.activeIndex].json = graphJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTabData(newTab: TabData) {
|
||||||
|
const currentFlowIsDirty = this.activeTab?.isDirty;
|
||||||
|
const existingIndex = this.tabs.findIndex((tab) => tab.id === newTab.id);
|
||||||
|
|
||||||
|
if (this.tabs.length === 0) {
|
||||||
|
this.tabs.push(newTab);
|
||||||
|
this.activeTab = newTab;
|
||||||
|
} else {
|
||||||
|
let nextActiveIndex = -1;
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
nextActiveIndex = existingIndex;
|
||||||
|
} else if (currentFlowIsDirty) {
|
||||||
|
nextActiveIndex = this.activeIndex + 1;
|
||||||
|
this.tabs.splice(nextActiveIndex, 0, newTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextActiveIndex >= 0) {
|
||||||
|
this.saveCurTabJson();
|
||||||
|
this.activeIndex = nextActiveIndex;
|
||||||
|
this.activeTab = this.tabs[nextActiveIndex];
|
||||||
|
} else {
|
||||||
|
this.updateTabData(this.activeIndex, newTab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifyChanges("loadNewFlow");
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTabData(index: number) {
|
||||||
|
if (index < 0 || index >= this.tabs.length) return;
|
||||||
|
|
||||||
|
if (this.tabs.length === 1) {
|
||||||
|
this.tabs = [];
|
||||||
|
this.activeIndex = 0;
|
||||||
|
this.activeTab = null;
|
||||||
|
this.notifyChanges("clearCanvas");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabs.splice(index, 1);
|
||||||
|
|
||||||
|
if (index <= this.activeIndex) {
|
||||||
|
const isDeleteActiveTab = index === this.activeIndex;
|
||||||
|
this.activeIndex = isDeleteActiveTab ? Math.min(this.tabs.length - 1, this.activeIndex) : this.activeIndex - 1;
|
||||||
|
this.activeTab = this.tabs[this.activeIndex];
|
||||||
|
this.notifyChanges(isDeleteActiveTab ? "loadNewFlow" : '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.notifyChanges('');
|
||||||
|
}
|
||||||
|
|
||||||
|
batchDeleteTabData(ids: string[]) {
|
||||||
|
if (this.tabs.length > 0) {
|
||||||
|
this.tabs = this.tabs.filter(tab => !ids.includes(tab.id));
|
||||||
|
if (this.tabs.length === 0) {
|
||||||
|
this.activeIndex = 0;
|
||||||
|
this.activeTab = null;
|
||||||
|
this.notifyChanges("clearCanvas");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const activeIndex = this.tabs.findIndex(tab => tab.id === this.activeTab?.id);
|
||||||
|
if (activeIndex === -1) {
|
||||||
|
this.activeIndex = this.tabs.length - 1;
|
||||||
|
this.activeTab = this.tabs[this.activeIndex];
|
||||||
|
this.notifyChanges("loadNewFlow");
|
||||||
|
return;
|
||||||
|
} else if (activeIndex !== this.activeIndex) {
|
||||||
|
this.activeIndex = activeIndex;
|
||||||
|
this.activeTab = this.tabs[activeIndex];
|
||||||
|
this.notifyChanges();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifyChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyChanges(otherAction?: string) {
|
||||||
|
const event = new CustomEvent(RE_RENDER_MULTIPLE_TABS_EVENT, {
|
||||||
|
detail: {
|
||||||
|
tabs: this.tabs,
|
||||||
|
activeIndex: this.activeIndex,
|
||||||
|
activeTab: this.activeTab,
|
||||||
|
otherAction,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tabDataManager = new TabDataManager();
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
.workspace-multiple-tabs::-webkit-scrollbar {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-multiple-tabs::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 1em;
|
||||||
|
background-color: rgba(130, 129, 129, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-multiple-tabs
|
||||||
|
.workspace-multiple-tabs-item-action:hover
|
||||||
|
.workspace-multiple-tabs-item-dirty {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-multiple-tabs-item-action:hover
|
||||||
|
.workspace-multiple-tabs-item-dirty-close {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-multiple-tabs-item:hover .workspace-multiple-tabs-item-close {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { IconButton, HStack, Box } from "@chakra-ui/react";
|
||||||
|
import { IconX, IconAsterisk, IconLock } from "@tabler/icons-react";
|
||||||
|
import { useEffect, useContext, useReducer } from "react";
|
||||||
|
// @ts-ignore
|
||||||
|
import { app } from "/scripts/app.js";
|
||||||
|
import { WorkspaceContext } from "../../WorkspaceContext";
|
||||||
|
import {
|
||||||
|
OPEN_TAB_EVENT,
|
||||||
|
RE_RENDER_MULTIPLE_TABS_EVENT,
|
||||||
|
} from "./TabDataManager";
|
||||||
|
import { tabDataManager } from "./TabDataManager";
|
||||||
|
import "./index.css";
|
||||||
|
import { workflowsTable } from "../../db-tables/WorkspaceDB";
|
||||||
|
import EditFlowName from "../../components/EditFlowName";
|
||||||
|
import { useDialog } from "../../components/AlertDialogProvider";
|
||||||
|
|
||||||
|
export default function MultipleTabs() {
|
||||||
|
const { showDialog } = useDialog();
|
||||||
|
const {
|
||||||
|
setCurFlowIDAndName,
|
||||||
|
isDirty,
|
||||||
|
setIsDirty,
|
||||||
|
setCurVersion,
|
||||||
|
curVersion,
|
||||||
|
loadWorkflowID,
|
||||||
|
saveCurWorkflow,
|
||||||
|
} = useContext(WorkspaceContext);
|
||||||
|
const [, reRenderDispatch] = useReducer((state) => state + 1, 0);
|
||||||
|
|
||||||
|
const onClose = (index: number) => {
|
||||||
|
const deleteTabInfo = tabDataManager.tabs[index];
|
||||||
|
if (deleteTabInfo.isDirty) {
|
||||||
|
showDialog(
|
||||||
|
`Please save or discard your changes to "${deleteTabInfo.name}" before leaving, or your changes will be lost.`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "Save",
|
||||||
|
colorScheme: "teal",
|
||||||
|
onClick: async () => {
|
||||||
|
const deleteFlow = await workflowsTable?.get(deleteTabInfo.id);
|
||||||
|
if (deleteFlow) {
|
||||||
|
/**
|
||||||
|
* Get the deleted flow information and update updateCurWorkflow,
|
||||||
|
* Because workflowsTable.curWorkflow.id needs to be used when saving
|
||||||
|
*/
|
||||||
|
workflowsTable?.updateCurWorkflow({
|
||||||
|
...deleteFlow,
|
||||||
|
json: deleteTabInfo.json,
|
||||||
|
});
|
||||||
|
await saveCurWorkflow(deleteTabInfo.json);
|
||||||
|
tabDataManager.deleteTabData(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Close",
|
||||||
|
colorScheme: "red",
|
||||||
|
onClick: async () => {
|
||||||
|
tabDataManager.deleteTabData(index);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tabDataManager.deleteTabData(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
tabDataManager.updateTabData(tabDataManager.activeIndex, { isDirty });
|
||||||
|
}, [isDirty]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const reRender = (e: CustomEvent) => {
|
||||||
|
const detail = e.detail;
|
||||||
|
reRenderDispatch();
|
||||||
|
switch (detail.otherAction) {
|
||||||
|
case "clearCanvas":
|
||||||
|
loadWorkflowID(null);
|
||||||
|
break;
|
||||||
|
case "loadNewFlow":
|
||||||
|
app.loadGraphData(JSON.parse(detail.activeTab.json));
|
||||||
|
workflowsTable?.get(detail.activeTab.id).then((flow) => {
|
||||||
|
flow &&
|
||||||
|
setCurFlowIDAndName({
|
||||||
|
...flow,
|
||||||
|
json: detail.activeTab.json,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setIsDirty(detail.activeTab.isDirty);
|
||||||
|
curVersion && setCurVersion(null);
|
||||||
|
// Avoid scrolling when the corresponding element has not yet been rendered.
|
||||||
|
setTimeout(() => {
|
||||||
|
document
|
||||||
|
.getElementById(
|
||||||
|
`workspace-multiple-tabs-item-${detail.activeTab.id}`,
|
||||||
|
)
|
||||||
|
?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTab = (e: CustomEvent) => {
|
||||||
|
const detail = e.detail;
|
||||||
|
if (detail.tabInfo.id !== tabDataManager.activeTab?.id) {
|
||||||
|
tabDataManager.addTabData(detail.tabInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener(RE_RENDER_MULTIPLE_TABS_EVENT, reRender);
|
||||||
|
document.addEventListener(OPEN_TAB_EVENT, openTab);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener(RE_RENDER_MULTIPLE_TABS_EVENT, reRender);
|
||||||
|
document.removeEventListener(OPEN_TAB_EVENT, reRender);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
spacing={0}
|
||||||
|
className="workspace-multiple-tabs"
|
||||||
|
overflowX="auto"
|
||||||
|
maxW="calc(100vw - 380px)"
|
||||||
|
>
|
||||||
|
{tabDataManager.tabs.map((tab, index) => {
|
||||||
|
const isActive = tabDataManager.activeIndex === index;
|
||||||
|
const { id, name, isDirty } = tab;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={`${id}-${name}`}
|
||||||
|
className="workspace-multiple-tabs-item"
|
||||||
|
id={`workspace-multiple-tabs-item-${id}`}
|
||||||
|
cursor="pointer"
|
||||||
|
border="1px"
|
||||||
|
borderColor="#64646459"
|
||||||
|
borderTopColor={isActive ? "#0078d4" : "#64646459"}
|
||||||
|
borderRightWidth={0}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
h="32px"
|
||||||
|
lineHeight="32px"
|
||||||
|
padding="0 30px 0 10px"
|
||||||
|
position="relative"
|
||||||
|
bg={isActive ? "#1f1f1f" : "#181818"}
|
||||||
|
_hover={{ bg: "#1f1f1f" }}
|
||||||
|
_last={{ borderRightWidth: 1 }}
|
||||||
|
onClick={() => {
|
||||||
|
tabDataManager.activeIndex !== index &&
|
||||||
|
tabDataManager.changeActiveTab(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditFlowName isActive={isActive} displayName={name ?? ""} />
|
||||||
|
{isDirty ? (
|
||||||
|
<Box
|
||||||
|
className="workspace-multiple-tabs-item-action"
|
||||||
|
position="absolute"
|
||||||
|
right="4px"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
icon={<IconAsterisk color="white" width={14} height={14} />}
|
||||||
|
size={"xs"}
|
||||||
|
aria-label="un save flag"
|
||||||
|
variant={"ghost"}
|
||||||
|
display="flex"
|
||||||
|
_hover={{ bg: "whiteAlpha.200" }}
|
||||||
|
className="workspace-multiple-tabs-item-dirty"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose(index);
|
||||||
|
}}
|
||||||
|
icon={<IconX color="white" width={14} height={14} />}
|
||||||
|
size={"xs"}
|
||||||
|
aria-label="close tab"
|
||||||
|
variant={"ghost"}
|
||||||
|
_hover={{ bg: "whiteAlpha.200" }}
|
||||||
|
display="none"
|
||||||
|
className="workspace-multiple-tabs-item-dirty-close"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose(index);
|
||||||
|
}}
|
||||||
|
icon={<IconX color="white" width={14} height={14} />}
|
||||||
|
size={"xs"}
|
||||||
|
aria-label="close tab"
|
||||||
|
variant={"ghost"}
|
||||||
|
_hover={{ bg: "whiteAlpha.200" }}
|
||||||
|
display={isActive ? "flex" : "none"}
|
||||||
|
position="absolute"
|
||||||
|
right="4px"
|
||||||
|
className={isActive ? "" : "workspace-multiple-tabs-item-close"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isActive && workflowsTable?.curWorkflow?.saveLock && (
|
||||||
|
<IconLock color="#FFF" size={20} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
Vendored
+18
-1
@@ -1,12 +1,29 @@
|
|||||||
import { SHORTCUT_TRIGGER_EVENT } from "../const";
|
import { SHORTCUT_TRIGGER_EVENT } from "../const";
|
||||||
|
import {
|
||||||
|
OPEN_TAB_EVENT,
|
||||||
|
RE_RENDER_MULTIPLE_TABS_EVENT,
|
||||||
|
TabData,
|
||||||
|
} from "../topbar/multipleTabs/TabDataManager";
|
||||||
import { EOtherKeys, EShortcutKeys } from "./dbTypes";
|
import { EOtherKeys, EShortcutKeys } from "./dbTypes";
|
||||||
|
|
||||||
interface ShortcutTriggerDetail {
|
interface ShortcutTriggerDetail {
|
||||||
shortcutType: EShortcutKeys | EOtherKeys;
|
shortcutType: EShortcutKeys | EOtherKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface reRenderTabsDetail {
|
||||||
|
tabs: TabData;
|
||||||
|
activeIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface reRenderTabsDetail {
|
||||||
|
tabs: TabData;
|
||||||
|
activeIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface WindowEventMap {
|
interface DocumentEventMap {
|
||||||
[SHORTCUT_TRIGGER_EVENT]: CustomEvent<ShortcutTriggerDetail>;
|
[SHORTCUT_TRIGGER_EVENT]: CustomEvent<ShortcutTriggerDetail>;
|
||||||
|
[RE_RENDER_MULTIPLE_TABS_EVENT]: CustomEvent<reRenderTabsDetail>;
|
||||||
|
[OPEN_TAB_EVENT]: CustomEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user