Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"crypto-js": "^4.1.1",
"formik": "^2.2.9",
"graphql": "^16.6.0",
"kbar": "^0.1.0-beta.40",
"monaco-editor": "^0.34.1",
"monaco-editor-webpack-plugin": "^7.0.1",
"nanoid-dictionary": "^4.3.0",
Expand Down
1 change: 0 additions & 1 deletion ui/src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ function store2nodes(id: string, { getId2children, getPod }) {
// position: { x: 100, y: 100 },
position: { x: pod.x, y: pod.y },
parentNode: pod.parent !== "ROOT" ? pod.parent : undefined,
extent: pod.parent !== "ROOT" ? "parent" : undefined,
style: {
width: pod.width || undefined,
height: pod.height || undefined,
Expand Down
79 changes: 79 additions & 0 deletions ui/src/components/MyKBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
KBarProvider,
KBarPortal,
KBarPositioner,
KBarAnimator,
KBarSearch,
KBarResults,
useMatches,
NO_GROUP,
} from "kbar";

import { useStore } from "zustand";
import { RepoContext } from "../lib/store";
import { useContext } from "react";

function RenderResults() {
const { results } = useMatches();

return (
<KBarResults
items={results}
onRender={({ item, active }) =>
typeof item === "string" ? (
<div>{item}</div>
) : (
<div
style={{
background: active ? "#eee" : "transparent",
}}
>
{item.name}
</div>
)
}
/>
);
}

export function MyKBar() {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const autoLayout = useStore(store, (state) => state.autoLayout);
const actions = [
{
id: "auto-layout",
name: "Auto Layout",
keywords: "auto layout",
perform: () => {
autoLayout();
},
},
// {
// id: "blog",
// name: "Blog",
// shortcut: ["b"],
// keywords: "writing words",
// perform: () => (window.location.pathname = "blog"),
// },
// {
// id: "contact",
// name: "Contact",
// shortcut: ["c"],
// keywords: "email",
// perform: () => (window.location.pathname = "contact"),
// },
];
return (
<KBarProvider actions={actions}>
<KBarPortal>
<KBarPositioner>
<KBarAnimator>
<KBarSearch />
<RenderResults />
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
</KBarProvider>
);
}
2 changes: 2 additions & 0 deletions ui/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useSnackbar, VariantType } from "notistack";

import { gql, useQuery, useMutation, useApolloClient } from "@apollo/client";
import { useStore } from "zustand";
import { MyKBar } from "./MyKBar";

import { usePrompt } from "../lib/prompt";

