pass privacy settings to share api
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user