diff --git a/editor/components/editor-progress-indicator/editor-progress-indicator-popover-content.tsx b/editor/components/editor-progress-indicator/editor-progress-indicator-popover-content.tsx new file mode 100644 index 00000000..47075257 --- /dev/null +++ b/editor/components/editor-progress-indicator/editor-progress-indicator-popover-content.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import styled from "@emotion/styled"; + +export function EditorProgressIndicatorPopoverContent({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} + +const Container = styled.div` + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + flex: none; + gap: 32px; + box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.25); + border: solid 1px rgb(74, 73, 77); + border-radius: 12px; + background-color: rgba(30, 30, 30, 0.8); + box-sizing: border-box; + padding: 24px; + backdrop-filter: blur(32px); +`; diff --git a/editor/components/editor-progress-indicator/editor-progress-indicator-trigger-button.tsx b/editor/components/editor-progress-indicator/editor-progress-indicator-trigger-button.tsx new file mode 100644 index 00000000..00bfca80 --- /dev/null +++ b/editor/components/editor-progress-indicator/editor-progress-indicator-trigger-button.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import styled from "@emotion/styled"; + +export function EditorProgressIndicatorButton({ + isBusy = false, +}: { + isBusy?: boolean; +}) { + return ( + + + + + {isBusy && } + + ); +} + +const Container = styled.div` + display: flex; + justify-content: flex-start; + flex-direction: column; + align-items: center; + flex: none; + gap: 2px; + box-sizing: border-box; +`; + +const IndicatorLine = styled.div` + width: 20px; + height: 4px; + background-color: rgb(37, 98, 255); + border-radius: 7px; +`; diff --git a/editor/components/editor-progress-indicator/editor-progress-indicator.tsx b/editor/components/editor-progress-indicator/editor-progress-indicator.tsx new file mode 100644 index 00000000..791f91d3 --- /dev/null +++ b/editor/components/editor-progress-indicator/editor-progress-indicator.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { EditorTaskItem } from "./editor-task-item"; +import { EditorProgressIndicatorButton } from "./editor-progress-indicator-trigger-button"; +import { EditorProgressIndicatorPopoverContent } from "./editor-progress-indicator-popover-content"; +import * as Popover from "@radix-ui/react-popover"; + +export function EditorProgressIndicator({ + isBusy, + tasks, +}: { + isBusy: boolean; + tasks: any[]; +}) { + return ( + + + + + + + {tasks.map((task, index) => ( + + ))} + + + + ); +} + +const StyledTrigger = styled(Popover.Trigger)` + outline: none; + border: none; + background: none; +`; diff --git a/editor/components/editor-progress-indicator/editor-task-item.tsx b/editor/components/editor-progress-indicator/editor-task-item.tsx new file mode 100644 index 00000000..e1702c6a --- /dev/null +++ b/editor/components/editor-progress-indicator/editor-task-item.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import styled from "@emotion/styled"; +import LinearProgress from "@mui/material/LinearProgress"; + +export function EditorTaskItem({ + label, + description, + progress, +}: { + label: string; + description?: string; + progress: number | null; +}) { + return ( + + + {label} + + + {description} + + ); +} + +const RootWrapperProgressingItemReadonly = styled.div` + display: flex; + justify-content: center; + flex-direction: column; + align-items: flex-start; + flex: none; + gap: 4px; + box-sizing: border-box; +`; + +const TitleAndValueContainer = styled.div` + display: flex; + justify-content: flex-start; + flex-direction: row; + align-items: center; + gap: 4px; + align-self: stretch; + box-sizing: border-box; + flex-shrink: 0; +`; + +const ThisLabel = styled.span` + color: white; + text-overflow: ellipsis; + font-size: 12px; + font-family: Roboto, sans-serif; + font-weight: 400; + text-align: left; + width: 80px; +`; + +const ColoredLinearProgress = styled(LinearProgress)` + height: 4px; + width: 203px; + border-radius: 7px; + background-color: rgb(37, 98, 255); +`; + +const ThisDescription = styled.span` + color: rgba(255, 255, 255, 0.5); + text-overflow: ellipsis; + font-size: 10px; + font-weight: 400; + text-align: left; + align-self: stretch; + flex-shrink: 0; +`; diff --git a/editor/components/editor-progress-indicator/index.ts b/editor/components/editor-progress-indicator/index.ts new file mode 100644 index 00000000..e814dc4b --- /dev/null +++ b/editor/components/editor-progress-indicator/index.ts @@ -0,0 +1,4 @@ +export { EditorProgressIndicator } from "./editor-progress-indicator"; +export { EditorProgressIndicatorPopoverContent } from "./editor-progress-indicator-popover-content"; +export { EditorProgressIndicatorButton } from "./editor-progress-indicator-trigger-button"; +export { EditorTaskItem } from "./editor-task-item"; diff --git a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-canvas.tsx b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-canvas.tsx index 33bc1523..aad350d8 100644 --- a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-canvas.tsx +++ b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-canvas.tsx @@ -20,7 +20,6 @@ const RootWrapperAppbarFragmentForCanvas = styled.div` align-items: center; gap: 10px; align-self: stretch; - background-color: ${colors.color_editor_bg_on_dark}; box-sizing: border-box; padding: 10px 24px; `; diff --git a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-code-editor.tsx b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-code-editor.tsx index c11f393a..78a794ea 100644 --- a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-code-editor.tsx +++ b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-code-editor.tsx @@ -1,16 +1,22 @@ import React from "react"; import styled from "@emotion/styled"; -import { useRouter } from "next/router"; import { EditorAppbarIconButton } from "./editor-appbar-icon-button"; import { GithubIcon, NotificationBellIcon } from "icons"; import { EditorFrameworkConfigOnAppbar } from "../editor-framework-config-on-appbar"; +import { EditorProgressIndicator } from "scaffolds/editor-progress-indicator"; +import { colors } from "theme"; -export function AppbarFragmentForCodeEditor() { - const router = useRouter(); +export function AppbarFragmentForCodeEditor({ + background = false, +}: { + background?: boolean; +}) { const hasNotification = false; return ( - + {/* disable temporarily */}
{/* */} @@ -20,6 +26,7 @@ export function AppbarFragmentForCodeEditor() { )} + { window.open("https://github.com/gridaco/designto-code/", "_blank"); @@ -32,7 +39,9 @@ export function AppbarFragmentForCodeEditor() { ); } -const RootWrapperAppbarFragmentForCodeEditor = styled.div` +const RootWrapperAppbarFragmentForCodeEditor = styled.div<{ + background: React.CSSProperties["background"]; +}>` z-index: 10; display: flex; justify-content: center; @@ -46,6 +55,7 @@ const RootWrapperAppbarFragmentForCodeEditor = styled.div` padding-top: 14px; padding-left: 12px; padding-right: 20px; + background: ${(props) => props.background}; `; const AppbarActions = styled.div` diff --git a/editor/components/editor/editor-appbar/editor-appbar.tsx b/editor/components/editor/editor-appbar/editor-appbar.tsx index 7a842d92..9c3331a8 100644 --- a/editor/components/editor/editor-appbar/editor-appbar.tsx +++ b/editor/components/editor/editor-appbar/editor-appbar.tsx @@ -7,9 +7,9 @@ import { AppbarFragmentForCodeEditor } from "./editor-appbar-fragment-for-code-e export function Appbar() { return ( - - - + + + ); } diff --git a/editor/core/actions/index.ts b/editor/core/actions/index.ts index e5f91893..50846655 100644 --- a/editor/core/actions/index.ts +++ b/editor/core/actions/index.ts @@ -1,5 +1,10 @@ import type { FrameworkConfig } from "@grida/builder-config"; -import type { ConsoleLog, EditorState, ScenePreviewData } from "core/states"; +import type { + ConsoleLog, + EditorState, + EditorTask, + ScenePreviewData, +} from "core/states"; export type WorkspaceAction = // @@ -21,7 +26,8 @@ export type Action = | CanvasModeAction | PreviewAction | CodeEditorAction - | DevtoolsAction; + | DevtoolsAction + | EditorTaskAction; export type ActionType = Action["type"]; @@ -85,3 +91,24 @@ export interface DevtoolsConsoleAction { export interface DevtoolsConsoleClearAction { type: "devtools-console-clear"; } + +export type EditorTaskAction = + | EditorTaskPushAction + | EditorTaskPopAction + | EditorTaskUpdateProgressAction; + +export interface EditorTaskPushAction { + type: "editor-task-push"; + task: EditorTask; +} + +export interface EditorTaskPopAction { + type: "editor-task-pop"; + task: EditorTask | { id: string }; +} + +export interface EditorTaskUpdateProgressAction { + type: "editor-task-update-progress"; + id: string; + progress: number; +} diff --git a/editor/core/reducers/editor-reducer.ts b/editor/core/reducers/editor-reducer.ts index 8c1401b1..d1304543 100644 --- a/editor/core/reducers/editor-reducer.ts +++ b/editor/core/reducers/editor-reducer.ts @@ -10,6 +10,9 @@ import type { PreviewSetAction, DevtoolsConsoleAction, DevtoolsConsoleClearAction, + EditorTaskPushAction, + EditorTaskPopAction, + EditorTaskUpdateProgressAction, } from "core/actions"; import { EditorState } from "core/states"; import { useRouter } from "next/router"; @@ -187,6 +190,36 @@ export function editorReducer(state: EditorState, action: Action): EditorState { }); break; } + case "editor-task-push": { + const { task } = action; + const { id } = task; + // TODO: check id duplication + + return produce(state, (draft) => { + draft.editorTaskQueue.tasks.push(task); + }); + break; + } + case "editor-task-pop": { + const { task } = action; + const { id } = task; + + return produce(state, (draft) => { + draft.editorTaskQueue.tasks = draft.editorTaskQueue.tasks.filter( + (i) => i.id !== id + ); + // TODO: handle isBusy property by the task + }); + break; + } + case "editor-task-update-progress": { + const { id, progress } = action; + return produce(state, (draft) => { + draft.editorTaskQueue.tasks.find((i) => i.id !== id).progress = + progress; + }); + break; + } default: throw new Error(`Unhandled action type: ${action["type"]}`); } diff --git a/editor/core/states/editor-initial-state.ts b/editor/core/states/editor-initial-state.ts index 5ad68cf0..3b43d556 100644 --- a/editor/core/states/editor-initial-state.ts +++ b/editor/core/states/editor-initial-state.ts @@ -8,6 +8,7 @@ export function createInitialEditorState(editor: EditorSnapshot): EditorState { selectedLayersOnPreview: editor.selectedLayersOnPreview, design: editor.design, canvasMode: editor.canvasMode, + editorTaskQueue: editor.editorTaskQueue, }; } @@ -19,5 +20,15 @@ export function createPendingEditorState(): EditorState { selectedLayersOnPreview: [], design: null, canvasMode: "free", + editorTaskQueue: { + isBusy: true, + tasks: [ + { + id: "pending", + name: "loading", + progress: null, + }, + ], + }, }; } diff --git a/editor/core/states/editor-state.ts b/editor/core/states/editor-state.ts index 168378e0..bb991b30 100644 --- a/editor/core/states/editor-state.ts +++ b/editor/core/states/editor-state.ts @@ -28,6 +28,7 @@ export interface EditorState { code?: CodeRepository; editingModule?: EditingModule; devtoolsConsole?: DevtoolsConsole; + editorTaskQueue: EditorTaskQueue; } export interface EditorSnapshot { @@ -37,6 +38,7 @@ export interface EditorSnapshot { selectedNodesInitial?: string[] | null; design: FigmaReflectRepository; canvasMode: TCanvasMode; + editorTaskQueue: EditorTaskQueue; } export interface FigmaReflectRepository { @@ -133,3 +135,25 @@ export interface ConsoleLog { | "count" | "assert"; } + +export interface EditorTaskQueue { + isBusy: boolean; + tasks: EditorTask[]; +} + +export interface EditorTask { + id: string; + name: string; + /** + * If the task is short-lived, wait this much ms before displaying it. + * @default 200 (0.2s) + */ + debounce?: number; + description?: string; + cancelable?: boolean; + onCancel?: () => void; + /** + * 0-1, if null, it is indeterminate + */ + progress: number | null; +} diff --git a/editor/layouts/default-editor-workspace-layout.tsx b/editor/layouts/default-editor-workspace-layout.tsx index 1c8a1bdb..0b92b385 100644 --- a/editor/layouts/default-editor-workspace-layout.tsx +++ b/editor/layouts/default-editor-workspace-layout.tsx @@ -14,7 +14,14 @@ type SidebarElementSignature = export function DefaultEditorWorkspaceLayout(props: { leftbar?: SidebarElementSignature; rightbar?: SidebarElementSignature; + /** + * global area appbar + */ appbar?: JSX.Element; + /** + * content area appbar + */ + contentAreaAppbar?: JSX.Element; children: JSX.Element | Array; display?: "none" | "initial"; // set to none when to hide. backgroundColor?: string; @@ -30,7 +37,20 @@ export function DefaultEditorWorkspaceLayout(props: { {props.leftbar && ( )} - {props.children} + {props.contentAreaAppbar ? ( +
+ {props.contentAreaAppbar} + {props.children} +
+ ) : ( + {props.children} + )} {props.rightbar && ( )} diff --git a/editor/pages/files/[key]/index.tsx b/editor/pages/files/[key]/index.tsx index 0a183a72..3aa9f4d5 100644 --- a/editor/pages/files/[key]/index.tsx +++ b/editor/pages/files/[key]/index.tsx @@ -43,6 +43,8 @@ export default function FileEntryEditor() { ); } +const action_fetchfile_id = "fetchfile"; + function SetupEditor({ filekey, nodeid, @@ -112,6 +114,17 @@ function SetupEditor({ pages: pages, }, canvasMode: initialCanvasMode, + editorTaskQueue: { + isBusy: true, + tasks: [ + { + id: action_fetchfile_id, + name: "Figma File", + description: "Refreshing remote file", + progress: null, + }, + ], + }, }; } diff --git a/editor/scaffolds/appbar/index.tsx b/editor/scaffolds/appbar/index.tsx new file mode 100644 index 00000000..ef012673 --- /dev/null +++ b/editor/scaffolds/appbar/index.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { EditorAppbarFragments } from "components/editor"; +import { useEditorState } from "core/states"; +/** + * a scaffold App bar linked with editor state + */ +export function Appbar() { + const [state] = useEditorState(); + + const isCodeEditorShown = state.selectedNodes.length > 0; + + return ( +
+ +
+ ); +} diff --git a/editor/scaffolds/editor-progress-indicator/index.tsx b/editor/scaffolds/editor-progress-indicator/index.tsx new file mode 100644 index 00000000..c16c1f11 --- /dev/null +++ b/editor/scaffolds/editor-progress-indicator/index.tsx @@ -0,0 +1,12 @@ +import { EditorProgressIndicator as Base } from "components/editor-progress-indicator"; +import { useDispatch } from "core/dispatch"; +import { useEditorState } from "core/states"; + +export function EditorProgressIndicator() { + const dispatch = useDispatch(); + const [state] = useEditorState(); + + const { editorTaskQueue } = state; + + return ; +} diff --git a/editor/scaffolds/editor/editor.tsx b/editor/scaffolds/editor/editor.tsx index 375c98b5..1282bf25 100644 --- a/editor/scaffolds/editor/editor.tsx +++ b/editor/scaffolds/editor/editor.tsx @@ -11,6 +11,7 @@ import { CodeSegment } from "scaffolds/code"; import { Inspector } from "scaffolds/inspector"; import { EditorSkeleton } from "./skeleton"; import { colors } from "theme"; +import { Appbar } from "scaffolds/appbar"; export function Editor({ loading = false, @@ -47,6 +48,8 @@ export function Editor({ maxWidth: 600, children: , }} + contentAreaAppbar={} + // appbar={} // rightbar={} >