pass privacy settings to share api

This commit is contained in:
Weixuan Fu
2024-02-20 00:27:37 +08:00
parent 9bcf595271
commit d73d0f0d2f
7 changed files with 202 additions and 85 deletions
+34 -10
View File
@@ -2,21 +2,25 @@ import React, { useState, useRef, useEffect } from "react";
import { Box, Button, Card } from "@chakra-ui/react";
import { IconChevronDown } from "@tabler/icons-react";
export interface CustomSelectorOption {
export interface CustomSelectorOption<T> {
label: string;
value: string;
value: T;
icon?: React.ReactElement;
}
type Props = {
options: CustomSelectorOption[];
type Props<T> = {
options: CustomSelectorOption<T>[];
value: T;
onChange: (value: T) => void;
};
export default function CustomSelector({ options }: Props) {
export default function CustomSelector<T>({
options,
value,
onChange,
}: Props<T>) {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const [selectedOption, setSelectedOption] = useState<
CustomSelectorOption | undefined
>(options[0]);
const selectedOption = options.find((option) => option.value === value);
useEffect(() => {
const handleClickOutside = (event: any) => {
@@ -41,6 +45,25 @@ export default function CustomSelector({ options }: Props) {
>
{selectedOption?.label}
</Button>
{/* {isOpen && (
<Menu isOpen={true}>
<MenuList
minWidth={150}
zIndex={1000}
onMouseEnter={onOpen}
onMouseLeave={delayedClose}
>
<MenuItem
onClick={saveCurWorkflow}
icon={<IconDeviceFloppy size={20} />}
iconSpacing={1}
command={saveShortcut}
>
Save
</MenuItem>
</MenuList>
</Menu>
)} */}
{isOpen && (
<Card
gap={4}
@@ -50,13 +73,14 @@ export default function CustomSelector({ options }: Props) {
borderWidth="1px"
p="2"
position="absolute"
zIndex={100}
>
{options.map((option) => (
<Button
key={option.value}
key={option.label}
onClick={() => {
setSelectedOption(option);
setIsOpen(false);
onChange(option.value);
}}
leftIcon={option.icon}
justifyContent="flex-start"
+2 -1
View File
@@ -1,5 +1,6 @@
import {
ChangeEvent,
lazy,
useCallback,
useContext,
useEffect,
@@ -37,9 +38,9 @@ import { workflowsTable, userSettingsTable } from "../db-tables/WorkspaceDB";
import { WorkspaceContext } from "../WorkspaceContext";
import { VersionHistoryDrawer } from "./VersionHistoryDrawer";
import { Workflow } from "../types/dbTypes";
import ShareDialog from "../share/ShareDialog";
import CustomMenu from "./CustomMenu";
import CreateVersionDialog from "./CreateVersionDialog";
const ShareDialog = lazy(() => import("../share/ShareDialog"));
export default function DropdownTitle() {
const {
+49
View File
@@ -0,0 +1,49 @@
import { useRef, useState } from "react";
import CustomMenu from "./CustomMenu";
import { Box, Menu, MenuList } from "@chakra-ui/react";
type Props = {
menuButton: React.ReactElement;
menuContent: React.ReactElement;
onClose?: () => void;
};
export default function HoverMenu({ menuButton, menuContent, onClose }: Props) {
const closeTimeoutId = useRef<number>();
const [isOpen, setIsOpen] = useState(false);
const delayedClose = () => {
closeTimeoutId.current = setTimeout(() => setIsOpen(false), 400); // delay of 300ms
};
const onOpen = () => {
setIsOpen(true);
clearTimeout(closeTimeoutId.current);
closeTimeoutId.current = undefined;
};
return (
<CustomMenu
isOpen={isOpen}
onClose={delayedClose}
menuButton={
<Box
aria-label="menu"
onClick={onOpen}
onMouseEnter={onOpen}
onMouseLeave={delayedClose}
>
{menuButton}
</Box>
}
options={
<Menu isOpen={true}>
<MenuList
minWidth={150}
zIndex={1000}
onMouseEnter={onOpen}
onMouseLeave={delayedClose}
>
{menuContent}
</MenuList>
</Menu>
}
/>
);
}
+16 -58
View File
@@ -13,19 +13,10 @@ import {
Text,
useToast,
} from "@chakra-ui/react";
import { Workflow, WorkflowVersion } from "../types/dbTypes";
import { Workflow, WorkflowPrivacy, WorkflowVersion } from "../types/dbTypes";
import { useEffect, useRef, useState } from "react";
import CustomDropdown, {
CustomSelectorOption,
} from "../components/CustomSelector";
import {
IconCloud,
IconCopy,
IconExternalLink,
IconLink,
IconLock,
IconWorld,
} from "@tabler/icons-react";
import CustomSelector from "../components/CustomSelector";
import { IconCloud, IconCopy, IconExternalLink } from "@tabler/icons-react";
import {
userSettingsTable,
workflowVersionsTable,
@@ -35,36 +26,12 @@ import {
import { api } from "/scripts/api.js";
// @ts-ignore
import { app } from "/scripts/app.js";
import { generateRandomKey, getNodeDefs, privacyOptions } from "./shareUtils";
interface Props {
onClose: () => void;
}
type CreatedCloudflowVersionEvent = {
createdVer: WorkflowVersion;
localWorkflowID: string;
localVersionID: string;
};
function generateRandomKey(length: number) {
// Generate a random array of bytes
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
// Convert the bytes to a hex string
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
"",
);
}
const privacyOptions: CustomSelectorOption[] = [
{ label: "Private", value: "PRIVATE", icon: <IconLock /> },
{
label: "Anyone with the link can access",
value: "UNLISTED",
icon: <IconLink />,
},
{ label: "Public", value: "PUBLIC", icon: <IconWorld /> },
];
export default function ShareDialog({ onClose }: Props) {
const [versionName, setVersionName] = useState(
"version " + getCurDateString(),
@@ -72,6 +39,7 @@ export default function ShareDialog({ onClose }: Props) {
const [localVersions, setLocalVersions] = useState<WorkflowVersion[]>([]);
const [loading, setLoading] = useState(false);
const [cloudHost, setCloudHost] = useState("");
const [privacy, setPrivacy] = useState<WorkflowPrivacy>("PRIVATE");
const cloudHostRef = useRef("");
const [workflow, setWorkflow] = useState<Workflow>();
const toast = useToast();
@@ -81,11 +49,9 @@ export default function ShareDialog({ onClose }: Props) {
event.origin !== cloudHostRef.current ||
detail.type !== "share_workflow_success"
) {
console.log("event.origin !== cloudHost", event.origin !== cloudHost);
return;
}
console.log("share_workflow_success", detail);
const localID = detail.localWorkflowID;
const localVerID = detail.localVersionID;
const cloudVersionID = detail.version.id;
@@ -111,18 +77,7 @@ export default function ShareDialog({ onClose }: Props) {
window.removeEventListener("message", handleShareWorkflowSuccess);
};
}, []);
const getNodeDefs = () => {
const allNodes = app.graph._nodes;
const nodeDefs = {};
for (let n of allNodes) {
// @ts-ignore
if (n.type in LiteGraph.registered_node_types) {
// @ts-ignore
nodeDefs[n.type] = LiteGraph.registered_node_types[n.type].nodeData;
}
}
return nodeDefs;
};
const loadData = async () => {
const host =
import.meta.env.MODE === "production"
@@ -165,7 +120,7 @@ export default function ShareDialog({ onClose }: Props) {
workflowID: workflow!.id,
name: versionName,
createTime: Date.now(),
json: workflow!.json,
json: JSON.stringify(app.graph.serialize()),
});
if (!newVer) {
alert("Failed to create new version, please try again.");
@@ -186,17 +141,14 @@ export default function ShareDialog({ onClose }: Props) {
const handleChildReady = (event: MessageEvent) => {
if (event.origin === host && event.data === "child_ready") {
const curWorkflow = workflowsTable?.curWorkflow;
console.log("Child window is ready.inputdata", {
workflow: curWorkflow,
version: newVer,
nodeDefs: nodeDefs,
});
// Send data to the new window after it loads
console.log("sending privacy", privacy);
sharePopup!.postMessage(
{
workflow: curWorkflow,
version: newVer,
nodeDefs: nodeDefs,
privacy: privacy,
},
host,
);
@@ -214,7 +166,13 @@ export default function ShareDialog({ onClose }: Props) {
<ModalHeader>Share "{workflow?.name}"</ModalHeader>
<ModalBody pb={10}>
<Stack gap={6}>
<CustomDropdown options={privacyOptions} />
<CustomSelector<WorkflowPrivacy>
options={privacyOptions}
value={privacy}
onChange={(val) => {
setPrivacy(val);
}}
/>
{cloudWorkflowID && (
<HStack spacing={2} color="teal.400">
+55 -14
View File
@@ -1,22 +1,63 @@
import { Button } from "@chakra-ui/react";
import { IconCloud } from "@tabler/icons-react";
import {
Button,
Text,
Stack,
Link,
HStack,
IconButton,
} from "@chakra-ui/react";
import { IconCloud, IconCopy } from "@tabler/icons-react";
import { workflowsTable } from "../db-tables/WorkspaceDB";
import { useContext, useEffect, useState } from "react";
import { WorkspaceContext } from "../WorkspaceContext";
import HoverMenu from "../components/HoverMenu";
export function SharedTopbarButton({}) {
const isCloudflow = workflowsTable?.curWorkflow?.cloudID;
if (!isCloudflow) return null;
const [cloudURL, setCloudURL] = useState<string>();
const { curFlowID } = useContext(WorkspaceContext);
useEffect(() => {
if (curFlowID) {
workflowsTable?.get(curFlowID).then((flow) => {
if (flow?.cloudID) {
setCloudURL(flow.cloudURL);
}
});
}
}, [curFlowID]);
if (!cloudURL) return null;
return (
<>
<Button
onClick={() => {
window.open(workflowsTable?.curWorkflow?.cloudURL);
}}
aria-label={"Shared"}
size={"sm"}
leftIcon={<IconCloud />}
>
Public
</Button>
<HoverMenu
menuButton={
<Button
onClick={() => {
window.open(workflowsTable?.curWorkflow?.cloudURL);
}}
aria-label={"Shared"}
size={"xs"}
iconSpacing={"2px"}
leftIcon={<IconCloud />}
height={"26px"}
px={1}
>
Public
</Button>
}
menuContent={
<HStack width={"200px"} p={1} color={"blue.400"}>
<Link href={cloudURL} noOfLines={1} isExternal>
{cloudURL}
</Link>
<IconButton
size={"xs"}
variant={"ghost"}
icon={<IconCopy size={19} />}
aria-label="copy link"
></IconButton>
</HStack>
}
/>
</>
);
}
+45
View File
@@ -0,0 +1,45 @@
import { IconLink, IconLock, IconWorld } from "@tabler/icons-react";
import { CustomSelectorOption } from "../components/CustomSelector";
import { WorkflowPrivacy } from "../types/dbTypes";
// @ts-expect-error
import { app } from "/scripts/app.js";
export function generateRandomKey(length: number) {
// Generate a random array of bytes
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
// Convert the bytes to a hex string
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
"",
);
}
export const privacyOptions: CustomSelectorOption<WorkflowPrivacy>[] = [
{ label: "Private", value: "PRIVATE", icon: <IconLock /> },
{
label: "Anyone with the link can access",
value: "UNLISTED",
icon: <IconLink />,
},
{ label: "Public", value: "PUBLIC", icon: <IconWorld /> },
];
export const getNodeDefs = () => {
const allNodes = app.graph._nodes;
// console.log("allNodes", allNodes);
// console.log(
// "LiteGraph.registered_node_types",
// LiteGraph.registered_node_types,
// );
const nodeDefs = {};
for (let n of allNodes) {
// @ts-ignore
if (n.type in LiteGraph.registered_node_types) {
// TODO: js only nodes has no nodeData field, need to handle this....
// @ts-ignore
nodeDefs[n.type] = LiteGraph.registered_node_types[n.type].nodeData;
}
}
return nodeDefs;
};
+1 -2
View File
@@ -16,7 +16,6 @@ export interface Workflow extends SortableItem {
coverMediaPath?: string;
cloudID?: string;
cloudURL?: string;
privacy?: WorkflowPrivacy;
}
export interface TableBaseModel {
@@ -121,6 +120,6 @@ export enum EFlowOperationType {
DELETE = "delete",
}
type WorkflowPrivacy = "PUBLIC" | "PRIVATE" | "UNLISTED";
export type WorkflowPrivacy = "PUBLIC" | "PRIVATE" | "UNLISTED";
export const WORKSPACE_INDEXDB_NAME = "comfyui_workspace_db";