Skip to content

Commit

Permalink
[UI] Edit cmd mode patch (#431)
Browse files Browse the repository at this point in the history
  • Loading branch information
senwang86 committed Aug 4, 2023
1 parent 9336551 commit b77c374
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 46 deletions.
95 changes: 56 additions & 39 deletions ui/src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -360,18 +360,19 @@ function isInputDOMNode(event: KeyboardEvent): boolean {
function useJump() {
const store = useContext(RepoContext)!;

const selectPod = useStore(store, (state) => state.selectPod);
const resetSelection = useStore(store, (state) => state.resetSelection);
const cursorNode = useStore(store, (state) => state.cursorNode);
const setCursorNode = useStore(store, (state) => state.setCursorNode);

const focusedEditor = useStore(store, (state) => state.focusedEditor);
const setFocusedEditor = useStore(store, (state) => state.setFocusedEditor);

const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
const pods = useStore(store, (state) => state.pods);

const reactflow = useReactFlow();

const selectedPods = useStore(store, (state) => state.selectedPods);
const wsRun = useStore(store, (state) => state.wsRun);
const wsRunScope = useStore(store, (state) => state.wsRunScope);

const handleKeyDown = (event) => {
// This is a hack to address the extra propagation of "Esc" pressed in Rich node, https://github.com/codepod-io/codepod/pull/398#issuecomment-1655153696
if (isInputDOMNode(event)) return false;
Expand All @@ -382,13 +383,12 @@ function useJump() {
case "ArrowLeft":
case "ArrowRight":
case "Enter":
case "Escape":
break;
default:
return;
}
// Get the selected node.
const id = selectedPods.values().next().value; // Assuming only one node can be selected at a time
// Get the current cursor node.
const id = cursorNode;
if (!id) {
console.log("No node selected");
return; // Ignore arrow key presses if there's no selected node or if the user is typing in an input field
Expand All @@ -408,10 +408,40 @@ function useJump() {

switch (event.key) {
case "ArrowUp":
to = getBestNode(nodes, pod, "up");
if (event.shiftKey) {
if (pod.parentNode !== "ROOT") {
to = nodesMap.get(pod.parentNode!)!;
} else {
to = pod;
}
} else {
to = getBestNode(nodes, pod, "up");
}
break;
case "ArrowDown":
to = getBestNode(nodes, pod, "down");
if (event.shiftKey) {
if (pod.type === "SCOPE") {
to = pod;
let minDist = Math.sqrt(
(pod.height || 1) ** 2 + (pod.width || 1) ** 2
);
let childDist = 0;
for (const child of pods[id].children) {
childDist = Math.sqrt(
nodesMap.get(child.id)!.position.x ** 2 +
nodesMap.get(child.id)!.position.y ** 2
);
if (minDist > childDist) {
minDist = childDist;
to = nodesMap.get(child.id)!;
}
}
} else {
to = pod;
}
} else {
to = getBestNode(nodes, pod, "down");
}
break;
case "ArrowLeft":
to = getBestNode(nodes, pod, "left");
Expand All @@ -420,44 +450,31 @@ function useJump() {
to = getBestNode(nodes, pod, "right");
break;
case "Enter":
// Hitting "Enter" on a Scope will go to its upper-left most child.
// Hitting "Enter" on a Code/Rich pod will go to "Edit" mode.
if (pod.type === "SCOPE") {
to = pod;
let minDist = Math.sqrt(
(pod.height || 1) ** 2 + (pod.width || 1) ** 2
);
let childDist = 0;
for (const child of pods[id].children) {
childDist = Math.sqrt(
nodesMap.get(child.id)!.position.x ** 2 +
nodesMap.get(child.id)!.position.y ** 2
);
if (minDist > childDist) {
minDist = childDist;
to = nodesMap.get(child.id)!;
}
if (pod.type == "CODE") {
if (event.shiftKey) {
// Hitting "SHIFT"+"Enter" will run the code pod
wsRun(id);
} else {
// Hitting "Enter" on a Code pod will go to "Edit" mode.
setFocusedEditor(id);
}
} else {
} else if (pod.type === "SCOPE") {
if (event.shiftKey) {
// Hitting "SHIFT"+"Enter" on a Scope will run the scope.
wsRunScope(id);
}
} else if (pod.type === "RICH") {
// Hitting "Enter" on a Rich pod will go to "Edit" mode.
setFocusedEditor(id);
}
break;
case "Escape":
// Hitting "Esc" in command mode will go to its parent
if (pod.parentNode !== "ROOT") {
to = nodesMap.get(pod.parentNode!)!;
} else {
to = pod;
}
break;
default:
return;
}

if (to) {
// set the to node as selected
resetSelection();
selectPod(to.id, true);
setCursorNode(to.id);

// move the viewport to the to node
// get the absolute position of the to node
const pos = getAbsPos(to, nodesMap);
Expand All @@ -476,7 +493,7 @@ function useJump() {
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [selectedPods]);
}, [cursorNode]);
}

function usePaste(reactFlowWrapper) {
Expand Down
5 changes: 2 additions & 3 deletions ui/src/components/MyMonaco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,7 @@ export const MyMonaco = memo<MyMonacoProps>(function MyMonaco({
const focusedEditor = useStore(store, (state) => state.focusedEditor);
const setFocusedEditor = useStore(store, (state) => state.setFocusedEditor);
const setPodBlur = useStore(store, (state) => state.setPodBlur);
const selectPod = useStore(store, (state) => state.selectPod);
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
const setCursorNode = useStore(store, (state) => state.setCursorNode);
const annotations = useStore(store, (state) => state.pods[id]?.annotations);
const showAnnotations = useStore(store, (state) => state.showAnnotations);
const scopedVars = useStore(store, (state) => state.scopedVars);
Expand Down Expand Up @@ -491,7 +490,7 @@ export const MyMonaco = memo<MyMonacoProps>(function MyMonaco({
if (document.activeElement) {
(document.activeElement as any).blur();
setPodBlur(id);
selectPod(id, true);
setCursorNode(id);
setFocusedEditor(undefined);
}
},
Expand Down
12 changes: 11 additions & 1 deletion ui/src/components/nodes/Code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ export const CodeNode = memo<NodeProps>(function ({

const pod = getPod(id);
const isGuest = useStore(store, (state) => state.role === "GUEST");
const cursorNode = useStore(store, (state) => state.cursorNode);
const isPodFocused = useStore(store, (state) => state.pods[id]?.focus);
const inputRef = useRef<HTMLInputElement>(null);
const updateView = useStore(store, (state) => state.updateView);
Expand All @@ -509,6 +510,8 @@ export const CodeNode = memo<NodeProps>(function ({
const autoRunLayout = useStore(store, (state) => state.autoRunLayout);

const prevLayout = useRef(layout);
const [showToolbar, setShowToolbar] = useState(false);

useEffect(() => {
if (autoRunLayout) {
// Run auto-layout when the output box layout changes.
Expand All @@ -519,6 +522,14 @@ export const CodeNode = memo<NodeProps>(function ({
}
}, [layout]);

useEffect(() => {
if (cursorNode === id) {
setShowToolbar(true);
} else {
setShowToolbar(false);
}
}, [cursorNode]);

const onResizeStop = useCallback(
(e, data) => {
const { size } = data;
Expand Down Expand Up @@ -551,7 +562,6 @@ export const CodeNode = memo<NodeProps>(function ({
[id, nodesMap, setPodGeo, updateView, autoLayoutROOT]
);

const [showToolbar, setShowToolbar] = useState(false);
useEffect(() => {
if (!data.name) return;
setPodName({ id, name: data.name });
Expand Down
14 changes: 11 additions & 3 deletions ui/src/components/nodes/Rich.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ const MyStyledWrapper = styled("div")(
function HotkeyControl({ id }) {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const selectPod = useStore(store, (state) => state.selectPod);
const setCursorNode = useStore(store, (state) => state.setCursorNode);
const setPodBlur = useStore(store, (state) => state.setPodBlur);
const focusedEditor = useStore(store, (state) => state.focusedEditor);
const setFocusedEditor = useStore(store, (state) => state.setFocusedEditor);
Expand All @@ -262,7 +262,7 @@ function HotkeyControl({ id }) {
if (document.activeElement) {
(document.activeElement as any).blur();
setPodBlur(id);
selectPod(id, true);
setCursorNode(id);
setFocusedEditor(undefined);
}
return true;
Expand Down Expand Up @@ -446,7 +446,6 @@ function MyFloatingToolbar({ id }: { id: string }) {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const reactFlowInstance = useReactFlow();
// const selected = useStore(store, (state) => state.pods[id]?.selected);
const isGuest = useStore(store, (state) => state.role === "GUEST");
return (
<>
Expand Down Expand Up @@ -517,6 +516,7 @@ export const RichNode = memo<Props>(function ({
const pod = getPod(id);
const isGuest = useStore(store, (state) => state.role === "GUEST");
const width = useStore(store, (state) => state.pods[id]?.width);
const cursorNode = useStore(store, (state) => state.cursorNode);
const isPodFocused = useStore(store, (state) => state.pods[id]?.focus);
const devMode = useStore(store, (state) => state.devMode);
const inputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -579,6 +579,14 @@ export const RichNode = memo<Props>(function ({
(state) => state.contextualZoomParams.threshold
);

useEffect(() => {
if (cursorNode === id) {
setShowToolbar(true);
} else {
setShowToolbar(false);
}
}, [cursorNode]);

if (!pod) return null;

const node = nodesMap.get(id);
Expand Down
8 changes: 8 additions & 0 deletions ui/src/components/nodes/Scope.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export const ScopeNode = memo<NodeProps>(function ScopeNode({

const devMode = useStore(store, (state) => state.devMode);
const isCutting = useStore(store, (state) => state.cuttingIds.has(id));
const cursorNode = useStore(store, (state) => state.cursorNode);

useEffect(() => {
if (!data.name) return;
Expand All @@ -218,6 +219,13 @@ export const ScopeNode = memo<NodeProps>(function ScopeNode({

const [showToolbar, setShowToolbar] = useState(false);

useEffect(() => {
if (cursorNode === id) {
setShowToolbar(true);
} else {
setShowToolbar(false);
}
}, [cursorNode]);
const contextualZoom = useStore(store, (state) => state.contextualZoom);
const contextualZoomParams = useStore(
store,
Expand Down
14 changes: 14 additions & 0 deletions ui/src/lib/store/canvasSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ export interface CanvasSlice {
focusedEditor: string | undefined;
setFocusedEditor: (id?: string) => void;

cursorNode: string | undefined;
setCursorNode: (id?: string) => void;

updateView: () => void;
updateEdgeView: () => void;

Expand Down Expand Up @@ -407,6 +410,14 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
state.focusedEditor = id;
})
),

cursorNode: undefined,
setCursorNode: (id?: string) =>
set(
produce((state: MyState) => {
state.cursorNode = id;
})
),
/**
* This function handles the real updates to the reactflow nodes to render.
*/
Expand Down Expand Up @@ -975,6 +986,9 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
throw new Error("Add node should not be handled here");
case "select":
get().selectPod(change.id, change.selected);
if (change.selected) {
get().setCursorNode(change.id);
}
break;
case "dimensions":
{
Expand Down

0 comments on commit b77c374

Please sign in to comment.