Skip to content

Commit

Permalink
package tree generation and load into showcase
Browse files Browse the repository at this point in the history
  • Loading branch information
heswell committed Nov 10, 2024
1 parent bd0ddab commit 93e887b
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 85 deletions.
27 changes: 27 additions & 0 deletions vuu-ui/tools/showcase-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Showcase CLI

## startup sequence

#### cli.mjs

- load index.html from templates
- get config file from input args, default to 'showcase.config.json'
- check config file exists
- check ./.showcase folder exists
if not - create ./.showcase - copy index.html to .showcase
if ../dist folder exists (this will be part of published package) - copy build files from ../dist to .showcase
- read config json from config file
- validate that directory exists at `config.exhibits`
- build exhibit structure from files at `config.exhibits`
- write the packageTree structure out to .showcase
- call (main.ts).start(config)

## Next steps

- once package tree is created, creat import maps
- inject importmaps into index.html
- update Tree to allow runtime node expansion
- update showcase-standalone

- integrate into vuu cli, using stricli. Parameter parsing will happen there
- use jumpgen to monitor file system for new/edited exhibits in dev mode
55 changes: 36 additions & 19 deletions vuu-ui/tools/showcase-cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,63 @@ import {
} from "./cli/cli-utils.ts";
import indexHtml from "./templates/index.html.ts";
import { fileURLToPath } from "url";
import start from "./cli/main.ts";
import { buildPackageTree } from "./cli/buildPackageTree";

/** Parse the command line */
var args = process.argv.slice(2);

// Validate input
if (args.length !== 1) {
console.log("Warning: Requires 1 argument");
console.log("node config-path");
process.exit();
}
let configFilePath = "./showcase.config.json";

const configPath = args[0];

// const dirsrc = path.dirname(configPath);
if (!fs.existsSync(configPath)) {
console.log("Error: Config file doesn't exist. Given: ", configPath);
process.exit();
// Validate input
if (args.length === 0) {
if (!fs.existsSync(configFilePath)) {
console.log("Warning: Requires 1 argument, path to config file. ");
process.exit();
} else {
console.log("using config file at './showcase.config.json'");
}
} else {
if (fs.existsSync(args[0])) {
configFilePath = args[0];
} else {
console.log(
`Warning: first argument ${args[0]} should be path to config file, file not found`,
);
process.exit();
}
}

const templateDir = path.resolve(fileURLToPath(import.meta.url), "../dist");
const distFolder = path.resolve(fileURLToPath(import.meta.url), "../dist");

if (!fs.existsSync(".showcase")) {
createFolder(".showcase");
// DOn't do this until we create importmaps
await writeFile(indexHtml, "./.showcase/index.html");
} else {
console.log(".showcase folder present and correct");
}

if (fs.existsSync(templateDir)) {
copyFiles(templateDir, "./.showcase");
// TODO check whether dist files already present in .showcase
if (fs.existsSync(distFolder)) {
copyFiles(distFolder, "./.showcase");
}

const config = readJson(configPath);
const config = readJson(configFilePath);

//TODO use type validator to check config file
const { exhibits } = config;
if (!fs.existsSync(exhibits)) {
console.log("Error: Exhibits location doesn't exist. Given: ", exhibits);
process.exit();
}

import("./cli/main.ts").then(({ default: start }) => {
start(config);
});
const stories = buildPackageTree(exhibits);
await writeFile(
`export default ${JSON.stringify(stories, null, 2)};`,
"./.showcase/exhibits.js",
);

console.log(JSON.stringify(stories, null, 2));

start(config);
2 changes: 1 addition & 1 deletion vuu-ui/tools/showcase-cli/cli/buildPackageTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const buildPackageTree = (dir: string, tree = {}) => {
}
} else if (fileName.match(/(examples.tsx|.mdx)$/)) {
const [storyName] = fileName.split(".");
tree[storyName] = `${dir}${fileName}`;
tree[storyName] = fileName;
}
});
return tree;
Expand Down
6 changes: 0 additions & 6 deletions vuu-ui/tools/showcase-cli/cli/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createServer } from "vite";
import { HttpServerConfig, startHTTPServer } from "./http-server";
import { buildPackageTree } from "./buildPackageTree";