Expand Down Expand Up @@ -468,6 +469,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
const isGuest = useStore(store, (state) => state.role === "GUEST");
return (
<>
<MyKBar />
<Box
sx={{
position: "absolute",
Expand Down
125 changes: 115 additions & 10 deletions ui/src/lib/store/canvasSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ function createTemporaryNode(pod, position, parent = "ROOT", level = 0): any {
parent,
level,
},
extent: level > 0 ? "parent" : undefined,
dragHandle: ".custom-drag-handle",
width: pod.width,
height: pod.height!,
Expand Down Expand Up @@ -177,13 +176,6 @@ function getNodePositionInsideScope(
let [dx, dy] = getAbsPos(scope, nodesMap);
x -= dx;
y -= dy;
// auto-align the node to, keep it bound in the scope
// FIXME: it assumes the scope must be larger than the node
x = Math.max(x, 0);
x = Math.min(x, scope.width! - node.width!);
y = Math.max(y, 0);
// FIXME: node.height can be undefined
y = Math.min(y, scope.height! - nodeHeight);
return { x, y };
}

Expand Down Expand Up @@ -282,6 +274,8 @@ export interface CanvasSlice {
onNodesChange: (client: ApolloClient<any>) => OnNodesChange;
onEdgesChange: (client: ApolloClient<any>) => OnEdgesChange;
onConnect: (client: ApolloClient<any>) => OnConnect;

autoLayout: () => void;
}

export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
Expand Down Expand Up @@ -592,7 +586,6 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
let newNode: Node = {
...node,
parentNode: undefined,
extent: undefined,
data: {
...node.data,
level: 0,
Expand Down Expand Up @@ -632,7 +625,6 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
...node,
position,
parentNode: scope.id,
extent: "parent",
data: {
...node.data,
level: scope.data.level + 1,
Expand Down Expand Up @@ -816,8 +808,121 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
},
setPaneFocus: () => set({ isPaneFocused: true }),
setPaneBlur: () => set({ isPaneFocused: false }),
autoLayout: () => {
// Auto layout the nodes.
// Find all the scope nodes, and change its width and height to fit its children nodes.
let nodesMap = get().ydoc.getMap<Node>("pods");
let nodes: Node[] = Array.from(nodesMap.values());
let node2children = new Map<string, string[]>();
nodes.forEach((node) => {
if (!node2children.has(node.id)) {
node2children.set(node.id, []);
}
if (node.parentNode) {
if (!node2children.has(node.parentNode)) {
node2children.set(node.parentNode, []);
}
node2children.get(node.parentNode)?.push(node.id);
}
});
// fit the children.
nodes
// sort the children so that the inner scope gets processed first.
.sort((a: Node, b: Node) => b.data.level - a.data.level)
.forEach((node) => {
let newSize = fitChildren(node, node2children, nodesMap);
if (newSize === null) return;
let { x, y, width, height } = newSize;
let newNode = {
...node,
position: {
x: node.position.x + x,
y: node.position.y + y,
},
width,
height,
style: {
...node.style,
width,
height,
},
};
nodesMap.set(node.id, newNode);
// I actually need to set the children's position as well, because they are relative to the parent.
let children = node2children.get(node.id);
children?.forEach((child) => {
let n = nodesMap.get(child)!;
let newChild = {
...n,
position: {
x: n.position.x - x,
y: n.position.y - y,
},
};
nodesMap.set(child, newChild);
});
});
// trigger update to the db
// get the most recent nodes
nodes = Array.from(nodesMap.values());
nodes.forEach((node) => {
// trigger update to the DB
let geoData = {
parent: node.parentNode ? node.parentNode : "ROOT",
x: node.position.x,
y: node.position.y,
width: node.width!,
height: node.height!,
};
get().setPodGeo(node.id, geoData, true);
});
// update the view
get().updateView();
},
});

/**
* Compute the rectangle of that is tightly fit to the children.
* @param node
* @param node2children
* @param nodesMap
* @returns {x,y,width,height}
*/
function fitChildren(
node: Node,
node2children,
nodesMap
): null | { x: number; y: number; width: number; height: number } {
if (node.type !== "scope") return null;
// This is a scope node. Get all its children and calculate the x,y,width,height to tightly fit its children.
let children = node2children.get(node.id);
// If no children, nothing is changed.
if (children.length === 0) return null;
// These positions are actually relative to the parent node.
let x1s = children.map((child) => nodesMap.get(child)!.position.x);
let minx = Math.min(...x1s);
let y1s = children.map((child) => nodesMap.get(child)!.position.y);
let miny = Math.min(...y1s);
let x2s = children.map((child) => {
let n = nodesMap.get(child)!;
return n.position.x + n.width!;
});
let maxx = Math.max(...x2s);
let y2s = children.map((child) => {
let n = nodesMap.get(child)!;
return n.position.y + n.height!;
});
let maxy = Math.max(...y2s);
let width = maxx - minx;
let height = maxy - miny;
return {
x: minx - 10,
y: miny - 10,
width: width + 20,
height: height + 20,
};
}

async function remoteAddEdge({ client, source, target }) {
const mutation = gql`
mutation addEdge($source: ID!, $target: ID!) {
Expand Down
69 changes: 69 additions & 0 deletions ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2046,6 +2046,37 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==

"@radix-ui/react-compose-refs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz#37595b1f16ec7f228d698590e78eeed18ff218ae"
integrity sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==
dependencies:
"@babel/runtime" "^7.13.10"

"@radix-ui/react-portal@^1.0.1":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.2.tgz#102370b1027a767a371cab0243be4bc664f72330"
integrity sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.2"

"@radix-ui/react-primitive@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz#54e22f49ca59ba88d8143090276d50b93f8a7053"
integrity sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.1"

"@radix-ui/react-slot@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz#e7868c669c974d649070e9ecbec0b367ee0b4d81"
integrity sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"

"@reach/auto-id@^0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.16.0.tgz#dfabc3227844e8c04f8e6e45203a8e14a8edbaed"
Expand All @@ -2054,6 +2085,11 @@
"@reach/utils" "0.16.0"
tslib "^2.3.0"

"@reach/observe-rect@^1.1.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==

"@reach/utils@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.16.0.tgz#5b0777cf16a7cab1ddd4728d5d02762df0ba84ce"
Expand Down Expand Up @@ -5164,6 +5200,11 @@ comma-separated-tokens@^1.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==

command-score@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381"
integrity sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==

commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
Expand Down Expand Up @@ -6562,6 +6603,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==

fast-equals@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927"
integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==

fast-glob@^3.2.11, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
Expand Down Expand Up @@ -8332,6 +8378,17 @@ jsx-dom-cjs@^8.0.0:
dependencies:
csstype "^3.1.0"

kbar@^0.1.0-beta.40:
version "0.1.0-beta.40"
resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.40.tgz#89747e3c1538375fef779af986b6614bb441ae7c"
integrity sha512-vEV02WuEBvKaSivO2DnNtyd3gUAbruYrZCax5fXcLcVTFV6q0/w6Ew3z6Qy+AqXxbZdWguwQ3POIwgdHevp+6A==
dependencies:
"@radix-ui/react-portal" "^1.0.1"
command-score "^0.1.2"
fast-equals "^2.0.3"
react-virtual "^2.8.2"
tiny-invariant "^1.2.0"

kind-of@^6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
Expand Down Expand Up @@ -10488,6 +10545,13 @@ react-use@^17.3.2:
ts-easing "^0.2.0"
tslib "^2.1.0"

react-virtual@^2.8.2:
version "2.10.4"
resolved "https://registry.yarnpkg.com/react-virtual/-/react-virtual-2.10.4.tgz#08712f0acd79d7d6f7c4726f05651a13b24d8704"
integrity sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==
dependencies:
"@reach/observe-rect" "^1.1.0"

react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Expand Down Expand Up @@ -11715,6 +11779,11 @@ tiny-invariant@1.0.6:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"
integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==

tiny-invariant@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==

tiny-warning@^1.0.2, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
Expand Down