diff --git a/editor-packages/editor-figma-file/repository.ts b/editor-packages/editor-figma-file/repository.ts
index f20212a1c..5f61a7198 100644
--- a/editor-packages/editor-figma-file/repository.ts
+++ b/editor-packages/editor-figma-file/repository.ts
@@ -39,7 +39,11 @@ export class FigmaDesignRepository {
if (existing) {
// everytime the file is consumed consider it as used, we upsert the file so that the lastUsed can be updated.
metastore.upsert(existing.key, existing);
- yield { ...existing, __initial: false } as TFetchFileForApp;
+ yield {
+ ...existing,
+ __initial: false,
+ __type: "file-fetched-for-app",
+ } as TFetchFileForApp;
}
const _iter = fetch.fetchFile({ file: filekey, auth: this.auth });
diff --git a/editor/components/home/home-side-bar-tree.tsx b/editor/components/home/home-side-bar-tree.tsx
index 2c8ee037c..006f73cbb 100644
--- a/editor/components/home/home-side-bar-tree.tsx
+++ b/editor/components/home/home-side-bar-tree.tsx
@@ -89,6 +89,12 @@ const preset_pages: PresetPage[] = [
},
],
},
+ {
+ id: "/community/files",
+ name: "Community Files",
+ path: "/community/files",
+ depth: 0,
+ },
];
export function HomeSidebarTree() {
diff --git a/editor/components/prompt-banner-signin-to-continue/index.tsx b/editor/components/prompt-banner-signin-to-continue/index.tsx
index 2a0e57286..928e47eb1 100644
--- a/editor/components/prompt-banner-signin-to-continue/index.tsx
+++ b/editor/components/prompt-banner-signin-to-continue/index.tsx
@@ -1,9 +1,10 @@
import styled from "@emotion/styled";
-import React, { useEffect } from "react";
+import React from "react";
import { useAuthState } from "hooks";
import { useRouter } from "next/router";
-
-const __is_dev = process.env.NODE_ENV == "development";
+import { ArrowRightIcon } from "@radix-ui/react-icons";
+const __is_prod = process.env.NODE_ENV == "production";
+const __overide_show_if_dev = true;
export function SigninToContinuePrmoptProvider({
children,
@@ -17,7 +18,9 @@ export function SigninToContinuePrmoptProvider({
return (
<>
{children}
- {!__is_dev && shouldshow && }
+ {((__is_prod && shouldshow) || (!__is_prod && __overide_show_if_dev)) && (
+
+ )}
>
);
}
@@ -31,34 +34,30 @@ export function SigninToContinueBannerPrmopt() {
return (
-
- Ready to build your apps with Grida?
- Next
-
+
+ Ready to build your Apps with Grida?
+
+ Sign Up
+
+
+
);
}
const Positioner = styled.div`
- display: flex;
- align-items: center;
- flex-direction: column;
- justify-content: center;
- align-items: flex-end;
-
position: fixed;
bottom: 0;
- left: 0px;
- right: 0px;
+ left: 0;
+ right: 0;
+ padding: 40px 40px;
- background-color: #fff;
z-index: 998;
-
+ display: flex;
justify-content: center;
flex-direction: column;
- align-items: end;
+ align-items: center;
box-sizing: border-box;
- padding: 16px 20px;
a {
margin: 0px 2px;
@@ -66,56 +65,62 @@ const Positioner = styled.div`
}
`;
-const Contents = styled.div`
+const Container = styled.div`
+ width: 100%;
+ max-width: 600px;
+ min-width: 400px;
+ background-color: rgba(0, 0, 0, 0.4);
+ backdrop-filter: blur(21px);
+ color: white;
display: flex;
- justify-content: flex-end;
+ justify-content: space-between;
flex-direction: row;
align-items: center;
flex: none;
+ padding: 16px 24px;
+ border-radius: 48px;
+ border: 1px solid rgba(255, 255, 255, 0.2);
gap: 48px;
- width: 439px;
- height: 59px;
box-sizing: border-box;
-`;
+ box-shadow: 0px 4px 32px rgba(0, 0, 0, 0.24);
-const Desc = styled.span`
- color: rgba(0, 0, 0, 1);
- text-overflow: ellipsis;
- font-size: 16px;
- font-family: "Helvetica Neue", sans-serif;
- font-weight: 500;
- text-align: center;
+ h5 {
+ color: white;
+ margin: 0;
+ text-overflow: ellipsis;
+ font-weight: 500;
+ }
`;
-const NextButton = styled.button`
+const CTAButton = styled.button`
+ cursor: pointer;
display: flex;
justify-content: center;
flex-direction: row;
align-items: center;
flex: none;
- gap: 10px;
- border-radius: 4px;
- width: 116px;
- height: 59px;
- background-color: rgba(45, 66, 255, 1);
+ gap: 4px;
+ border-radius: 24px;
+ background-color: transparent;
+ border: 1px solid rgba(255, 255, 255, 0.4);
box-sizing: border-box;
- padding: 10px 10px;
+ padding: 8px 16px;
outline: none;
- border: none;
- color: rgba(255, 255, 255, 1);
+ color: white;
text-overflow: ellipsis;
- font-size: 21px;
- font-family: "Helvetica Neue", sans-serif;
- font-weight: 400;
+ font-weight: 500;
text-align: left;
:hover {
opacity: 0.9;
+ scale: 1.02;
}
:focus {
opacity: 0.9;
}
+
+ transition: all 0.1s ease-in-out;
`;
diff --git a/editor/core/reducers/editor-reducer.ts b/editor/core/reducers/editor-reducer.ts
index 4471d7ac8..62e11e408 100644
--- a/editor/core/reducers/editor-reducer.ts
+++ b/editor/core/reducers/editor-reducer.ts
@@ -21,7 +21,7 @@ import type {
EnterIsolatedInspectionAction,
ExitIsolatedInspectionAction,
} from "core/actions";
-import { EditorState } from "core/states";
+import { EditorState, EssentialWorkspaceInfo } from "core/states";
import { NextRouter, useRouter } from "next/router";
import { CanvasStateStore } from "@code-editor/canvas/stores";
import q from "@design-sdk/query";
@@ -31,8 +31,6 @@ import { nanoid } from "nanoid";
import { last_page_by_mode } from "core/stores";
import { track } from "@code-editor/analytics";
-const _editor_path_name = "/files/[key]/";
-
const _DEV_CLEAR_LOG = false;
const clearlog = (by: string) => {
@@ -42,7 +40,10 @@ const clearlog = (by: string) => {
}
};
-export function editorReducer(state: EditorState, action: Action): EditorState {
+export function editorReducer(
+ state: EditorState & EssentialWorkspaceInfo,
+ action: Action
+): EditorState {
const router = useRouter();
const filekey = state.design.key;
@@ -395,10 +396,8 @@ function update_route(
// remove undefined fields
Object.keys(q).forEach((k) => q[k] === undefined && delete q[k]);
-
router.push(
{
- pathname: _editor_path_name,
query: { ...router.query, ...q },
},
undefined,
diff --git a/editor/core/states/workspace-initial-state.ts b/editor/core/states/workspace-initial-state.ts
index b51a93f21..83566a973 100644
--- a/editor/core/states/workspace-initial-state.ts
+++ b/editor/core/states/workspace-initial-state.ts
@@ -1,5 +1,5 @@
import { EditorSnapshot } from "./editor-state";
-import { WorkspaceState } from "./workspace-state";
+import { WorkspaceState, EssentialWorkspaceInfo } from "./workspace-state";
import {
createInitialHistoryState,
createPendingHistoryState,
@@ -23,7 +23,7 @@ export function merge_initial_workspace_state_with_editor_snapshot(
};
}
-export function create_initial_pending_workspace_state(): WorkspaceState {
+export function create_initial_pending_workspace_state({}: EssentialWorkspaceInfo): WorkspaceState {
return {
taskQueue: {
isBusy: false,
diff --git a/editor/core/states/workspace-state.ts b/editor/core/states/workspace-state.ts
index 0ea4caf0c..640fe3aba 100644
--- a/editor/core/states/workspace-state.ts
+++ b/editor/core/states/workspace-state.ts
@@ -1,7 +1,11 @@
import { config } from "@grida/builder-config";
import { HistoryState } from "core/states/history-state";
-export interface WorkspaceState {
+export interface EssentialWorkspaceInfo {
+ // Add workspace seed data here, which cannot be automatically filled on initial state.
+}
+
+export interface WorkspaceState extends EssentialWorkspaceInfo {
history: HistoryState;
/**
* hovered layer; single or none.
diff --git a/editor/hooks/index.ts b/editor/hooks/index.ts
index 8c1b01f77..f45eb3794 100644
--- a/editor/hooks/index.ts
+++ b/editor/hooks/index.ts
@@ -1,4 +1,4 @@
-export * from "./use-design";
+export * from "./use-figma";
export * from "./use-async-effect";
export * from "./use-auth-state";
export * from "./use-target-node";
diff --git a/editor/hooks/use-figma/index.ts b/editor/hooks/use-figma/index.ts
new file mode 100644
index 000000000..78d5ffa18
--- /dev/null
+++ b/editor/hooks/use-figma/index.ts
@@ -0,0 +1,2 @@
+export * from "./use-figma";
+export * from "./use-figma-community";
diff --git a/editor/hooks/use-figma/types.ts b/editor/hooks/use-figma/types.ts
new file mode 100644
index 000000000..1a876bd42
--- /dev/null
+++ b/editor/hooks/use-figma/types.ts
@@ -0,0 +1,37 @@
+import type { NextRouter } from "next/router";
+import type { TFetchFileForApp } from "@editor/figma-file";
+
+export type UseFigmaInput =
+ | (UseFigmaFromRouter & UseFigmaOptions)
+ | (UseFimgaFromUrl & UseFigmaOptions)
+ | (UseFigmaFromFileNodeKey & UseFigmaOptions);
+
+export interface UseFigmaOptions {
+ use_session_cache?: boolean;
+}
+
+export interface UseFigmaFromRouter {
+ type: "use-router";
+ router?: NextRouter;
+}
+
+export interface UseFimgaFromUrl {
+ type: "use-url";
+ url: string;
+}
+
+export interface UseFigmaFromFileNodeKey {
+ type: "use-file-node-id";
+ file: string;
+ node: string;
+}
+
+export type TUseDesignFile =
+ | TFetchFileForApp
+ | {
+ __type: "error";
+ reason: "no-auth" | "unauthorized";
+ cached?: TFetchFileForApp;
+ }
+ | { __type: "error"; reason: "no-file" }
+ | { __type: "loading" };
diff --git a/editor/hooks/use-figma/use-figma-community.ts b/editor/hooks/use-figma/use-figma-community.ts
new file mode 100644
index 000000000..ee66e0beb
--- /dev/null
+++ b/editor/hooks/use-figma/use-figma-community.ts
@@ -0,0 +1,40 @@
+import { useState, useEffect } from "react";
+import type { TUseDesignFile, UseFigmaInput, UseFimgaFromUrl } from "./types";
+import { Client } from "@figma-api/community";
+import { TargetNodeConfig } from "query/target-node";
+
+const client = Client();
+
+/**
+ * Figma Community File Retrieval Hook
+ * This does not use...
+ * 1. local store since the api response is static and cached by browser.
+ * 2. procedual loading since whole file is archived at the server.
+ * @returns
+ */
+export function useFigmaCommunityFile({ id }: { id: string }) {
+ const [file, setFile] = useState({
+ __type: "loading",
+ });
+
+ useEffect(() => {
+ // load with community client
+ client.file(id).then(({ data }) => {
+ setFile({
+ ...data,
+ key: id,
+ __initial: true, // ?
+ __type: "file-fetched-for-app",
+ });
+ });
+ }, [id]);
+
+ //
+ return file;
+}
+
+export function useFigmaCommunityNode() {
+ const [design, setDesign] = useState(null);
+ throw new Error("not implemented");
+ //
+}
diff --git a/editor/hooks/use-design.ts b/editor/hooks/use-figma/use-figma.ts
similarity index 77%
rename from editor/hooks/use-design.ts
rename to editor/hooks/use-figma/use-figma.ts
index f2dbd4efb..5c222420d 100644
--- a/editor/hooks/use-design.ts
+++ b/editor/hooks/use-figma/use-figma.ts
@@ -1,4 +1,4 @@
-import { NextRouter, useRouter } from "next/router";
+import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { DesignProvider, analyzeDesignUrl } from "@design-sdk/url-analysis";
import {
@@ -8,17 +8,18 @@ import {
import { fetch } from "@design-sdk/figma-remote";
import { personal } from "@design-sdk/figma-auth-store";
import { configure_auth_credentials } from "@design-sdk/figma-remote";
-import { TargetNodeConfig } from "../query/target-node";
+import { TargetNodeConfig } from "query/target-node";
import {
FigmaRemoteErrors,
UnauthorizedError,
NotfoundError,
} from "@design-sdk/figma-remote";
-import { RemoteDesignSessionCacheStore } from "../store";
+import { RemoteDesignSessionCacheStore } from "store";
import { convert } from "@design-sdk/figma-node-conversion";
import { mapper } from "@design-sdk/figma-remote";
import { useFigmaAuth } from "scaffolds/workspace/figma-auth";
import { FigmaDesignRepository, TFetchFileForApp } from "@editor/figma-file";
+import type { TUseDesignFile, UseFigmaInput, UseFimgaFromUrl } from "./types";
// globally configure auth credentials for interacting with `@design-sdk/figma-remote`
configure_auth_credentials({
@@ -30,36 +31,11 @@ configure_auth_credentials({
*/
export const P_DESIGN = "design";
-type UseDesignProp =
- | (UseDesignFromRouter & UseDesingOptions)
- | (UseDesingFromUrl & UseDesingOptions)
- | (UseDesignFromFileAndNode & UseDesingOptions);
-
-interface UseDesingOptions {
- use_session_cache?: boolean;
-}
-
-interface UseDesignFromRouter {
- type: "use-router";
- router?: NextRouter;
-}
-
-interface UseDesingFromUrl {
- type: "use-url";
- url: string;
-}
-
-interface UseDesignFromFileAndNode {
- type: "use-file-node-id";
- file: string;
- node: string;
-}
-
-export function useDesign({
+export function useFigmaNode({
use_session_cache = false,
type,
...props
-}: UseDesignProp) {
+}: UseFigmaInput) {
const [design, setDesign] = useState(null);
const fat = useFigmaAuth();
const router = (type === "use-router" && props["router"]) ?? useRouter();
@@ -79,7 +55,7 @@ export function useDesign({
}
case "use-router": {
const designparam: string = router.query[P_DESIGN] as string;
- const _r = designparam && analyze(designparam);
+ const _r = designparam && analyzeRouterQuery(designparam);
switch (_r) {
case "figma": {
targetnodeconfig = parseFileAndNodeId(designparam);
@@ -96,7 +72,7 @@ export function useDesign({
break;
}
case "use-url": {
- targetnodeconfig = parseFileAndNodeId((props as UseDesingFromUrl).url);
+ targetnodeconfig = parseFileAndNodeId((props as UseFimgaFromUrl).url);
break;
}
}
@@ -176,36 +152,28 @@ export function useDesign({
return design;
}
-export type TUseDesignFile =
- | TFetchFileForApp
- | {
- __type: "error";
- reason: "no-auth" | "unauthorized";
- cached?: TFetchFileForApp;
- }
- | { __type: "error"; reason: "no-file" }
- | { __type: "loading" };
-
-export function useDesignFile({ file }: { file: string }) {
+export function useFigmaFile({ file }: { file: string }) {
const [designfile, setDesignFile] = useState({
__type: "loading",
});
const fat = useFigmaAuth();
+
+ async function iterator() {
+ const repo = new FigmaDesignRepository({
+ personalAccessToken: fat.personalAccessToken,
+ accessToken: fat.accessToken.token,
+ });
+ const iterator = repo.fetchFile(file);
+ let next: IteratorResult;
+ while ((next = await iterator.next()).done === false) {
+ setDesignFile(next.value);
+ }
+ }
+
useEffect(() => {
if (file) {
if (fat.personalAccessToken || fat.accessToken.token) {
- async function handle() {
- const repo = new FigmaDesignRepository({
- personalAccessToken: fat.personalAccessToken,
- accessToken: fat.accessToken.token,
- });
- const iterator = repo.fetchFile(file);
- let next: IteratorResult;
- while ((next = await iterator.next()).done === false) {
- setDesignFile(next.value);
- }
- }
- handle();
+ iterator();
} else {
if (fat.accessToken.loading) {
setDesignFile({
@@ -240,7 +208,7 @@ export function useDesignFile({ file }: { file: string }) {
return designfile;
}
-const analyze = (query: string): "id" | DesignProvider => {
+const analyzeRouterQuery = (query: string): "id" | DesignProvider => {
const _r = analyzeDesignUrl(query);
if (_r == "unknown") {
return "id";
diff --git a/editor/package.json b/editor/package.json
index f45835539..5fe0a1f97 100644
--- a/editor/package.json
+++ b/editor/package.json
@@ -23,6 +23,7 @@
"@emotion/react": "^11.11.0",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
+ "@figma-api/community": "^0.0.5",
"@floating-ui/core": "^1.0.1",
"@floating-ui/react-dom": "^1.0.0",
"@floating-ui/react-dom-interactions": "^0.10.2",
diff --git a/editor/pages/community/file/[id]/index.tsx b/editor/pages/community/file/[key]/index.tsx
similarity index 74%
rename from editor/pages/community/file/[id]/index.tsx
rename to editor/pages/community/file/[key]/index.tsx
index 89ce97a39..5609483f1 100644
--- a/editor/pages/community/file/[id]/index.tsx
+++ b/editor/pages/community/file/[key]/index.tsx
@@ -1,7 +1,11 @@
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { SigninToContinuePrmoptProvider } from "components/prompt-banner-signin-to-continue";
-import { Editor, SetupEditor } from "scaffolds/editor";
+import {
+ Editor,
+ EditorDefaultProviders,
+ SetupFigmaCommunityFileEditor,
+} from "scaffolds/editor";
import { Workspace, useWorkspaceInitializerContext } from "scaffolds/workspace";
import { useRouter } from "next/router";
import { InferGetServerSidePropsType } from "next";
@@ -58,45 +62,26 @@ export default function FigmaCommunityFileEditorPage(
-
-
+
+
-
-
+
+
>
);
}
-interface CommunityFileSetupProps {
- /**
- * The file id of the community file.
- */
- id: string;
-}
-
-function CommunityFileSetup({
- children,
- id,
-}: React.PropsWithChildren) {
- const { provideEditorSnapshot: initialize } =
- useWorkspaceInitializerContext();
-
- // TODO:
- return <>{children}>;
-}
-
export async function getServerSideProps(context) {
return {
props: new FigmaCommunityArchiveMetaRepository().getProps(
- context.params.id
+ context.params.key
),
};
}
diff --git a/editor/pages/community/file/[id]/notes.md b/editor/pages/community/file/[key]/notes.md
similarity index 100%
rename from editor/pages/community/file/[id]/notes.md
rename to editor/pages/community/file/[key]/notes.md
diff --git a/editor/pages/figma/inspect-component.tsx b/editor/pages/figma/inspect-component.tsx
index ca578b8ca..bdd999aa3 100644
--- a/editor/pages/figma/inspect-component.tsx
+++ b/editor/pages/figma/inspect-component.tsx
@@ -17,12 +17,12 @@ import {
WorkspaceContentPanelGridLayout,
} from "layouts/panel";
import { WorkspaceBottomPanelDockLayout } from "layouts/panel/workspace-bottom-panel-dock-layout";
-import { useDesign } from "hooks";
+import { useFigmaNode } from "hooks";
import { make_instance_component_meta } from "@code-features/component";
export default function InspectComponent() {
//
- const design = useDesign({ type: "use-router" });
+ const design = useFigmaNode({ type: "use-router" });
if (!design) {
return ;
}
diff --git a/editor/pages/figma/inspect-frame.tsx b/editor/pages/figma/inspect-frame.tsx
index 7222de65f..aac50aaa1 100644
--- a/editor/pages/figma/inspect-frame.tsx
+++ b/editor/pages/figma/inspect-frame.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { MonacoEditor } from "components/code-editor";
import { SceneNode } from "@design-sdk/figma-types";
-import { useDesign } from "hooks";
+import { useFigmaNode } from "hooks";
import LoadingLayout from "layouts/loading-overlay";
/**
@@ -10,7 +10,7 @@ import LoadingLayout from "layouts/loading-overlay";
*/
export default function InspectAutolayout() {
//
- const design = useDesign({ type: "use-router" });
+ const design = useFigmaNode({ type: "use-router" });
if (!design) {
return ;
}
diff --git a/editor/pages/figma/inspect-raw.tsx b/editor/pages/figma/inspect-raw.tsx
index ece227dc1..d596aba9d 100644
--- a/editor/pages/figma/inspect-raw.tsx
+++ b/editor/pages/figma/inspect-raw.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { MonacoEditor } from "components/code-editor";
-import { useDesign } from "hooks";
+import { useFigmaNode } from "hooks";
import LoadingLayout from "layouts/loading-overlay";
/**
@@ -9,7 +9,7 @@ import LoadingLayout from "layouts/loading-overlay";
*/
export default function InspectRaw() {
//
- const design = useDesign({ type: "use-router" });
+ const design = useFigmaNode({ type: "use-router" });
if (!design) {
return ;
}
diff --git a/editor/pages/figma/to-token.tsx b/editor/pages/figma/to-token.tsx
index 1d7ad6ed9..1ee22ed1a 100644
--- a/editor/pages/figma/to-token.tsx
+++ b/editor/pages/figma/to-token.tsx
@@ -10,7 +10,7 @@ import { LayerHierarchy } from "components/editor-hierarchy";
import { WorkspaceContentPanelGridLayout } from "layouts/panel/workspace-content-panel-grid-layout";
import { WorkspaceContentPanel } from "layouts/panel";
import { WorkspaceBottomPanelDockLayout } from "layouts/panel/workspace-bottom-panel-dock-layout";
-import { useDesign } from "hooks";
+import { useFigmaNode } from "hooks";
import {
ImageRepository,
MainImageRepository,
@@ -19,7 +19,7 @@ import { RemoteImageRepositories } from "@design-sdk/figma-remote/asset-reposito
import LoadingLayout from "layouts/loading-overlay";
export default function FigmaToReflectWidgetTokenPage() {
- const design = useDesign({ type: "use-router" });
+ const design = useFigmaNode({ type: "use-router" });
if (!design) {
return ;
diff --git a/editor/pages/files/[key]/index.tsx b/editor/pages/files/[key]/index.tsx
index 5975ccfc4..c64ae4808 100644
--- a/editor/pages/files/[key]/index.tsx
+++ b/editor/pages/files/[key]/index.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { SigninToContinuePrmoptProvider } from "components/prompt-banner-signin-to-continue";
-import { Editor, SetupEditor } from "scaffolds/editor";
+import { Editor, SetupFigmaFileEditor } from "scaffolds/editor";
import { Workspace } from "scaffolds/workspace/workspace";
import { EditorDefaultProviders } from "scaffolds/editor";
import { EditorBrowserMetaHead } from "components/editor";
@@ -16,7 +16,7 @@ export default function FileEntryEditor() {
return (
-
-
+
);
diff --git a/editor/pages/live/index.tsx b/editor/pages/live/index.tsx
index a773c56e6..df0d6b34d 100644
--- a/editor/pages/live/index.tsx
+++ b/editor/pages/live/index.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import Pusher from "pusher-js";
import LoadingLayout from "layouts/loading-overlay";
-import { useDesign } from "hooks";
+import { useFigmaNode } from "hooks";
import { designToCode, Result } from "@designto/code";
import { TargetNodeConfig } from "../../query/target-node";
import {
@@ -71,7 +71,7 @@ export default function LiveSessionPage() {
}
function DesignProxyPage({ file, node }: { file: string; node: string }) {
- const design = useDesign({
+ const design = useFigmaNode({
type: "use-file-node-id",
file: file,
node: node,
diff --git a/editor/scaffolds/editor/_providers.tsx b/editor/scaffolds/editor/_providers.tsx
index 6d785902a..20bbd2ef8 100644
--- a/editor/scaffolds/editor/_providers.tsx
+++ b/editor/scaffolds/editor/_providers.tsx
@@ -4,7 +4,6 @@ import { EditorImageRepositoryProvider } from "./editor-image-repository-provide
import { EditorPreviewDataProvider } from "./editor-preview-provider";
import { EditorCodeWebworkerProvider } from "scaffolds/editor/editor-code-webworker-provider";
import { EditorToastProvider } from "./editor-toast-provider";
-import { FigmaImageServiceProvider } from "./editor-figma-image-service-provider";
import { FigmaImageServiceProviderForCanvasRenderer } from "./editor-figma-image-service-for-canvas-provider";
import { DashboardStateProvider } from "@code-editor/dashboard";
import { EditorState, useEditorState } from "core/states";
@@ -34,13 +33,11 @@ export function EditorDefaultProviders(props: { children: React.ReactNode }) {
-
-
-
- {props.children}
-
-
-
+
+
+ {props.children}
+
+
diff --git a/editor/scaffolds/editor/editor-figma-image-service-provider.tsx b/editor/scaffolds/editor/editor-figma-image-service-provider.tsx
index 49f0d8bcf..1397c3d41 100644
--- a/editor/scaffolds/editor/editor-figma-image-service-provider.tsx
+++ b/editor/scaffolds/editor/editor-figma-image-service-provider.tsx
@@ -1,25 +1,42 @@
-import { useWorkspace, useWorkspaceState } from "core/states";
-import React, { useCallback, useEffect, useMemo } from "react";
-import { FigmaImageService } from "services";
+import { WorkspaceState, useWorkspace, useWorkspaceState } from "core/states";
+import React, { useEffect, useMemo } from "react";
+import { FigmaImageService, ImageClientInterface } from "services";
type Fetcher = { fetch: FigmaImageService["fetch"] };
type FetcherParams = Parameters;
+type ApiClientResolver = ({
+ filekey,
+ authentication,
+}: {
+ filekey: string;
+ authentication?: WorkspaceState["figmaAuthentication"];
+}) => ImageClientInterface | undefined | "reject";
export const FigmaImageServiceContext = React.createContext(null);
export function FigmaImageServiceProvider({
filekey,
children,
+ resolveApiClient,
}: React.PropsWithChildren<{
+ resolveApiClient: ApiClientResolver;
filekey: string;
}>) {
const wssate = useWorkspaceState();
const { pushTask, popTask } = useWorkspace();
const service = useMemo(() => {
- if (!filekey || !wssate.figmaAuthentication) return;
+ const client = resolveApiClient({
+ filekey: filekey,
+ authentication: wssate.figmaAuthentication,
+ });
+
+ if (client === "reject" || !client) {
+ // do not create service without valid client.
+ return;
+ }
- return new FigmaImageService(filekey, wssate.figmaAuthentication, null, 24);
+ return new FigmaImageService(filekey, client, null, 24);
}, [filekey, wssate.figmaAuthentication]);
const fetcher = useMemo(() => {
diff --git a/editor/scaffolds/editor/index.ts b/editor/scaffolds/editor/index.ts
index 59b735bfd..f73d6257a 100644
--- a/editor/scaffolds/editor/index.ts
+++ b/editor/scaffolds/editor/index.ts
@@ -1,4 +1,8 @@
-export { SetupEditor, useEditorSetupContext } from "./setup";
+export {
+ SetupFigmaFileEditor,
+ SetupFigmaCommunityFileEditor,
+ useEditorSetupContext,
+} from "./setup";
export { Editor } from "./editor";
export { EditorDefaultProviders } from "./_providers";
export { useFigmaImageService } from "./editor-figma-image-service-provider";
diff --git a/editor/scaffolds/editor/setup.tsx b/editor/scaffolds/editor/setup.tsx
index 801498dc9..bba84f056 100644
--- a/editor/scaffolds/editor/setup.tsx
+++ b/editor/scaffolds/editor/setup.tsx
@@ -1,11 +1,14 @@
import React, { useEffect, useCallback, useState } from "react";
import { NextRouter } from "next/router";
import { EditorPage, EditorSnapshot, useEditorState } from "core/states";
-import { useDesignFile } from "hooks";
+import { useFigmaCommunityFile, useFigmaFile } from "hooks";
import { warmup } from "scaffolds/editor";
import type { FileResponse } from "@design-sdk/figma-remote-types";
import { useWorkspaceInitializerContext } from "scaffolds/workspace";
import { useDispatch } from "@code-editor/preferences";
+import { FigmaImageServiceProvider } from "./editor-figma-image-service-provider";
+import { Client as FigmaImageClient } from "@design-sdk/figma-remote-api";
+import { Client as FigmaCommunityImageClient } from "@figma-api/community";
const action_fetchfile_id = "fetchfile" as const;
@@ -22,22 +25,28 @@ export function useEditorSetupContext() {
return React.useContext(EditorSetupContext);
}
-export function SetupEditor({
+interface EssentialEditorSetupProps {
+ nodeid: string;
+ filekey: string;
+ router: NextRouter;
+}
+
+function FigmaEditorBaseSetup({
+ file,
filekey,
nodeid,
router,
children,
-}: React.PropsWithChildren<{
- nodeid: string;
- filekey: string;
- router: NextRouter;
-}>) {
+ loaded,
+}: React.PropsWithChildren<
+ EssentialEditorSetupProps & {
+ file: FileResponse;
+ loaded?: boolean;
+ }
+>) {
const { provideEditorSnapshot: initialize } =
useWorkspaceInitializerContext();
- // background whole file fetching
- const file = useDesignFile({ file: filekey });
-
// todo background file fetching to task queue
// useEffect(() => {
// const task =
@@ -51,16 +60,7 @@ export function SetupEditor({
// };
// }, [file]);
- const [loading, setLoading] = useState(true);
const [state] = useEditorState();
- const prefDispatch = useDispatch();
-
- const openFpatConfigurationPreference = useCallback(() => {
- prefDispatch({
- type: "open",
- route: "/figma/personal-access-token",
- });
- }, [prefDispatch]);
const initialCanvasMode = q_map_canvas_mode_from_query(
router.query.mode as string
@@ -136,22 +136,103 @@ export function SetupEditor({
);
useEffect(() => {
- if (!loading) {
+ if (file) {
+ initWith(file);
+ }
+ }, [file]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function SetupFigmaCommunityFileEditor({
+ filekey,
+ nodeid,
+ router,
+ children,
+}: React.PropsWithChildren) {
+ const fig = useFigmaCommunityFile({ id: filekey });
+ const [file, setFile] = useState(null);
+ const [loaded, setLoaded] = useState(false);
+
+ useEffect(() => {
+ switch (fig.__type) {
+ case "error": {
+ // TODO: set error here
+ break;
+ }
+ case "loading": {
+ // Do nothing. the community file won't be having loading state though.
+ break;
+ }
+ case "file-fetched-for-app": {
+ // ready.
+ setLoaded(true);
+ setFile(fig);
+ break;
+ }
+ }
+ }, [fig]);
+
+ return (
+
+ FigmaCommunityImageClient()}
+ >
+ {children}
+
+
+ );
+}
+
+export function SetupFigmaFileEditor({
+ filekey,
+ nodeid,
+ router,
+ children,
+}: React.PropsWithChildren) {
+ const [file, setFile] = useState(null);
+ const [loaded, setLoaded] = useState(false);
+
+ // background whole file fetching
+ const fig = useFigmaFile({ file: filekey });
+
+ const prefDispatch = useDispatch();
+
+ const openFpatConfigurationPreference = useCallback(() => {
+ prefDispatch({
+ type: "open",
+ route: "/figma/personal-access-token",
+ });
+ }, [prefDispatch]);
+
+ useEffect(() => {
+ if (loaded) {
return;
}
- if (file.__type === "loading") {
+ if (fig.__type === "loading") {
return;
}
- if (file.__type === "error") {
+ if (fig.__type === "error") {
// handle error by reason
- switch (file.reason) {
+ switch (fig.reason) {
case "unauthorized":
case "no-auth": {
- if (file.cached) {
- initWith(file.cached);
- setLoading(false);
+ if (fig.cached) {
+ setFile(fig.cached);
+ setLoaded(true);
alert(
"You will now see the cached version of this file. To view the latest version, setup your personall access token."
);
@@ -170,22 +251,38 @@ export function SetupEditor({
return;
}
- if (!file.__initial) {
+ if (!fig.__initial) {
// when full file is loaded, allow editor with user interaction.
- setLoading(false);
+ setLoaded(true);
}
- initWith(file);
+ setFile(fig);
}, [
filekey,
- file,
- file.__type == "file-fetched-for-app" ? file.document?.children : null,
+ fig,
+ fig.__type == "file-fetched-for-app" ? fig.document?.children : null,
]);
return (
-
- {children}
-
+
+ {
+ if (!filekey || !authentication) return "reject";
+ return FigmaImageClient({
+ ...authentication,
+ });
+ }}
+ >
+ {children}
+
+
);
}
diff --git a/editor/scaffolds/workspace/warmup.ts b/editor/scaffolds/workspace/warmup.ts
index e4efaa5b1..66722ee95 100644
--- a/editor/scaffolds/workspace/warmup.ts
+++ b/editor/scaffolds/workspace/warmup.ts
@@ -3,23 +3,29 @@ import {
EditorSnapshot,
WorkspaceState,
} from "core/states";
-import { merge_initial_workspace_state_with_editor_snapshot } from "core/states";
+import {
+ merge_initial_workspace_state_with_editor_snapshot,
+ EssentialWorkspaceInfo,
+} from "core/states";
import { workspaceWarmupReducer, workspaceReducer } from "core/reducers";
import { PendingState, PendingState_Pending } from "core/utility-types";
import { WorkspaceAction, WorkspaceWarmupAction } from "core/actions";
-const initial_pending_workspace_state =
- create_initial_pending_workspace_state();
//
export type InitializationAction =
| { type: "warmup"; value: WorkspaceWarmupAction }
| { type: "setup-with-editor-snapshot"; value: EditorSnapshot }
| { type: "update"; value: WorkspaceAction };
+type TState = PendingState & EssentialWorkspaceInfo;
+
export function initialReducer(
- state: PendingState,
+ state: TState,
action: InitializationAction
): PendingState {
+ const initial_pending_workspace_state =
+ create_initial_pending_workspace_state({});
+
switch (action.type) {
case "setup-with-editor-snapshot":
return {
@@ -63,13 +69,16 @@ export function initialReducer(
}
}
-export function safestate(initialState) {
+export function safestate(initialState: TState): WorkspaceState {
+ const initial_pending_workspace_state =
+ create_initial_pending_workspace_state({});
+
switch (initialState.type) {
case "success":
return initialState.value;
case "pending": {
if (initialState.value) {
- return initialState.value;
+ return initialState.value as WorkspaceState;
} else {
return initial_pending_workspace_state;
}
diff --git a/editor/scaffolds/workspace/workspace.tsx b/editor/scaffolds/workspace/workspace.tsx
index eab049cfa..83e7f61c7 100644
--- a/editor/scaffolds/workspace/workspace.tsx
+++ b/editor/scaffolds/workspace/workspace.tsx
@@ -5,7 +5,7 @@ import React, {
createContext,
} from "react";
import { useRouter } from "next/router";
-import { StateProvider } from "core/states";
+import { EssentialWorkspaceInfo, StateProvider } from "core/states";
import { SetupWorkspace } from "./setup";
import { WorkspaceDefaultProviders } from "./_providers";
import * as warmup from "./warmup";
@@ -21,7 +21,10 @@ export function useWorkspaceInitializerContext() {
return useContext(WorkspaceInitializerContext);
}
-export function Workspace({ children }: React.PropsWithChildren<{}>) {
+export function Workspace({
+ children,
+ ...seed
+}: React.PropsWithChildren) {
const router = useRouter();
const handleDispatch = useCallback((action: WorkspaceAction) => {
@@ -46,7 +49,10 @@ export function Workspace({ children }: React.PropsWithChildren<{}>) {
type: "pending",
});
- const safe_value = warmup.safestate(initialState);
+ const safe_value = warmup.safestate({
+ ...initialState,
+ ...seed,
+ });
return (
<>
diff --git a/editor/services/figma-image-service/index.ts b/editor/services/figma-image-service/index.ts
index 48af60119..344c36f33 100644
--- a/editor/services/figma-image-service/index.ts
+++ b/editor/services/figma-image-service/index.ts
@@ -1,21 +1,25 @@
-import type { TargetImage, FigmaImageType, ImageHashMap } from "./types";
+import type { TargetImage, ImageHashMap } from "./types";
import { FigmaNodeImageStore, ImageHashmapCache } from "./figma-image-store";
import { Client } from "@design-sdk/figma-remote-api";
-import { fetchNodeAsImage } from "@design-sdk/figma-remote";
+
+type TClientInterface = ReturnType;
+export type ImageClientInterface = {
+ fileImages: TClientInterface["fileImages"];
+ fileImageFills: TClientInterface["fileImageFills"];
+};
/**
* if the new request is made within 0.1 second (100ms), merge the requests.
*/
const DEBOUNCE = 100;
+const ACTION_UPDATE_IMAGE_HASH_MAP = "update-image-hash-map";
+
/**
* Top level Figma image service to fetch images in a safe, promised and request efficient way.
*/
export class FigmaImageService {
private store = new FigmaNodeImageStore(this.filekey);
- private api = Client({
- ...this.authentication,
- });
private retries: number;
/**
@@ -25,10 +29,11 @@ export class FigmaImageService {
constructor(
readonly filekey: string,
- private readonly authentication: {
- personalAccessToken?: string;
- accessToken?: string;
- },
+ readonly api: ImageClientInterface,
+ // private readonly authentication: {
+ // personalAccessToken?: string;
+ // accessToken?: string;
+ // },
readonly version?: string | null,
readonly maxQueue = 100
) {}
@@ -78,11 +83,11 @@ export class FigmaImageService {
// #endregion
private async updateImageHashMap() {
- if (this.hasTask("update-image-hash-map")) {
- return this.tasks.get("update-image-hash-map");
+ if (this.hasTask(ACTION_UPDATE_IMAGE_HASH_MAP)) {
+ return this.tasks.get(ACTION_UPDATE_IMAGE_HASH_MAP);
} else {
const { data } = await this.pushTask(
- "update-image-hash-map",
+ ACTION_UPDATE_IMAGE_HASH_MAP,
this.api.fileImageFills(this.filekey)
);
@@ -133,25 +138,25 @@ export class FigmaImageService {
}
const tasktargetsmap: {
- bakes: string[];
+ exports: string[];
images: string[];
} = tasktargets.reduce(
(acc, id) => {
if (is_node_id(id)) {
- acc["bakes"].push(id);
+ acc["exports"].push(id);
} else {
acc["images"].push(id);
}
return acc;
},
{
- bakes: [],
+ exports: [],
images: [],
}
);
//
- const { bakes, images } = tasktargetsmap;
+ const { exports, images } = tasktargetsmap;
const tasks: { [key: string]: Promise } = {};
//
@@ -174,11 +179,11 @@ export class FigmaImageService {
});
}
}
- // fetch bakes (handle debounce)
- if (bakes.length > 0) {
+ // fetch exports (handle debounce)
+ if (exports.length > 0) {
if (do_debounce) {
// fetch with merged queues
- this.queue = new Set([...this.queue, ...bakes]);
+ this.queue = new Set([...this.queue, ...exports]);
const handle_queue = async () => {
const fetchtargets = async (...targets) => {
@@ -220,7 +225,7 @@ export class FigmaImageService {
this.timeout = setTimeout(handle_queue, DEBOUNCE);
}
- const promises = bakes.map((b) => {
+ const promises = exports.map((b) => {
const key = b;
const promise = new Promise((resolve) => {
this.queuedResolvers.set(key, resolve);
@@ -236,21 +241,25 @@ export class FigmaImageService {
).reduce((acc, data: string, i) => {
return {
...acc,
- [bakes[i]]: data,
+ [exports[i]]: data,
};
}, {});
resolve({ ...datas, ...results });
});
} else {
- // this does not support format, scale, ... (todo)
- const request = fetchNodeAsImage(
- this.filekey,
- this.authentication,
- ...bakes
- );
-
- for (const t of bakes) {
+ // fetcher promise with data un-wrapping
+ const request = new Promise(async (resolve) => {
+ this.api
+ .fileImages(this.filekey, {
+ ids: exports,
+ })
+ .then(({ data }) => {
+ resolve(data.images);
+ });
+ });
+
+ for (const t of exports) {
tasks[t] = this.pushTask(
t,
new Promise((resolve) => {
diff --git a/editor/services/index.ts b/editor/services/index.ts
index 9de40073f..46897c9fc 100644
--- a/editor/services/index.ts
+++ b/editor/services/index.ts
@@ -1,2 +1,3 @@
export { FigmaImageService } from "./figma-image-service";
+export type { ImageClientInterface } from "./figma-image-service";
export { FigmaCommentsStore, useFigmaComments } from "./figma-comments-service";
diff --git a/testing/report/package.json b/testing/report/package.json
index e0c7f7f5b..6aae49ae6 100644
--- a/testing/report/package.json
+++ b/testing/report/package.json
@@ -18,7 +18,7 @@
"typescript": "^5.0.4"
},
"dependencies": {
- "@figma-api/community": "^0.0.3",
+ "@figma-api/community": "^0.0.5",
"axios-cache-interceptor": "^1.1.1",
"minimist": "^1.2.8",
"ora": "^5.4.0"
diff --git a/yarn.lock b/yarn.lock
index e8d0da874..e5206ab95 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2374,10 +2374,10 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
-"@figma-api/community@^0.0.3":
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/@figma-api/community/-/community-0.0.3.tgz#f0556beb72722a2051e70901e4b36661575e4028"
- integrity sha512-F3SV7wTrVGQ+hBH/Wk8nmy96ZZ3rFqGhycEO8LCZZKf0LHQL84eH/DHe/P+mgZCBEIHQXUtWu3K5TwSiF+ycVw==
+"@figma-api/community@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@figma-api/community/-/community-0.0.5.tgz#bb747f8881e199ab9d28f53cfcd14af676a1d02a"
+ integrity sha512-cjr13Ku0kZypbCmcBIRMZAHTnMjQ+vfodoDDxAOreGNjaRA4jAoMILZImzVy8EmbcJccch+bHwsVrK/ibWacqg==
dependencies:
mime-types "^2.1.35"