export type ShowcaseConfig = {
exhibits: string;
Expand All @@ -9,11 +8,6 @@ export type ShowcaseConfig = {
};

export default async (config: ShowcaseConfig) => {
const start = performance.now();
const stories = buildPackageTree(config.exhibits);
const end = performance.now();
console.log(`building exhibits menu took ${end - start}ms`);
console.log(JSON.stringify(stories, null, 2));
// fs.writeFile(OUT, JSON.stringify(stories, null, 2), (err) => {
// if (err) {
// console.log(err);
Expand Down
47 changes: 11 additions & 36 deletions vuu-ui/tools/showcase-cli/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { Flexbox } from "@finos/vuu-layout";
import { Tree, TreeSourceNode } from "@finos/vuu-ui-controls";
import { Tree } from "@finos/vuu-ui-controls";
import { Density, ThemeMode } from "@finos/vuu-utils";
import {
Button,
Expand All @@ -9,42 +10,17 @@ import {
ToggleButtonGroup,
} from "@salt-ds/core";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
import { IFrame } from "./components";
import { byDisplaySequence, ExamplesModule, loadTheme } from "./showcase-utils";

import { loadTheme } from "./showcase-utils";
import type { ExhibitsJson } from "./exhibit-utils";
import { ThemeSwitch } from "@finos/vuu-shell";

import "./App.css";
import { useShowcaseApp } from "./useShowcaseApp";

const sourceFromImports = (
stories: ExamplesModule,
prefix = "",
icon = "folder",
): TreeSourceNode[] =>
Object.entries(stories)
.filter(([path]) => path !== "default")
.sort(byDisplaySequence)
.map<TreeSourceNode>(([label, stories]) => {
const id = `${prefix}${label}`;
// TODO how can we know when a potential docs node has docs
// console.log(`id=${id}`);
if (typeof stories === "function") {
return {
id,
icon: "rings",
label,
};
}
return {
id,
icon,
label,
childNodes: sourceFromImports(stories, `${id}/`, "box"),
};
});
export interface AppProps {
stories: ExamplesModule;
exhibits: ExhibitsJson;
}

type ThemeDescriptor = { label?: string; id: string };
Expand All @@ -70,20 +46,19 @@ const availableDensity: DensityDescriptor[] = [
{ id: "touch", label: "Touch" },
];

export const App = ({ stories }: AppProps) => {
const navigate = useNavigate();
export const App = ({ exhibits }: AppProps) => {
const [themeReady, setThemeReady] = useState(false);

const { onSelectionChange, source } = useShowcaseApp({ exhibits });

useEffect(() => {
loadTheme("vuu-theme").then(() => {
setThemeReady(true);
});
}, []);

// // TODO cache source in localStorage
const source = useMemo(() => sourceFromImports(stories), [stories]);
const { pathname } = useLocation();
const handleChange = ([selected]: TreeSourceNode[]) => navigate(selected.id);
const [themeIndex, setThemeIndex] = useState(2);
const [themeModeIndex, setThemeModeIndex] = useState(0);
const [densityIndex, setDensityIndex] = useState(0);
Expand Down Expand Up @@ -134,7 +109,7 @@ export const App = ({ stories }: AppProps) => {
style={{ flex: "0 0 200px" }}
data-resizeable
selected={[pathname.slice(1)]}
onSelectionChange={handleChange}
onSelectionChange={onSelectionChange}
revealSelected
source={source}
/>
Expand Down
14 changes: 8 additions & 6 deletions vuu-ui/tools/showcase-cli/src/Showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import "./Showcase.css";

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { ExamplesModule } from "./showcase-utils";
import { App } from "./App";
import { ExamplesModule } from "./showcase-utils";

import "./Showcase.css";
import { ExhibitsJson } from "./exhibit-utils";

const createRoutes = (examples: ExamplesModule, prefix = ""): JSX.Element[] =>
const createRoutes = (examples: ExhibitsJson, prefix = ""): JSX.Element[] =>
Object.entries(examples)
.filter(([path]) => path !== "default")
.reduce<JSX.Element[]>((routes, [label, Value]) => {
Expand All @@ -16,11 +18,11 @@ const createRoutes = (examples: ExamplesModule, prefix = ""): JSX.Element[] =>
: routes.concat(<Route key={label} path={id} />);
}, []);

export const Showcase = ({ exhibits }: { exhibits: ExamplesModule }) => {
export const Showcase = ({ exhibits }: { exhibits: ExhibitsJson }) => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<App stories={exhibits} />}>
<Route path="/" element={<App exhibits={exhibits} />}>
{createRoutes(exhibits)}
</Route>
</Routes>
Expand Down
3 changes: 3 additions & 0 deletions vuu-ui/tools/showcase-cli/src/exhibit-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ExhibitsJson {
[key: string]: string | ExhibitsJson;
}
11 changes: 7 additions & 4 deletions vuu-ui/tools/showcase-cli/src/index-main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import ReactDOM from "react-dom";
import React from "react";
import { Showcase } from "./Showcase";
import { ExhibitsJson } from "./exhibit-utils";

const root = document.getElementById("root") as HTMLDivElement;
// The full Showcase shell loads all examples in order to render the Navigation Tree. This can
// be a bit slow in dev mode.
ReactDOM.render(<Showcase exhibits={{}} />, root);
export default (exhibits: ExhibitsJson) => {
const root = document.getElementById("root") as HTMLDivElement;
// The full Showcase shell loads all examples in order to render the Navigation Tree. This can
// be a bit slow in dev mode.
ReactDOM.render(<Showcase exhibits={exhibits} />, root);
};
7 changes: 4 additions & 3 deletions vuu-ui/tools/showcase-cli/src/index-standalone.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ShowcaseStandalone } from "@finos/vuu-showcase";
import ReactDOM from "react-dom";

const root = document.getElementById("root") as HTMLDivElement;

ReactDOM.render(<ShowcaseStandalone />, root);
export default (exhibits: unknown) => {
const root = document.getElementById("root") as HTMLDivElement;
ReactDOM.render(<ShowcaseStandalone />, root);
};
19 changes: 14 additions & 5 deletions vuu-ui/tools/showcase-cli/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { hasUrlParameter } from "@finos/vuu-utils";
if (hasUrlParameter("standalone")) {
import("./index-standalone");
} else {
import("./index-main");
}
import { ExhibitsJson } from "./exhibit-utils";

export default async (exhibits: ExhibitsJson) => {
console.log("Showcase start", {
exhibits,
});
if (hasUrlParameter("standalone")) {
const { default: start } = await import("./index-standalone");
start(exhibits);
} else {
const { default: start } = await import("./index-main");
start(exhibits);
}
};
69 changes: 69 additions & 0 deletions vuu-ui/tools/showcase-cli/src/useShowcaseApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { TreeSourceNode } from "@finos/vuu-ui-controls";
import { useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { AppProps } from "./App";
import { ExhibitsJson } from "./exhibit-utils";

const sourceFromImports = (
exhibits: ExhibitsJson,
prefix = "",
icon = "folder",
): TreeSourceNode[] =>
Object.entries(exhibits).map<TreeSourceNode>(([label, exhibits]) => {
const id = `${prefix}${label}`;
if (typeof exhibits === "string") {
return {
id,
icon: "rings",
label,
};
}
return {
id,
icon,
label,
childNodes: sourceFromImports(exhibits, `${id}/`, "box"),
};
});

const getTargetExhibit = (exhibits: ExhibitsJson, path: string) => {
const steps = path.split("/");
const root = steps.slice(0, -1).join("/");

let node: string | ExhibitsJson = exhibits;
let pathRoot: string[] = [];
while (steps.length) {
const step = steps.shift() as string;
node = node[step];
}
if (typeof node === "string") {
return `${root}/${node}`;
} else {
throw Error(`unexpected leaf node ${JSON.stringify(node)}`);
}
};

export const useShowcaseApp = ({ exhibits }: AppProps) => {
const navigate = useNavigate();

const source = useMemo(() => sourceFromImports(exhibits), [exhibits]);

const handleChange = async ([selected]: TreeSourceNode[]) => {
console.log(JSON.stringify(selected, null, 2));

const sourceTarget = getTargetExhibit(exhibits, selected.id);
if (sourceTarget?.endsWith(".tsx")) {
const module = await import(
/* @vite-ignore */
`exhibits:src/examples/${sourceTarget}`
);
console.log(module);
}
// navigate(selected.id);
};

return {
onSelectionChange: handleChange,
source,
};
};
Loading

0 comments on commit 93e887b

Please sign in to comment.