Skip to content

Commit e95edb8

Browse files
committed
Merge branch 'main' into feat/auto-complete
2 parents 51532f7 + 055e174 commit e95edb8

File tree

9 files changed

+335
-159
lines changed

9 files changed

+335
-159
lines changed

README.md

Lines changed: 17 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,25 @@ overwhelmed by the great number of code snippets as the projects grow.
1616
# Contributing
1717

1818
CodePod is open-source under an MIT license. Feel free to contribute to make
19-
it better together with us. You can contribute by [creating awesome showcases](#gallery),
20-
[reporting a bug, suggesting a feature](https://github.com/codepod-io/codepod/issues),
21-
or submitting a pull request.
19+
it better together with us. You can contribute by [creating awesome showcases](#gallery),
20+
[reporting a bug, suggesting a feature](https://github.com/codepod-io/codepod/issues),
21+
or submitting a pull request.
2222
Do use [Prettier](https://prettier.io/) (e.g., [its VSCode
2323
plugin](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode))
24-
to format your code before checking in.
25-
Last but not least, give us a star on Github!
24+
to format your code before checking in.
25+
Last but not least, give us a star on Github!
26+
27+
# Run CodePod Locally
28+
29+
Please refer to the [developer manual](https://codepod.io/docs/developer) for how to run CodePod locally.
30+
31+
# Gallery
32+
33+
Thanks to our community, we now have CodePod showcases ranging from analytical geometry to bioinformatics.
34+
35+
- [plotting common functions](https://app.codepod.io/repo/2ncnioylo9abo3otdxjs)
36+
- [image operations using skimage](https://user-images.githubusercontent.com/44469195/239033643-decbd7ae-29bb-44b9-af33-d4cb7c2bce46.png)
37+
- [tel-siRNA sequence detector](https://app.codepod.io/repo/b94n7n00a9395xwhv1o8)
2638

2739
# Citation
2840

@@ -39,87 +51,3 @@ https://arxiv.org/abs/2301.02410
3951
copyright = {Creative Commons Attribution 4.0 International}
4052
}
4153
```
42-
43-
# Gallery
44-
45-
Thanks to our community, we now have CodePod showcases ranging from analytical geometry to bioinformatics.
46-
47-
* [plotting common functions](https://app.codepod.io/repo/2ncnioylo9abo3otdxjs)
48-
* [image operations using skimage](https://user-images.githubusercontent.com/44469195/239033643-decbd7ae-29bb-44b9-af33-d4cb7c2bce46.png)
49-
* [tel-siRNA sequence detector](https://app.codepod.io/repo/b94n7n00a9395xwhv1o8)
50-
51-
# Developing CodePod using docker-compose
52-
53-
The docker compose files are in `compose/dev` folder. The `dev` stack mounts the
54-
`src` folder, so that you can edit the files on your local computer, and let the
55-
node.js process inside the container do the compiling and hot-reloading.
56-
57-
To install docker-compose, follow the official [Docker documentation](https://docs.docker.com/compose/install/linux/).
58-
59-
## .env file
60-
61-
First, create a `dev/.env` file with the following content (leave as is or change the value to
62-
whatever you want).
63-
64-
```properties
65-
POSTGRES_USER=myusername
66-
POSTGRES_PASSWORD=mypassword
67-
POSTGRES_DB=mydbname
68-
JWT_SECRET=mysupersecretjwttoken
69-
70-
GOOGLE_CLIENT_ID=<google oauth client id>
71-
72-
EXPORT_AWS_S3_REGION=us-west-1
73-
EXPORT_AWS_S3_BUCKET=<YOUR_BUCKET_NAME>
74-
EXPORT_AWS_S3_ACCESS_KEY_ID=<YOUR_ACCESS_KEY>
75-
EXPORT_AWS_S3_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
76-
```
77-
78-
Optional:
79-
80-
- Leave the `GOOGLE_CLIENT_ID` empty if you do not need the OAuth provided by Google.
81-
- `EXPORT_AWS_S3_XXX` are used for file export. You could leave it empty if you don't use it.
82-
83-
## Start the stack
84-
85-
```bash
86-
cd dev
87-
docker compose up -d
88-
```
89-
90-
You need to initialized the database first before starting the stack. See below.
91-
92-
Wait a few minutes for the package installation and compilation. Once the `ui` and
93-
`api` containers are ready, go to `http://localhost:80` to see the app.
94-
95-
- `http://localhost:80/graphql`: Apollo GraphQL explorer for the backend APIs
96-
- `http://prisma.127.0.0.1.sslip.io`: Prisma Studio for viewing and debugging the database.
97-
98-
## Initialize the database
99-
100-
If this is your first time running it, you would need to initialize the database as it's empty. To do that, open a shell into the API container and run:
101-
102-
```bash
103-
npx prisma migrate dev
104-
```
105-
106-
This command is also needed after the database schema is changed. The protocol is:
107-
108-
- One developer changed [the schema](./api/prisma/schema.prisma). He will run
109-
`npx prisma migrate dev --name add_a_new_field`. This will generate a
110-
migration, e.g. [this
111-
migration](./api/prisma/migrations/20221206194247_add_google_login/migration.sql).
112-
The schema change along with this migration need to be checked in to git.
113-
- Another developer pulls the change, then running the `npx prisma migrate dev` (in the api container's shell) to apply the schema change.
114-
115-
## Auto-completion & Linting
116-
117-
Although we developed this project using docker, we still want features like auto-completion and linting while coding. For that to work, you need to install the all the relevant node packages, i.e.
118-
119-
```bash
120-
# api, proxy, runtime, ui
121-
cd ./api/
122-
123-
# Run 'npm install' instead if you are using npm
124-
yarn
125-
```

k8s/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is deprecated. The k8s setup is moved to a private repo https://github.com/codepod-io/codepod-k8s.

ui/src/components/Canvas.tsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import ReactFlow, {
2424
ReactFlowProvider,
2525
Edge,
2626
useViewport,
27+
XYPosition,
2728
} from "reactflow";
2829
import "reactflow/dist/style.css";
2930

@@ -47,6 +48,7 @@ import { YMap } from "yjs/dist/src/types/YMap";
4748
import FloatingEdge from "./nodes/FloatingEdge";
4849
import CustomConnectionLine from "./nodes/CustomConnectionLine";
4950
import HelperLines from "./HelperLines";
51+
import { getAbsPos } from "../lib/store/canvasSlice";
5052

5153
const nodeTypes = { SCOPE: ScopeNode, CODE: CodeNode, RICH: RichNode };
5254
const edgeTypes = {
@@ -273,6 +275,152 @@ function useInitNodes() {
273275
return { loading };
274276
}
275277

278+
function getBestNode(
279+
nodes: Node[],
280+
from,
281+
direction: "up" | "down" | "left" | "right"
282+
) {
283+
// find the best node to jump to from (x,y) in the given direction
284+
let bestNode: Node | null = null;
285+
let bestDistance = Infinity;
286+
nodes = nodes.filter((node) => {
287+
switch (direction) {
288+
case "up":
289+
return (
290+
node.position.y + node.height! / 2 <
291+
from.position.y + from.height! / 2
292+
);
293+
case "down":
294+
return (
295+
node.position.y + node.height! / 2 >
296+
from.position.y + from.height! / 2
297+
);
298+
case "left":
299+
return (
300+
node.position.x + node.width! / 2 < from.position.x + from.width! / 2
301+
);
302+
case "right":
303+
return (
304+
node.position.x + node.width! / 2 > from.position.x + from.width! / 2
305+
);
306+
}
307+
});
308+
for (let node of nodes) {
309+
// I should start from the edge, instead of the center
310+
const startPoint: XYPosition = (() => {
311+
// the center
312+
// return {
313+
// x: from.position.x + from.width! / 2,
314+
// y: from.position.y + from.height! / 2,
315+
// };
316+
// the edge depending on direction.
317+
switch (direction) {
318+
case "up":
319+
return {
320+
x: from.position.x + from.width! / 2,
321+
y: from.position.y,
322+
};
323+
case "down":
324+
return {
325+
x: from.position.x + from.width! / 2,
326+
y: from.position.y + from.height!,
327+
};
328+
case "left":
329+
return {
330+
x: from.position.x,
331+
y: from.position.y + from.height! / 2,
332+
};
333+
case "right":
334+
return {
335+
x: from.position.x + from.width!,
336+
y: from.position.y + from.height! / 2,
337+
};
338+
}
339+
})();
340+
let distance =
341+
Math.pow(node.position.x + node.width! / 2 - startPoint.x, 2) *
342+
(["left", "right"].includes(direction) ? 1 : 2) +
343+
Math.pow(node.position.y + node.height! / 2 - startPoint.y, 2) *
344+
(["up", "down"].includes(direction) ? 1 : 2);
345+
if (distance < bestDistance) {
346+
bestDistance = distance;
347+
bestNode = node;
348+
}
349+
}
350+
return bestNode;
351+
}
352+
353+
function useJump() {
354+
const store = useContext(RepoContext)!;
355+
356+
const selectPod = useStore(store, (state) => state.selectPod);
357+
const resetSelection = useStore(store, (state) => state.resetSelection);
358+
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
359+
360+
const reactflow = useReactFlow();
361+
362+
const selectedPods = useStore(store, (state) => state.selectedPods);
363+
const handleKeyDown = (event) => {
364+
const id = selectedPods.values().next().value; // Assuming only one node can be selected at a time
365+
if (!id) {
366+
console.log("No node selected");
367+
return; // Ignore arrow key presses if there's no selected node or if the user is typing in an input field
368+
}
369+
const pod = nodesMap.get(id);
370+
if (!pod) {
371+
console.log("pod is undefined");
372+
return;
373+
}
374+
375+
// get the sibling nodes
376+
const nodes = Array.from(nodesMap.values()).filter(
377+
(node) => node.parentNode === pod.parentNode
378+
);
379+
380+
let to: null | Node = null;
381+
382+
switch (event.key) {
383+
case "ArrowUp":
384+
to = getBestNode(nodes, pod, "up");
385+
break;
386+
case "ArrowDown":
387+
to = getBestNode(nodes, pod, "down");
388+
break;
389+
case "ArrowLeft":
390+
to = getBestNode(nodes, pod, "left");
391+
break;
392+
case "ArrowRight":
393+
to = getBestNode(nodes, pod, "right");
394+
break;
395+
default:
396+
return;
397+
}
398+
399+
if (to) {
400+
// set the to node as selected
401+
resetSelection();
402+
selectPod(to.id, true);
403+
// move the viewport to the to node
404+
// get the absolute position of the to node
405+
const pos = getAbsPos(to, nodesMap);
406+
407+
reactflow.setCenter(pos.x + to.width! / 2, pos.y + to.height! / 2, {
408+
zoom: reactflow.getZoom(),
409+
duration: 800,
410+
});
411+
}
412+
413+
event.preventDefault(); // Prevent default browser behavior for arrow keys
414+
};
415+
416+
useEffect(() => {
417+
window.addEventListener("keydown", handleKeyDown);
418+
return () => {
419+
window.removeEventListener("keydown", handleKeyDown);
420+
};
421+
}, [selectedPods]);
422+
}
423+
276424
function usePaste(reactFlowWrapper) {
277425
const store = useContext(RepoContext);
278426
if (!store) throw new Error("Missing BearContext.Provider in the tree");
@@ -461,6 +609,7 @@ function CanvasImplWrap() {
461609
useEdgesYjsObserver();
462610
usePaste(reactFlowWrapper);
463611
useCut(reactFlowWrapper);
612+
useJump();
464613

465614
const { loading } = useInitNodes();
466615
if (loading) return <div>Loading...</div>;
@@ -677,6 +826,8 @@ function CanvasImpl() {
677826
multiSelectionKeyCode={isMac ? "Meta" : "Control"}
678827
// TODO restore previous viewport
679828
defaultViewport={{ zoom: 1, x: 0, y: 0 }}
829+
proOptions={{ hideAttribution: true }}
830+
disableKeyboardA11y={true}
680831
>
681832
<Box>
682833
<MiniMap

ui/src/components/CanvasContextMenu.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,6 @@ export function CanvasContextMenu(props) {
7878
<ListItemText>New Scope</ListItemText>
7979
</MenuItem>
8080
)}
81-
<MenuItem onClick={flipShowLineNumbers} sx={ItemStyle}>
82-
<ListItemIcon sx={{ color: "inherit" }}>
83-
<FormatListNumberedIcon />
84-
</ListItemIcon>
85-
<ListItemText>
86-
{showLineNumbers ? "Hide " : "Show "} Line Numbers
87-
</ListItemText>
88-
</MenuItem>
8981
<MenuItem onClick={() => flipAutoCompletion(client)} sx={ItemStyle}>
9082
<ListItemIcon sx={{ color: "inherit" }}>
9183
<AutoFixHighIcon />

ui/src/components/Sidebar.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ function SidebarSettings() {
4949
);
5050
const devMode = useStore(store, (state) => state.devMode);
5151
const setDevMode = useStore(store, (state) => state.setDevMode);
52+
const showLineNumbers = useStore(store, (state) => state.showLineNumbers);
53+
const setShowLineNumbers = useStore(
54+
store,
55+
(state) => state.setShowLineNumbers
56+
);
5257
const autoRunLayout = useStore(store, (state) => state.autoRunLayout);
5358
const setAutoRunLayout = useStore(store, (state) => state.setAutoRunLayout);
5459
const contextualZoom = useStore(store, (state) => state.contextualZoom);
@@ -59,9 +64,27 @@ function SidebarSettings() {
5964
(state) => state.flipAutoCompletion
6065
);
6166
const client = useApolloClient();
67+
const autoLayoutROOT = useStore(store, (state) => state.autoLayoutROOT);
6268
return (
6369
<Box>
6470
<Box>
71+
<Tooltip title={"Show Line Numbers"} disableInteractive>
72+
<FormGroup>
73+
<FormControlLabel
74+
control={
75+
<Switch
76+
checked={showLineNumbers}
77+
size="small"
78+
color="warning"
79+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
80+
setShowLineNumbers(event.target.checked);
81+
}}
82+
/>
83+
}
84+
label="Show Line Numbers"
85+
/>
86+
</FormGroup>
87+
</Tooltip>
6588
<Tooltip
6689
title={"Enable Debug Mode, e.g., show pod IDs"}
6790
disableInteractive
@@ -95,6 +118,9 @@ function SidebarSettings() {
95118
color="warning"
96119
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
97120
setAutoRunLayout(event.target.checked);
121+
if (event.target.checked) {
122+
autoLayoutROOT();
123+
}
98124
}}
99125
/>
100126
}

0 commit comments

Comments
 (0)