diff --git a/editor/babel.config.js b/editor/babel.config.js index a14f9cb5..1bc4404b 100644 --- a/editor/babel.config.js +++ b/editor/babel.config.js @@ -13,6 +13,8 @@ module.exports = { scaffolds: "./scaffolds", utils: "./utils", core: "./core", + store: "./store", + repository: "./repository", public: "./public", hooks: "./hooks", }, diff --git a/editor/components/canvas/controller-zoom-control.tsx b/editor/components/canvas/controller-zoom-control.tsx index 07ea95bd..a17dbc69 100644 --- a/editor/components/canvas/controller-zoom-control.tsx +++ b/editor/components/canvas/controller-zoom-control.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "@emotion/styled"; import RefreshSharpIcon from "@material-ui/icons/RefreshSharp"; +import { colors } from "theme"; export function ZoomControl({ scale, @@ -87,8 +88,8 @@ const Controls = styled.div` box-sizing: border-box; border-radius: 4px; padding: 4px; - background-color: #252526; - box-shadow: #25252650 0px 0px 0px 16px inset; + background-color: ${colors.color_editor_bg_on_dark}; + box-shadow: ${colors.color_editor_bg_on_dark} 0px 0px 0px 16px inset; `; const ControlsContainer = styled.div` diff --git a/editor/components/canvas/interactive-canvas.tsx b/editor/components/canvas/interactive-canvas.tsx index baa486fa..4f3db8e9 100644 --- a/editor/components/canvas/interactive-canvas.tsx +++ b/editor/components/canvas/interactive-canvas.tsx @@ -34,7 +34,7 @@ export function InteractiveCanvas({ const InteractiveCanvasWrapper = styled.div` display: flex; flex-direction: column; - overflow-y: auto; + /* overflow-y: auto; */ overflow-x: hidden; flex: 1; `; diff --git a/editor/components/code-editor/monaco-mock-empty.tsx b/editor/components/code-editor/monaco-mock-empty.tsx new file mode 100644 index 00000000..6c3dfac4 --- /dev/null +++ b/editor/components/code-editor/monaco-mock-empty.tsx @@ -0,0 +1,31 @@ +import React from "react"; +export function MonacoEmptyMock() { + return ( +
+ {Array.from(Array(100).keys()).map((i) => ( + + {i + 1} + + ))} +
+ ); +} diff --git a/editor/components/code-editor/monaco.tsx b/editor/components/code-editor/monaco.tsx index c1f3b617..92538629 100644 --- a/editor/components/code-editor/monaco.tsx +++ b/editor/components/code-editor/monaco.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import Editor, { useMonaco, Monaco } from "@monaco-editor/react"; import * as monaco from "monaco-editor/esm/vs/editor/editor.api"; +import { MonacoEmptyMock } from "./monaco-mock-empty"; export interface MonacoEditorProps { defaultValue?: string; @@ -26,7 +27,7 @@ export function MonacoEditor(props: MonacoEditorProps) { defaultLanguage={ pollyfill_language(props.defaultLanguage) ?? "typescript" } - loading={<>} + loading={} defaultValue={props.defaultValue ?? "// no content"} theme="vs-dark" options={{ ...props.options }} 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 a23bedc9..33bc1523 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 @@ -1,6 +1,7 @@ import React from "react"; import styled from "@emotion/styled"; import { useEditorState } from "core/states"; +import { colors } from "theme"; export function AppbarFragmentForCanvas() { const [state] = useEditorState(); @@ -19,7 +20,7 @@ const RootWrapperAppbarFragmentForCanvas = styled.div` align-items: center; gap: 10px; align-self: stretch; - background-color: rgba(37, 37, 38, 1); + 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 9e8aa2ad..d4a68e17 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 @@ -34,7 +34,6 @@ const RootWrapperAppbarFragmentForCodeEditor = styled.div` flex: 1; gap: 10px; align-self: stretch; - background-color: rgba(30, 30, 30, 1); box-sizing: border-box; padding-bottom: 14px; padding-top: 14px; diff --git a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx index 1176401d..110eaf33 100644 --- a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx +++ b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx @@ -1,15 +1,23 @@ import React from "react"; import styled from "@emotion/styled"; +import { ArrowBack } from "@material-ui/icons"; +import { useRouter } from "next/router"; +import { colors } from "theme"; export function AppbarFragmentForSidebar() { + const router = useRouter(); + return ( -
{ + router.push("/"); }} - >
+ /> {/* { + id: string; + name: string; + children?: ITreeNode[]; + data?: T; +} + +export interface FlattenedDisplayItemNode { + id: string; + name: string; + depth: number; + parent: string; + expanded?: boolean | undefined; + selected?: boolean; + data?: T; +} + +export const flatten = ( + tree: T, + parent?: string, + depth: number = 0 +): FlattenedDisplayItemNode[] => { + const convert = (node: T, depth: number, parent?: string) => { + if (!node) { + return; + } + + const result: FlattenedDisplayItemNode = { + ...node, + depth: depth, + parent, + }; + + return result; + }; + + const final = []; + final.push(convert(tree, depth, parent)); + for (const child of tree?.children || []) { + final.push(...flatten(child, tree.id, depth + 1)); + } + return final; +}; + +export function flattenNodeTree( + root: ReflectSceneNode, + selections: string[], + expands: string[] +): FlattenedDisplayItemNode[] { + const flattened: FlattenedDisplayItemNode[] = []; + + visit(root, { + getChildren: (layer) => { + if (expands.includes(layer.id)) { + return layer.children; + } + return []; + }, + + onEnter(layer, indexPath) { + flattened.push({ + id: layer.id, + name: layer.name, + parent: layer.parent?.id, + depth: indexPath.length - 1, + expanded: + layer.children.length <= 0 + ? undefined + : expands.includes(layer.id) + ? true + : false, + selected: selections.includes(layer.id), + data: layer, + }); + }, + }); + + return flattened; +} diff --git a/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-item.tsx b/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-item.tsx index 3026bac0..6accef23 100644 --- a/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-item.tsx +++ b/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-item.tsx @@ -94,7 +94,6 @@ export const LayerRow = memo( name, selected, onHoverChange, - onAddClick, onMenuClick, onClickChevron, onPress, @@ -105,7 +104,6 @@ export const LayerRow = memo( }: TreeView.TreeRowProps<""> & { name: string; selected: boolean; - onAddClick: () => void; onMenuClick: () => void; children?: ReactNode; }, @@ -130,6 +128,7 @@ export const LayerRow = memo( disabled={false} onPress={onPress} onClick={onClick} + onClickChevron={onClickChevron} onDoubleClick={onDoubleClick} {...props} > diff --git a/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-tree.tsx b/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-tree.tsx index a8f9fd88..05cb18e2 100644 --- a/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-tree.tsx +++ b/editor/components/editor/editor-layer-hierarchy/editor-layer-hierarchy-tree.tsx @@ -8,36 +8,61 @@ import { } from "./editor-layer-hierarchy-item"; import { useEditorState } from "core/states"; import { useDispatch } from "core/dispatch"; +import { + flattenNodeTree, + FlattenedDisplayItemNode, +} from "./editor-layer-heriarchy-controller"; export function EditorLayerHierarchy() { const [state] = useEditorState(); const dispatch = useDispatch(); + + const [expands, setExpands] = useState(state?.selectedNodes ?? []); + const root = state.selectedPage ? state.design.pages.find((p) => p.id == state.selectedPage).children : [state.design?.input?.entry]; - const layers: FlattenedNode[][] = root - ? root.filter((l) => !!l).map((layer) => flatten(layer)) - : []; + const layers: FlattenedDisplayItemNode[][] = useMemo(() => { + return root + ? root + .filter((l) => !!l) + .map((layer) => flattenNodeTree(layer, state.selectedNodes, expands)) + : []; + }, [root, state?.selectedNodes, expands]); const renderItem = useCallback( - ({ id, name, depth, type }) => { - const selected = state?.selectedNodes?.includes(id); + ({ + id, + name, + expanded, + selected, + depth, + data, + }: FlattenedDisplayItemNode) => { + // const _haschildren = useMemo(() => haschildren(id), [id, depth]); + // const _haschildren = haschildren(id); return ( - + } name={name} - depth={depth} + depth={depth + 1} // because the root is not a layer. it's the page, the array of roots. id={id} - expanded={haschildren(id) == true ? true : undefined} + expanded={expanded} key={id} selected={selected} - onAddClick={() => {}} + onClickChevron={() => { + if (expands.includes(id)) { + setExpands(expands.filter((e) => e !== id)); + } else { + setExpands([...expands, id]); + } + }} onMenuClick={() => {}} onDoubleClick={() => {}} onPress={() => { @@ -48,15 +73,15 @@ export function EditorLayerHierarchy() { /> ); }, - [state?.selectedNodes] + [dispatch, state?.selectedNodes, layers, expands] ); - const haschildren = useCallback( - (id: string) => { - return layers.some((l) => l.some((layer) => layer.parent === id)); - }, - [layers] - ); + // const haschildren = useCallback( + // (id: string) => { + // return layers.some((l) => l.some((layer) => layer.parent === id)); + // }, + // [layers] + // ); return ( ); } - -interface ITreeNode { - id: string; - name: string; - type: string; - children?: ITreeNode[]; -} - -interface FlattenedNode { - id: string; - name: string; - depth: number; - type: string; - parent: string; -} - -const flatten = ( - tree: T, - parent?: string, - depth: number = 0 -): FlattenedNode[] => { - const convert = (node: T, depth: number, parent?: string) => { - if (!node) { - return; - } - - const result: FlattenedNode = { - id: node.id, - name: node.name, - type: node.type, - depth: depth, - parent, - }; - - return result; - }; - - const final = []; - final.push(convert(tree, depth, parent)); - for (const child of tree?.children || []) { - final.push(...flatten(child, tree.id, depth + 1)); - } - return final; -}; diff --git a/editor/components/home-input/home-input-appbar.tsx b/editor/components/home-input/home-input-appbar.tsx index 3d8136b8..20ba2de9 100644 --- a/editor/components/home-input/home-input-appbar.tsx +++ b/editor/components/home-input/home-input-appbar.tsx @@ -1,21 +1,34 @@ import React from "react"; import styled from "@emotion/styled"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +export function HomeInputAppbar({ show_signin }: { show_signin: boolean }) { + const router = useRouter(); -export function HomeInputAppbar() { return ( - - Github - - - Grida.co - - - Docs - - - Sign in - + + Github + + + Grida.co + + + Docs + + {show_signin && ( + { + router.push( + "https://accounts.grida.co/signin?redirect_uri=" + + encodeURIComponent(router.asPath) + ); + }} + > + Sign in + + )} ); } @@ -45,15 +58,27 @@ const GithubMenu = styled.div` padding: 10px 10px; `; -const Github = styled.span` +const MenuTetx = styled.span` color: rgba(147, 147, 147, 1); - text-overflow: ellipsis; font-size: 16px; font-family: "Helvetica Neue", sans-serif; font-weight: 400; text-align: left; `; +const Menu = styled.a` + display: flex; + text-decoration: none; + justify-content: flex-start; + flex-direction: row; + align-items: start; + flex: none; + border-radius: 4px; + height: 40px; + box-sizing: border-box; + padding: 10px 10px; +`; + const GridacoMenu = styled.div` display: flex; justify-content: flex-start; @@ -68,15 +93,6 @@ const GridacoMenu = styled.div` padding: 10px 10px; `; -const GridaCo = styled.span` - color: rgba(147, 147, 147, 1); - text-overflow: ellipsis; - font-size: 16px; - font-family: "Helvetica Neue", sans-serif; - font-weight: 400; - text-align: left; -`; - const DocsMenu = styled.div` display: flex; justify-content: flex-start; @@ -91,15 +107,6 @@ const DocsMenu = styled.div` padding: 10px 10px; `; -const Docs = styled.span` - color: rgba(147, 147, 147, 1); - text-overflow: ellipsis; - font-size: 16px; - font-family: "Helvetica Neue", sans-serif; - font-weight: 400; - text-align: left; -`; - const SigninButton = styled.div` display: flex; justify-content: flex-start; diff --git a/editor/components/home-input/home-primary-input-next-button.tsx b/editor/components/home-input/home-primary-input-next-button.tsx index 5cad5fb2..d1f32b48 100644 --- a/editor/components/home-input/home-primary-input-next-button.tsx +++ b/editor/components/home-input/home-primary-input-next-button.tsx @@ -1,43 +1,23 @@ import React from "react"; import styled from "@emotion/styled"; - +import { ArrowRightIcon } from "@radix-ui/react-icons"; export function HomePrimaryInputNextButton({ disabled = false, + onClick, }: { disabled?: boolean; + onClick: () => void; }) { - if (disabled) { - return ; - } - return ; -} - -function DisabledFalseThemeDark() { - return ( - - ); -} + const color = disabled ? "#c4c4c4" : "#00a0ff"; -const RootWrapperDisabledFalseThemeDark = styled.img` - width: 24px; - height: 24px; - object-fit: cover; -`; - -function DisabledTrueThemeDark() { return ( - +
+ +
); } - -const RootWrapperDisabledTrueThemeDark = styled.img` - width: 24px; - height: 24px; - object-fit: cover; -`; diff --git a/editor/components/home/card.tsx b/editor/components/home/cards/base-home-scene-card.tsx similarity index 85% rename from editor/components/home/card.tsx rename to editor/components/home/cards/base-home-scene-card.tsx index 57462941..c9437d1c 100644 --- a/editor/components/home/card.tsx +++ b/editor/components/home/cards/base-home-scene-card.tsx @@ -1,7 +1,8 @@ import React from "react"; import styled from "@emotion/styled"; +import { colors } from "theme"; -export function HomeSceneCard({ +export function BaseHomeSceneCard({ label, description, onClick, @@ -18,9 +19,9 @@ export function HomeSceneCard({ - - - + + {thumbnail && } + @@ -45,7 +46,7 @@ const RootWrapperBaseHomeSceneCard = styled.div` gap: 10px; border: solid 1px rgba(72, 72, 72, 1); border-radius: 2px; - background-color: rgba(37, 37, 38, 1); + background-color: ${colors.color_editor_bg_on_dark}; box-sizing: border-box; :hover { @@ -76,8 +77,9 @@ const ThumbnailArea = styled.div` box-sizing: border-box; `; -const SceneCardPreviewThumbnailImage = styled.div` +const SceneCardPreviewThumbnail = styled.div` height: 101px; + background-color: #c1c1c1; position: relative; align-self: stretch; `; @@ -108,7 +110,6 @@ const LabelDescContainer = styled.div` flex: none; gap: 10px; width: 221px; - height: 48px; box-sizing: border-box; `; @@ -117,7 +118,6 @@ const LabelArea = styled.div` justify-content: flex-start; flex-direction: row; align-items: center; - flex: 1; gap: 8px; align-self: stretch; box-sizing: border-box; @@ -135,7 +135,11 @@ const ThisLabel = styled.span` font-family: "Helvetica Neue", sans-serif; font-weight: 400; text-align: left; - width: 197px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + -webkit-line-clamp: 1; + line-clamp: 1; `; const Description = styled.span` diff --git a/editor/components/recent-design-card/builtin-demo-design-card.tsx b/editor/components/home/cards/builtin-demo-file-card.tsx similarity index 58% rename from editor/components/recent-design-card/builtin-demo-design-card.tsx rename to editor/components/home/cards/builtin-demo-file-card.tsx index 9750aa93..65768ee2 100644 --- a/editor/components/recent-design-card/builtin-demo-design-card.tsx +++ b/editor/components/home/cards/builtin-demo-file-card.tsx @@ -1,23 +1,23 @@ import React from "react"; -import { RecentDesign } from "../../store"; -import { RecentDesignCard } from "./recent-design-card"; import moment from "moment"; import router from "next/router"; -import { formToCodeUrl } from "../../url"; +import { formToCodeUrl } from "../../../url"; +import { FileCard } from "./card-variant-file"; const _id = "https://www.figma.com/file/x7RRK6RwWtZuNakmbMLTVH/examples?node-id=1%3A120"; -const defaultdemodesign: RecentDesign = { - id: _id, +const defaultdemodesign = { + type: "file" as "file", + key: "x7RRK6RwWtZuNakmbMLTVH", name: "WNV Main screen", provider: "figma", - addedAt: moment().toDate(), + lastUsed: moment().toDate(), lastUpdatedAt: moment().toDate(), - previewUrl: + thumbnailUrl: "https://example-project-manifest.s3.us-west-1.amazonaws.com/app-wnv/cover.png", }; -export function BuiltinDemoDesignCard() { +export function BuiltinDemoFileCard() { const onclick = () => { const _path = formToCodeUrl({ design: _id, @@ -26,7 +26,7 @@ export function BuiltinDemoDesignCard() { }; return ( <> - + ); } diff --git a/editor/components/home/cards/builtin-import-new-design-card.tsx b/editor/components/home/cards/builtin-import-new-design-card.tsx new file mode 100644 index 00000000..b1f1c432 --- /dev/null +++ b/editor/components/home/cards/builtin-import-new-design-card.tsx @@ -0,0 +1,26 @@ +import { useRouter } from "next/router"; +import React from "react"; +import { FileCard } from "./card-variant-file"; + +const _id = "--new--"; +const importnewdesingcarddata = { + type: "file" as "file", + key: _id, + name: "New Design", + thumbnailUrl: + "https://example-project-manifest.s3.us-west-1.amazonaws.com/app-new/cover.png", +}; + +export function ImportNewDesignCard() { + const router = useRouter(); + const onclick = () => { + // router.push("/import"); + router.push("https://grida.co"); + }; + + return ( + <> + + + ); +} diff --git a/editor/components/home/cards/card-variant-component.tsx b/editor/components/home/cards/card-variant-component.tsx new file mode 100644 index 00000000..14823b9b --- /dev/null +++ b/editor/components/home/cards/card-variant-component.tsx @@ -0,0 +1,46 @@ +import React, { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { BaseHomeSceneCard } from "./base-home-scene-card"; +import { fetch } from "@design-sdk/figma-remote"; +import { useFigmaAccessToken } from "hooks/use-figma-access-token"; + +export function ComponentCard({ + label, + thumbnail: initialThumbnail, + data, +}: { + label: string; + thumbnail: string; + data: { + file: string; + fileName: string; + id: string; + }; +}) { + const router = useRouter(); + + const fat = useFigmaAccessToken(); + + const [thumbnail, setThumbnail] = useState(initialThumbnail); + + useEffect(() => { + if (fat) { + if (!thumbnail) { + fetch.fetchNodeAsImage(data.file, fat, data.id).then((url) => { + setThumbnail(url.__default); + }); + } + } + }, [fat]); + + return ( + { + router.push("/files/[key]/[id]", `/files/${data.file}/${data.id}`); + }} + label={label} + description={data.fileName} + thumbnail={thumbnail} + /> + ); +} diff --git a/editor/components/home/cards/card-variant-file.tsx b/editor/components/home/cards/card-variant-file.tsx new file mode 100644 index 00000000..b0ef2ba2 --- /dev/null +++ b/editor/components/home/cards/card-variant-file.tsx @@ -0,0 +1,34 @@ +import { useRouter } from "next/router"; +import React, { useEffect } from "react"; +import { LastUsedFileDisplay } from "repository/workspace-repository"; +import { BaseHomeSceneCard } from "./base-home-scene-card"; + +export function FileCard({ + label, + data, + onClick, +}: { + data: { + type?: "file"; + lastUsed?: Date; + key: string; + name: string; + thumbnailUrl: string; + }; + label?: string; + onClick?: () => void; +}) { + const router = useRouter(); + return ( + { + router.push(`/files/[key]`, `/files/${data.key}`); + }) + } + label={label ?? data.name} + thumbnail={data.thumbnailUrl} + /> + ); +} diff --git a/editor/components/home/cards/card-variant-scene.tsx b/editor/components/home/cards/card-variant-scene.tsx new file mode 100644 index 00000000..1e9fc45e --- /dev/null +++ b/editor/components/home/cards/card-variant-scene.tsx @@ -0,0 +1,33 @@ +import React, { useEffect, useState } from "react"; +import { BaseHomeSceneCard } from "./base-home-scene-card"; +import { fetch } from "@design-sdk/figma-remote"; +import { useFigmaAccessToken } from "hooks/use-figma-access-token"; + +export function SceneCard({ + label, + thumbnail: initialThumbnail, + data, +}: { + label: string; + thumbnail: string; + data: { + file: string; + id: string; + }; +}) { + const fat = useFigmaAccessToken(); + + const [thumbnail, setThumbnail] = useState(initialThumbnail); + + useEffect(() => { + if (fat) { + if (!thumbnail) { + fetch.fetchNodeAsImage(data.file, fat, data.id).then((url) => { + setThumbnail(url.__default); + }); + } + } + }, [fat]); + + return ; +} diff --git a/editor/components/home/cards/index.ts b/editor/components/home/cards/index.ts new file mode 100644 index 00000000..63cf5257 --- /dev/null +++ b/editor/components/home/cards/index.ts @@ -0,0 +1,11 @@ +export * from "./builtin-demo-file-card"; +export * from "./builtin-import-new-design-card"; + +import { ComponentCard } from "./card-variant-component"; +import { FileCard } from "./card-variant-file"; +import { SceneCard } from "./card-variant-scene"; +export const Cards = { + Component: ComponentCard, + Scene: SceneCard, + File: FileCard, +}; diff --git a/editor/components/home/home-card-group/home-card-group-header.tsx b/editor/components/home/home-card-group/home-card-group-header.tsx new file mode 100644 index 00000000..68f79ab2 --- /dev/null +++ b/editor/components/home/home-card-group/home-card-group-header.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import styled from "@emotion/styled"; +import Link from "next/link"; +export function HomeSceneCardGoupHeader({ + label, + action, + onAction, + anchor, +}: { + label: string; + onAction?: () => void; + action?: string; + anchor?: string; +}) { + const Content = ( + + + {action && {action}} + + ); + + return anchor ? {Content} : Content; +} + +const RootWrapperBaseHomeSceneCardGoupHeader = styled.div` + cursor: pointer; + display: flex; + justify-content: flex-start; + flex-direction: row; + align-items: center; + flex: 1; + align-self: stretch; + box-sizing: border-box; +`; + +const Label = styled.span` + color: rgba(255, 255, 255, 1); + text-overflow: ellipsis; + font-size: 21px; + font-family: "Helvetica Neue", sans-serif; + font-weight: 400; + text-align: left; + flex: 1; +`; + +const Action = styled.span` + color: rgba(255, 255, 255, 1); + text-overflow: ellipsis; + font-size: 16px; + font-family: "Helvetica Neue", sans-serif; + font-weight: 400; + text-align: left; + opacity: 0.2; +`; diff --git a/editor/components/home/home-card-group/home-card-group.tsx b/editor/components/home/home-card-group/home-card-group.tsx new file mode 100644 index 00000000..4afb523a --- /dev/null +++ b/editor/components/home/home-card-group/home-card-group.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { HomeSceneCardGoupHeader } from "./home-card-group-header"; + +export function HomeCardGroup({ + label, + anchor, + cards, +}: { + label?: string; + anchor?: string; + cards: React.ReactNode[]; +}) { + return ( + + {label && } + {cards.map((card) => card)} + + ); +} + +const RootWrapperGroup = styled.section` + display: flex; + justify-content: flex-start; + flex-direction: column; + align-items: start; + flex: 1; + gap: 24px; + align-self: stretch; + box-sizing: border-box; +`; + +const Cards = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + flex-direction: row; + align-items: start; + flex: 1; + gap: 20px; + align-self: stretch; + box-sizing: border-box; +`; diff --git a/editor/components/home/home-card-group/index.ts b/editor/components/home/home-card-group/index.ts new file mode 100644 index 00000000..62078287 --- /dev/null +++ b/editor/components/home/home-card-group/index.ts @@ -0,0 +1,2 @@ +export * from "./home-card-group-header"; +export * from "./home-card-group"; diff --git a/editor/components/home/home-side-bar-tree-item.tsx b/editor/components/home/home-side-bar-tree-item.tsx index ec2574f7..55946f24 100644 --- a/editor/components/home/home-side-bar-tree-item.tsx +++ b/editor/components/home/home-side-bar-tree-item.tsx @@ -52,7 +52,6 @@ export const PageRow = memo( name, selected, onHoverChange, - onAddClick, onMenuClick, onClickChevron, onPress, @@ -63,7 +62,6 @@ export const PageRow = memo( }: TreeView.TreeRowProps<""> & { name: string; selected: boolean; - onAddClick: () => void; onMenuClick: () => void; children?: ReactNode; }, @@ -89,18 +87,19 @@ export const PageRow = memo( onPress={onPress} onClick={onClick} onDoubleClick={onDoubleClick} + onClickChevron={onClickChevron} {...props} > {withSeparatorElements( [ {name}, - hovered && ( - <> - - - {/* */} - - ), + // hovered && ( + // <> + // + // + // {/* */} + // + // ), ], )} diff --git a/editor/components/home/home-side-bar-tree.tsx b/editor/components/home/home-side-bar-tree.tsx index 03cb78dc..c2604f64 100644 --- a/editor/components/home/home-side-bar-tree.tsx +++ b/editor/components/home/home-side-bar-tree.tsx @@ -1,46 +1,129 @@ import React, { memo, useCallback, useMemo, useState } from "react"; import styled from "@emotion/styled"; import { TreeView } from "@editor-ui/editor"; -import { ListView } from "@editor-ui/listview"; import { PageRow } from "./home-side-bar-tree-item"; +import { useRouter } from "next/router"; +import { flatten } from "components/editor/editor-layer-hierarchy/editor-layer-heriarchy-controller"; +interface PresetPage { + id: string; + name: string; + path: string; + depth: number; + children?: PresetPage[]; +} + +const preset_pages: PresetPage[] = [ + { + id: "/", + name: "Home", + path: "/", + depth: 0, + children: [ + { + id: "/#recents", + name: "Recents", + path: "/#recents", + depth: 1, + }, + { + id: "/#files", + name: "Files", + path: "/#files", + depth: 1, + }, + // { + // id: "/#scenes", + // name: "Scenes", + // path: "/#scenes", + // depth: 1, + // }, + // { + // id: "/#components", + // name: "Components", + // path: "/#components", + // depth: 1, + // }, + ], + }, + { + id: "/files", + name: "Files", + path: "/files", + depth: 0, + }, + // { + // id: "/components", + // name: "Components", + // path: "/components", + // depth: 0, + // }, + // { + // id: "/integrations", + // name: "Import / Sync", + // path: "/integrations", + // depth: 0, + // }, + { + id: "help", + name: "Help", + path: "", + depth: 0, + children: [ + { + id: "bug-report", + name: "Bug report", + path: "https://github.com/gridaco/designto-code/issues/new", + depth: 1, + }, + { + id: "Github", + name: "Github", + path: "https://github.com/gridaco/designto-code/", + depth: 1, + }, + { + id: "docs", + name: "Docs", + path: "https://github.com/gridaco/designto-code/tree/main/docs", + depth: 1, + }, + ], + }, +]; export function HomeSidebarTree() { + const router = useRouter(); + const [selected, setSelected] = useState(router.asPath); + const renderItem = useCallback( - ({ id, name, depth }, index: number, { isDragging }: ListView.ItemInfo) => { + ({ id, name, path, depth, children }, index: number) => { return ( {}} + depth={depth} + expanded={children?.length > 0 ? true : undefined} + selected={selected == path} onMenuClick={() => {}} onDoubleClick={() => {}} - onPress={() => {}} - onSelectMenuItem={() => {}} + onPress={() => { + setSelected(path); + router.push(path); + }} + onClickChevron={() => {}} onContextMenu={() => {}} /> ); }, - [] + [selected] ); - const pageInfo = [ - { id: "1", name: "Page 1", depth: 0 }, - { id: "2", name: "Page 2", depth: 0 }, - { id: "3", name: "Page 3", depth: 0 }, - { id: "4", name: "Page 4", depth: 0 }, - { id: "5", name: "Page 5", depth: 0 }, - ]; + const pages = preset_pages.map((pageroot) => flatten(pageroot)).flat(); return ( item.id, [])} - // onMoveItem={} - acceptsDrop={() => false} renderItem={renderItem} /> ); diff --git a/editor/components/home/home-side-bar.tsx b/editor/components/home/home-side-bar.tsx index bccf2119..bcceb3de 100644 --- a/editor/components/home/home-side-bar.tsx +++ b/editor/components/home/home-side-bar.tsx @@ -5,6 +5,7 @@ import { SideNavigation } from "components/side-navigation"; export function HomeSidebar() { return ( +
); diff --git a/editor/components/home/index.ts b/editor/components/home/index.ts index 89bc7282..4d38de34 100644 --- a/editor/components/home/index.ts +++ b/editor/components/home/index.ts @@ -1,3 +1,9 @@ export * from "./home-side-bar"; export * from "./home-side-bar-tree"; export * from "./home-side-bar-tree-item"; + +export * from "./home-card-group"; + +export * from "./cards"; + +export * from "./texts"; diff --git a/editor/components/home/texts/home-heading.tsx b/editor/components/home/texts/home-heading.tsx new file mode 100644 index 00000000..f19ae147 --- /dev/null +++ b/editor/components/home/texts/home-heading.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import styled from "@emotion/styled"; + +export function HomeHeading({ children }: { children: React.ReactNode }) { + return {children}; +} + +export const Heading = styled.h1` + color: white; +`; diff --git a/editor/components/home/texts/index.ts b/editor/components/home/texts/index.ts new file mode 100644 index 00000000..2ac83f16 --- /dev/null +++ b/editor/components/home/texts/index.ts @@ -0,0 +1 @@ +export * from "./home-heading"; diff --git a/editor/components/recent-design-card/import-new-design-card.tsx b/editor/components/recent-design-card/import-new-design-card.tsx deleted file mode 100644 index ced31212..00000000 --- a/editor/components/recent-design-card/import-new-design-card.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { RecentDesign } from "../../store/recent-designs-store"; -import { RecentDesignCard } from "./recent-design-card"; - -const _id = "--new--"; -const importnewdesingcarddata: RecentDesign = { - id: _id, - name: "New Design", - lastUpdatedAt: new Date(), - addedAt: new Date(), - provider: "unknown", - previewUrl: - "https://example-project-manifest.s3.us-west-1.amazonaws.com/app-new/cover.png", -}; - -export function ImportNewDesignCard() { - const onclick = () => { - // TODO: import design - }; - - return ( - <> - - - ); -} diff --git a/editor/components/recent-design-card/index.ts b/editor/components/recent-design-card/index.ts deleted file mode 100644 index ab02861d..00000000 --- a/editor/components/recent-design-card/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./recent-design-card"; -export * from "./recent-design-card-list"; diff --git a/editor/components/recent-design-card/recent-design-card-list.tsx b/editor/components/recent-design-card/recent-design-card-list.tsx deleted file mode 100644 index 07cd3801..00000000 --- a/editor/components/recent-design-card/recent-design-card-list.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import styled from "@emotion/styled"; -import router from "next/router"; -import React, { useEffect, useState } from "react"; -import { RecentDesignsStore, RecentDesign } from "../../store"; -import { BuiltinDemoDesignCard } from "./builtin-demo-design-card"; -import { ImportNewDesignCard } from "./import-new-design-card"; -import { RecentDesignCard } from "./recent-design-card"; - -export function RecentDesignCardList() { - const [recents, setRecents] = useState([]); - useEffect(() => { - const _loads = new RecentDesignsStore().load(); - setRecents(_loads); - }, []); - - const oncardclick = (id: string, d: RecentDesign) => { - console.log("click", id); - router.push(`/to-code/${id}`); // fixme id is not a param - // - }; - - return ( - - - - {recents.map((recentDesign) => { - return ( - - ); - })} - - ); -} - -const ListWrap = styled.div` - display: flex; - flex-direction: row; - gap: 20px; -`; diff --git a/editor/components/recent-design-card/recent-design-card.tsx b/editor/components/recent-design-card/recent-design-card.tsx deleted file mode 100644 index 833d3f88..00000000 --- a/editor/components/recent-design-card/recent-design-card.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import { RecentDesign } from "../../store"; -import moment from "moment"; -import styled from "@emotion/styled"; -import { HomeSceneCard } from "components/home/card"; - -export type OnCardClickCallback = (id: string, data?: RecentDesign) => void; - -/** - * create recent design card component - **/ -export function RecentDesignCard(props: { - data: RecentDesign; - onclick?: OnCardClickCallback; -}) { - const { name, id, provider, previewUrl, lastUpdatedAt } = props.data; - const onclick = () => { - props.onclick?.(id, props.data); - }; - return ( - - ); -} - -const _defaultpreview = - "https://s3.amazonaws.com/uifaces/faces/twitter/golovey/128.jpg"; -function _safe_previewurl(previewUrl: string): string { - if (!previewUrl) { - return _defaultpreview; - } - return previewUrl; -} - -function _str_lastUpdatedAt(lastUpdatedAt: Date) { - return moment(lastUpdatedAt).format("MM/dd/yyyy"); -} - -function _str_alt(name: string) { - return `${name} Design`; -} diff --git a/editor/hooks/use-design.ts b/editor/hooks/use-design.ts index e0b727fb..14a23f38 100644 --- a/editor/hooks/use-design.ts +++ b/editor/hooks/use-design.ts @@ -15,6 +15,7 @@ import { convert } from "@design-sdk/figma-node-conversion"; import { mapFigmaRemoteToFigma } from "@design-sdk/figma-remote/lib/mapper"; import { useFigmaAccessToken } from "."; import { FileResponse } from "@design-sdk/figma-remote-types"; +import { FigmaDesignRepository } from "repository/figma-design-repository"; // globally configure auth credentials for interacting with `@design-sdk/figma-remote` configure_auth_credentials({ @@ -57,8 +58,7 @@ export function useDesign({ ...props }: UseDesignProp) { const [design, setDesign] = useState(null); - const figmaAccessToken = useFigmaAccessToken(); - const personalAccessToken = personal.get_safe(); + const fat = useFigmaAccessToken(); const router = (type === "use-router" && props["router"]) ?? useRouter(); useEffect(() => { @@ -123,15 +123,12 @@ export function useDesign({ }; setDesign(res); } else { - if (figmaAccessToken || personalAccessToken) { + if (fat.accessToken || fat.personalAccessToken) { fetch .fetchTargetAsReflect({ file: targetnodeconfig.file, node: targetnodeconfig.node, - auth: { - personalAccessToken: personalAccessToken, - accessToken: figmaAccessToken, - }, + auth: fat, }) .then((res) => { cacheStore.set(res.raw); // setting cache does not need to be determined by `use_session_cache` option. @@ -149,7 +146,7 @@ export function useDesign({ break; } default: - if (figmaAccessToken) { + if (fat.accessToken) { // wait.. } else { console.error(`error while fetching design`, err); @@ -165,24 +162,18 @@ export function useDesign({ } } } - }, [router, figmaAccessToken, props["url"]]); + }, [router, fat.accessToken, props["url"]]); return design; } export function useDesignFile({ file }: { file: string }) { const [designfile, setDesignFile] = useState(null); - const figmaAccessToken = useFigmaAccessToken(); - const figmaPersonalAccessToken = personal.get_safe(); + const fat = useFigmaAccessToken(); useEffect(() => { - if (file && (figmaAccessToken || figmaPersonalAccessToken)) { + if (file && (fat.accessToken || fat.personalAccessToken)) { async function handle() { - const iterator = fetch.fetchFile({ - file, - auth: { - personalAccessToken: figmaPersonalAccessToken, - accessToken: figmaAccessToken, - }, - }); + const repo = new FigmaDesignRepository(fat); + const iterator = repo.fetchFile(file); let next: IteratorResult; while ((next = await iterator.next()).done === false) { setDesignFile(next.value); @@ -190,7 +181,7 @@ export function useDesignFile({ file }: { file: string }) { } handle(); } - }, [file, figmaAccessToken]); + }, [file, fat.accessToken]); return designfile; } diff --git a/editor/hooks/use-figma-access-token.ts b/editor/hooks/use-figma-access-token.ts index 86aa93f7..05a2cd02 100644 --- a/editor/hooks/use-figma-access-token.ts +++ b/editor/hooks/use-figma-access-token.ts @@ -1,11 +1,20 @@ import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import { personal } from "@design-sdk/figma-auth-store"; /** * retrieves figma access token (fat) from query param. + * while using this as a dependency, you should use the fat.accessToken, not the entire object. + * + * e.g. + * ``` + * const fat = useFigmaAccessToken(); + * useEffect(() => {}, [fat.accessToken]); + * ``` * @returns */ export function useFigmaAccessToken() { + const personalAccessToken = personal.get_safe(); const [fat, setFat] = useState(null); const router = useRouter(); @@ -13,5 +22,5 @@ export function useFigmaAccessToken() { setFat(router.query.fat as string); // undefined is a valid input. }, [router]); - return fat; + return { accessToken: fat, personalAccessToken: personalAccessToken }; } diff --git a/editor/icons/home-logo.tsx b/editor/icons/home-logo.tsx index 5a89f527..cce4c9e7 100644 --- a/editor/icons/home-logo.tsx +++ b/editor/icons/home-logo.tsx @@ -13,14 +13,14 @@ export const HomeLogo = ({ size = 42 }: { size?: number }) => { xmlns="http://www.w3.org/2000/svg" > diff --git a/editor/layouts/default-editor-workspace-layout.tsx b/editor/layouts/default-editor-workspace-layout.tsx index db324bd3..5cab767a 100644 --- a/editor/layouts/default-editor-workspace-layout.tsx +++ b/editor/layouts/default-editor-workspace-layout.tsx @@ -57,7 +57,6 @@ const PanelLeftSideWrap = styled.div` min-height: 100%; max-height: 100%; max-width: 400px; - overflow: auto; `; const PanelRightSideWrap = styled.div` diff --git a/editor/layouts/home/index.tsx b/editor/layouts/home/index.tsx deleted file mode 100644 index 871e45e6..00000000 --- a/editor/layouts/home/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import { RecentDesignCardList } from "components/recent-design-card"; -import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; -import { HomeSidebar } from "components/home"; - -export function HomeLayout() { - return ( - } - > - - - ); -} - -function BodyContainer() { - return ( -
- -
- ); -} - -function RecentDesignSection() { - return ( - <> - - - ); -} diff --git a/editor/layouts/panel/workspace-content-panel.tsx b/editor/layouts/panel/workspace-content-panel.tsx index d1ff4958..4f91c4c4 100644 --- a/editor/layouts/panel/workspace-content-panel.tsx +++ b/editor/layouts/panel/workspace-content-panel.tsx @@ -9,14 +9,17 @@ import React from "react"; export function WorkspaceContentPanel({ children, disableBorder = true, + flex = 1, backgroundColor = "none", }: { backgroundColor?: string; children: JSX.Element; disableBorder?: boolean; + flex?: number; }) { return ( @@ -26,6 +29,7 @@ export function WorkspaceContentPanel({ } const WorkspaceCPanel = styled.div<{ + flex?: number; backgroundColor: string; disableBorder: boolean; }>` @@ -33,6 +37,6 @@ const WorkspaceCPanel = styled.div<{ background-color: ${(p) => p.backgroundColor}; border-width: 1px; align-self: stretch; - flex: 1; + flex: ${(p) => p.flex}; overflow: auto; `; diff --git a/editor/next.config.js b/editor/next.config.js index f4149bf9..7457a78e 100644 --- a/editor/next.config.js +++ b/editor/next.config.js @@ -107,12 +107,12 @@ module.exports = withTM({ destination: "/preferences", permanent: true, }, - { - // typo gaurd - source: "/", - destination: "https://grida.co", - permanent: false, - }, + // { + // // typo gaurd + // source: "/", + // destination: "https://grida.co", + // permanent: false, + // }, ]; }, }); diff --git a/editor/package.json b/editor/package.json index 1e10ed84..586b90b3 100644 --- a/editor/package.json +++ b/editor/package.json @@ -41,6 +41,7 @@ "moment": "^2.29.1", "monaco-editor": "^0.24.0", "next": "10.0.3", + "pouchdb-adapter-idb": "^7.2.2", "re-resizable": "^6.9.1", "react": "17.0.1", "react-codemirror2": "^7.2.1", @@ -49,7 +50,10 @@ "react-resizable": "^3.0.1", "react-spring": "^9.3.2", "recoil": "^0.2.0", - "styled-components": "^5.3.3" + "rxdb": "^10.5.4", + "rxjs": "^7.4.0", + "styled-components": "^5.3.3", + "tree-visit": "^0.1.0" }, "devDependencies": { "@babel/core": "^7.14.0", diff --git a/editor/pages/_app.tsx b/editor/pages/_app.tsx index f84eb19d..c6eb7d18 100644 --- a/editor/pages/_app.tsx +++ b/editor/pages/_app.tsx @@ -2,13 +2,14 @@ import React from "react"; import { Global, css } from "@emotion/react"; import Head from "next/head"; import { EditorThemeProvider } from "@editor-ui/theme"; +import { colors } from "theme"; function GlobalCss() { return ( diff --git a/editor/pages/components/index.tsx b/editor/pages/components/index.tsx new file mode 100644 index 00000000..77168934 --- /dev/null +++ b/editor/pages/components/index.tsx @@ -0,0 +1,46 @@ +import React, { useEffect, useState } from "react"; +import styled from "@emotion/styled"; +import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; +import { + Cards, + HomeCardGroup, + HomeHeading, + HomeSidebar, +} from "components/home"; +import { WorkspaceRepository } from "repository"; +import { colors } from "theme"; + +export default function ComponentsPage() { + const repository = new WorkspaceRepository(); + + const [components, setComponents] = useState([]); + + useEffect(() => { + repository.getRecentComponents().then(setComponents); + }, []); + + return ( + <> + } + > +
+ Components + ( + + )), + ]} + /> +
+
+ + ); +} diff --git a/editor/pages/embed/vscode/grida-explorer-preview/index.tsx b/editor/pages/embed/vscode/grida-explorer-preview/index.tsx index cd07fef5..9639acb4 100644 --- a/editor/pages/embed/vscode/grida-explorer-preview/index.tsx +++ b/editor/pages/embed/vscode/grida-explorer-preview/index.tsx @@ -3,6 +3,7 @@ import styled from "@emotion/styled"; import { useRouter } from "next/router"; import VanillaPreview from "@code-editor/vanilla-preview"; import { ProgressBar } from "@modulz/design-system"; +import { colors } from "theme"; export default function VSCodeEmbedGridaExplorerPreview() { const router = useRouter(); // use router only for loading initial params. @@ -86,7 +87,7 @@ const EmptyStateContainer = styled.div` align-items: center; flex: none; gap: 10px; - background-color: rgba(37, 37, 38, 1); + background-color: ${colors.color_editor_bg_on_dark}; box-sizing: border-box; `; diff --git a/editor/pages/embed/vscode/index.tsx b/editor/pages/embed/vscode/index.tsx index 604d65a4..ef403f81 100644 --- a/editor/pages/embed/vscode/index.tsx +++ b/editor/pages/embed/vscode/index.tsx @@ -8,25 +8,25 @@ import styled from "@emotion/styled"; function Preview() { return ( - -
+
{}} + onMouseLeave={(e) => {}} + > +