Skip to content

Commit

Permalink
whiteboard: code resize, duplicate button, icons for actions menu
Browse files Browse the repository at this point in the history
  • Loading branch information
williamstein committed Apr 4, 2022
1 parent 55284ee commit 1705eb3
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const STYLE: CSSProperties = {
const InnerStyle: CSSProperties = {
border: "1px solid rgb(207, 207, 207)",
borderRadius: "2px",
background: "rgb(247, 247, 247)",
padding: "5px 25px",
};

Expand Down
26 changes: 19 additions & 7 deletions src/packages/frontend/frame-editors/whiteboard-editor/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,12 @@ export class Actions extends BaseActions<State> {
input: str ?? element.str ?? "",
id,
set: (obj) =>
this.setElementData({ element, obj, commit: true, cursors: [{}] }),
this.setElementData({
element,
obj: { ...obj, hideOutput: false },
commit: true,
cursors: [{}],
}),
});
}

Expand Down Expand Up @@ -708,26 +713,26 @@ export class Actions extends BaseActions<State> {
// e.g., this is used to implemented "duplicate"; otherwise,
// pastes to the center of the viewport.
paste(
frameId: string,
frameId?: string,
_value?: string | true | undefined,
nextTo?: Element[]
): void {
const pastedElements = pasteFromInternalClipboard();
let target: Point;
let target: Point = { x: 0, y: 0 };
if (nextTo != null) {
const { x, y, w, h } = rectSpan(nextTo);
const w2 = rectSpan(pastedElements).w;
target = { x: x + w + w2 / 2 + DEFAULT_GAP, y: y + h / 2 };
} else {
} else if (frameId != null) {
const viewport = this._get_frame_node(frameId)?.get("viewport")?.toJS();
if (viewport != null) {
target = centerOfRect(viewport);
} else {
target = { x: 0, y: 0 };
}
}
const ids = this.insertElements(pastedElements, target);
this.setSelectionMulti(frameId, ids);
if (frameId != null) {
this.setSelectionMulti(frameId, ids);
}
}

centerElement(id: string, frameId?: string) {
Expand Down Expand Up @@ -921,6 +926,13 @@ export class Actions extends BaseActions<State> {
}
}

duplicateElements(elements: Element[], frameId?: string) {
const elements0 = [...elements];
extendToIncludeEdges(elements0, this.getElements());
copyToClipboard(elements0);
this.paste(frameId, undefined, elements);
}

getGroup(group: string): Element[] {
const X: Element[] = [];
if (!group) return X;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function CodeControlBar({ element }: Props) {
{!element.data?.hideInput && (
<Tooltip title="Evaluate code (Shift+Enter)">
<Button
disabled={element.data?.runState == "busy"}
size="small"
onClick={() => {
actions.runCodeElement({ id: element.id });
Expand All @@ -77,6 +78,24 @@ export default function CodeControlBar({ element }: Props) {
Input
</Checkbox>
</Tooltip>
<Tooltip title="Toggle display of output">
<Checkbox
disabled={
element.data?.output == null ||
Object.keys(element.data?.output).length == 0
}
checked={!element.data?.hideOutput}
style={{ fontWeight: 250, marginLeft: "10px" }}
onChange={(e) => {
actions.setElementData({
element,
obj: { hideOutput: !e.target.checked },
});
}}
>
Output
</Checkbox>
</Tooltip>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Element } from "../../types";
import ControlBar from "./control";
import Input from "./input";
Expand All @@ -11,7 +11,7 @@ import { useAsyncEffect } from "use-async-effect";
import { getMode } from "./actions";
import { codemirrorMode } from "@cocalc/frontend/file-extensions";
import { useFrameContext } from "../../hooks";
import useWheel from "../scroll-wheel";
import useResizeObserver from "use-resize-observer";

interface Props {
element: Element;
Expand All @@ -29,7 +29,7 @@ export default function Code({
const { hideInput, hideOutput } = element.data ?? {};
const [editFocus, setEditFocus] = useEditFocus(false);

const { project_id, path } = useFrameContext();
const { actions, project_id, path } = useFrameContext();
const [mode, setMode] = useState<any>(codemirrorMode("py"));
useAsyncEffect(async () => {
setMode(await getMode({ project_id, path }));
Expand All @@ -55,19 +55,29 @@ export default function Code({
}
return <InputStatic element={element} mode={mode} />;
};
const divRef = useRef(null);
useWheel(divRef);
const divRef = useRef<any>(null);
const resize = useResizeObserver({ ref: divRef });
const resizeIfNecessary = useCallback(() => {
if (actions.in_undo_mode()) return;
const elt = divRef.current;
if (elt == null) return;
actions.setElement({
obj: { id: element.id, h: elt.offsetHeight + 30 },
commit: false,
});
}, [element]);
useEffect(() => {
resizeIfNecessary();
}, [resize]);

return (
<div ref={divRef} style={getStyle(element)}>
{!hideInput && <InputPrompt element={element} />}
{renderInput()}
{!hideOutput && element.data?.output && (
<div className="nodrag">
<Output element={element} />
</div>
)}
{focused && <ControlBar element={element} />}
<div style={{ ...getStyle(element), height: "100%" }}>
<div ref={divRef}>
{!hideInput && <InputPrompt element={element} />}
{renderInput()}
{!hideOutput && element.data?.output && <Output element={element} />}
{focused && <ControlBar element={element} />}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export default function Input({
obj: { id: element.id, str },
commit: false,
});
console.log("using input ", cm.getValue());
// evaluate in all cases
frame.actions.runCodeElement({ id: element.id, str });
// TODO: handle these cases
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { CellOutput } from "@cocalc/frontend/jupyter/cell-output";
import { fromJS } from "immutable";
import { useFrameContext } from "../../hooks";
import { path_split } from "@cocalc/util/misc";
import { getJupyterActions } from "./actions";
import { useIsMountedRef } from "@cocalc/frontend/app-framework";
import type { JupyterActions } from "@cocalc/frontend/jupyter/browser-actions";
import useWheel from "../scroll-wheel";

// Support for all the output Jupyter MIME types must be explicitly loaded.
import "@cocalc/frontend/jupyter/output-messages/mime-types/init-frontend";
Expand All @@ -20,12 +21,15 @@ export default function Output({ element }) {
// Initialize state needed for widgets to work.
useEffect(() => {
(async () => {
const jupyter_actions = await getJupyterActions({project_id, path});
const jupyter_actions = await getJupyterActions({ project_id, path });
if (!isMounted.current) return;
setJupyterActions(jupyter_actions);
})();
}, []);

const divRef = useRef(null);
useWheel(divRef);

if (jupyterActions == null) {
// don't render CellOutput until loaded, since CellOutput doesn't
// update when just the name changes from undefined to a string --
Expand All @@ -34,16 +38,22 @@ export default function Output({ element }) {
}

return (
<CellOutput
actions={jupyterActions}
name={jupyterActions?.name}
id={element.id}
cell={fromJS(element.data)}
project_id={project_id}
directory={path_split(path).head}
trust={true}
complete={false}
hidePrompt
/>
<div
ref={divRef}
style={{ maxHeight: "80vh", overflowY: "auto" }}
className="nodrag" /* because of ipywidgets, e.g., sliders */
>
<CellOutput
actions={jupyterActions}
name={jupyterActions?.name}
id={element.id}
cell={fromJS(element.data)}
project_id={project_id}
directory={path_split(path).head}
trust={true}
complete={false}
hidePrompt
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { getStyle };
import MultiMarkdownInput from "@cocalc/frontend/editors/markdown-input/multimode";
import useEditFocus from "./edit-focus";
import useMouseClickDrag from "./mouse-click-drag";
import useResizeObserver from "use-resize-observer";

interface Props {
element: Element;
Expand Down Expand Up @@ -38,22 +39,19 @@ function EditText({
focused?: boolean;
}) {
const { actions, id: frameId } = useFrameContext();
const expandIfNecessary = useCallback(() => {
const resizeIfNecessary = useCallback(() => {
if (actions.in_undo_mode()) return;
// possibly adjust height. We do this in the next render
// loop because sometimes when the change fires the dom
// hasn't updated the height of the containing div yet,
// so we end up setting the height 1 step behind reality.
// We never make the height smaller -- user can manually do that.
const elt = editorDivRef.current;
if (elt == null) return;
const height = (elt.offsetHeight ?? 0) + 2 * PADDING + 2 + 15;
if (height > (element.h ?? 0)) {
actions.setElement({
obj: { id: element.id, h: height },
commit: false,
});
}
actions.setElement({
obj: { id: element.id, h: height },
commit: false,
});
}, [element]);
const [mode, setMode] = useState<string>("");

Expand Down Expand Up @@ -89,6 +87,11 @@ function EditText({
};
}, []);

const resize = useResizeObserver({ ref: editorDivRef });
useEffect(() => {
resizeIfNecessary();
}, [resize]);

return (
<div
{...mouseClickDrag}
Expand All @@ -112,7 +115,7 @@ function EditText({
isFocused={editFocus && focused}
onFocus={() => {
setEditFocus(true);
expandIfNecessary();
resizeIfNecessary();
// NOTE: we do not do "setEditFocus(false)" with onBlur, because
// there are many ways to "blur" the slate editor technically, but
// still want to consider it focused, e.g., editing math and code
Expand All @@ -126,7 +129,7 @@ function EditText({
fontSize={element.data?.fontSize ?? DEFAULT_FONT_SIZE}
onChange={(value) => {
actions.setElement({ obj: { id: element.id, str: value } });
setTimeout(expandIfNecessary, 0);
setTimeout(resizeIfNecessary, 0);
}}
onModeChange={setMode}
editBarStyle={{
Expand Down
Loading

0 comments on commit 1705eb3

Please sign in to comment.