diff --git a/ui/src/components/CustomSelector.tsx b/ui/src/components/CustomSelector.tsx index 1261c2c..38d15c1 100644 --- a/ui/src/components/CustomSelector.tsx +++ b/ui/src/components/CustomSelector.tsx @@ -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 { label: string; - value: string; + value: T; icon?: React.ReactElement; } -type Props = { - options: CustomSelectorOption[]; +type Props = { + options: CustomSelectorOption[]; + value: T; + onChange: (value: T) => void; }; -export default function CustomSelector({ options }: Props) { +export default function CustomSelector({ + options, + value, + onChange, +}: Props) { const [isOpen, setIsOpen] = useState(false); const ref = useRef(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} + {/* {isOpen && ( + + + } + iconSpacing={1} + command={saveShortcut} + > + Save + + + + )} */} {isOpen && ( {options.map((option) => ( )} - - New Version - { - setVersionName(e.target.value); - }} - /> - - {localVersions.slice(0, 4).map((ver) => { - return ( - - {ver.name} - {ver.cloudID && ( - - - - )} + Choose a version to share: + { + setSelectedVersion(val); + }} + value={selectedVersion} + > + + + + { + setSelectedVersion("new_version"); + }} + onChange={(e) => { + setVersionName(e.target.value); + }} + /> + + New version + - ); - })} + + {localVersions.slice(0, 4).map((ver) => { + return ( + + ); + })} + + + + )} + + ); + if (!version.cloudID) { + return ( + + {content} + + ); + } + + return <>{content}; +} diff --git a/ui/src/share/SharedTopbarButton.tsx b/ui/src/share/SharedTopbarButton.tsx index c915401..5bc9be9 100644 --- a/ui/src/share/SharedTopbarButton.tsx +++ b/ui/src/share/SharedTopbarButton.tsx @@ -1,22 +1,58 @@ -import { Button } from "@chakra-ui/react"; -import { IconCloud } from "@tabler/icons-react"; +import { Button, 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(); + const { curFlowID } = useContext(WorkspaceContext); + + useEffect(() => { + if (curFlowID) { + workflowsTable?.get(curFlowID).then((flow) => { + if (flow?.cloudID) { + setCloudURL(flow.cloudURL); + } else { + setCloudURL(undefined); + } + }); + } + }, [curFlowID]); + if (!cloudURL) return null; return ( <> - + { + window.open(workflowsTable?.curWorkflow?.cloudURL); + }} + aria-label={"Shared"} + size={"xs"} + iconSpacing={"2px"} + leftIcon={} + height={"26px"} + px={1} + > + Public + + } + menuContent={ + + + {cloudURL} + + } + aria-label="copy link" + > + + } + /> ); } diff --git a/ui/src/share/shareUtils.tsx b/ui/src/share/shareUtils.tsx new file mode 100644 index 0000000..4841dfc --- /dev/null +++ b/ui/src/share/shareUtils.tsx @@ -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[] = [ + { label: "Private", value: "PRIVATE", icon: }, + { + label: "Anyone with the link can access", + value: "UNLISTED", + icon: , + }, + { label: "Public", value: "PUBLIC", icon: }, +]; + +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; +}; diff --git a/ui/src/types/dbTypes.ts b/ui/src/types/dbTypes.ts index 6406f7e..968b1a9 100644 --- a/ui/src/types/dbTypes.ts +++ b/ui/src/types/dbTypes.ts @@ -16,7 +16,6 @@ export interface Workflow extends SortableItem { coverMediaPath?: string; cloudID?: string; cloudURL?: string; - privacy?: WorkflowPrivacy; } export interface TableBaseModel { @@ -60,6 +59,7 @@ export type WorkflowVersion = { json: string; createTime: number; cloudID?: string; + cloudURL?: string; nodeDefs?: string; //for cloud workflow version }; @@ -121,6 +121,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"; diff --git a/ui/src/utils.tsx b/ui/src/utils.tsx index 85a948d..ff7c8f7 100644 --- a/ui/src/utils.tsx +++ b/ui/src/utils.tsx @@ -106,6 +106,7 @@ function isValidFileName(fileName: string) { export function formatTimestamp( unixTimestamp: number, + showHourMinue: boolean = true, showSec: boolean = false, ) { // Create a new Date object from the UNIX timestamp @@ -119,7 +120,10 @@ export function formatTimestamp( const minutes = String(date.getMinutes()).padStart(2, "0"); const seconds = String(date.getSeconds()).padStart(2, "0"); // Format the date and time string - const res = `${month}-${day}-${year} ${hours}:${minutes}`; + const res = `${month}-${day}-${year}`; + if (showHourMinue) { + return res + ` ${hours}:${minutes}`; + } if (showSec) { return res + `:${seconds}`; }