Skip to content

Commit

Permalink
feat: Resize option panel by dragging the divider
Browse files Browse the repository at this point in the history
  • Loading branch information
graphemecluster committed Nov 26, 2024
1 parent 2d6c8ef commit 5577af1
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 29 deletions.
12 changes: 12 additions & 0 deletions patches/monaco-editor+0.52.0.patch
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,18 @@ index 1e98235..f8ea2c7 100644
- "entry": "vs/basic-languages/yaml/yaml.contribution"
}
];
diff --git a/node_modules/monaco-editor/esm/vs/base/browser/ui/mouseCursor/mouseCursor.css b/node_modules/monaco-editor/esm/vs/base/browser/ui/mouseCursor/mouseCursor.css
index 1d7ede8..23b85be 100644
--- a/node_modules/monaco-editor/esm/vs/base/browser/ui/mouseCursor/mouseCursor.css
+++ b/node_modules/monaco-editor/esm/vs/base/browser/ui/mouseCursor/mouseCursor.css
@@ -3,6 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

-.monaco-mouse-cursor-text {
+body:not(.dragging) .monaco-mouse-cursor-text {
cursor: text;
}
diff --git a/node_modules/monaco-editor/esm/vs/base/common/buffer.js b/node_modules/monaco-editor/esm/vs/base/common/buffer.js
index 4534981..024fdcc 100644
--- a/node_modules/monaco-editor/esm/vs/base/common/buffer.js
Expand Down
3 changes: 3 additions & 0 deletions src/Components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ injectGlobal`
overflow: hidden;
touch-action: none;
}
body.dragging {
user-select: none;
}
:lang(och-Latn-fonipa) {
font-family: "CharisSILW", serif;
}
Expand Down
100 changes: 92 additions & 8 deletions src/Components/SchemaEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";

import { css } from "@emotion/react";
Expand Down Expand Up @@ -139,9 +139,7 @@ const CreateSchemaButton = styled.button`
}
`;
const EditorArea = styled.div`
flex: 1;
position: relative;
min-height: calc(6rem + 20vh);
`;
const ResetButton = styled.button`
display: inline-block;
Expand Down Expand Up @@ -179,11 +177,31 @@ const ParameterErrorHint = styled.p`
color: red;
`;
const Options = styled.form`
flex: 1;
padding: 0 1rem;
overflow-y: auto;
border-top: 0.2rem solid #c4c6c8;
`;
const SeparatorShadow = styled.div`
const Divider = styled.div<{ isDragging: boolean }>`
background-color: #c4c6c8;
height: 0.2rem;
position: relative;
cursor: ns-resize;
&::after {
content: "";
position: absolute;
top: -0.1rem;
bottom: -0.1rem;
left: 0;
right: 0;
background-color: ${({ isDragging }) => (isDragging ? "#0078e7" : "transparent")};
transition: background-color 150ms;
}
&:hover::after,
&:focus::after {
background-color: #0078e7;
}
`;
const DividerShadow = styled.div`
position: absolute;
left: 0;
bottom: 0;
Expand Down Expand Up @@ -481,6 +499,7 @@ export default function SchemaEditor({ state, setState, commonOptions, evaluateH

const tabBarRef = useRef<HTMLDivElement>(null);
function drag(name: string, { clientX: startX }: { clientX: number }, isMouse?: boolean) {
document.body.classList.add("dragging");
if (activeSchemaName !== name) setState(state => ({ ...state, activeSchemaName: name }));
const { length } = schemas;
if (length <= 1 || tabBarRef.current?.childElementCount !== length + 1) return;
Expand Down Expand Up @@ -531,6 +550,7 @@ export default function SchemaEditor({ state, setState, commonOptions, evaluateH
}
if (i !== index) setState(actions.moveSchema(name, i));

document.body.classList.remove("dragging");
if (isMouse) {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", end);
Expand Down Expand Up @@ -646,6 +666,65 @@ export default function SchemaEditor({ state, setState, commonOptions, evaluateH
};
}, [addFilesToSchema]);

const [isDividerDragging, setIsDividerDragging] = useState(false);
function dividerDrag({ target, clientY }: { target: EventTarget; clientY: number }, isMouse?: boolean) {
document.body.classList.add("dragging");
document.body.style.cursor = "ns-resize";
setIsDividerDragging(true);
const dividerElement = target as HTMLDivElement;
const container = dividerElement.parentElement!;
const editorElement = container.children[2];

const offsetY = clientY - dividerElement.getBoundingClientRect().top;

function move(event: { clientY: number } | TouchEvent) {
clientY = "clientY" in event ? event.clientY : (event.touches?.[0]?.clientY ?? clientY);
const editorTop = editorElement.getBoundingClientRect().top;
const numerator = clientY - offsetY - editorTop;
const denominator =
container.getBoundingClientRect().height - dividerElement.getBoundingClientRect().height - editorTop;
setState(state => ({ ...state, optionPanelHeight: Math.min(Math.max(1 - numerator / denominator, 0.1), 0.9) }));
}

function end() {
document.body.classList.remove("dragging");
document.body.style.cursor = "";
setIsDividerDragging(false);
if (isMouse) {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", end);
} else {
document.removeEventListener("touchmove", move);
document.removeEventListener("touchend", end);
document.removeEventListener("touchcancel", end);
}
}

if (isMouse) {
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", end);
} else {
document.addEventListener("touchmove", move);
document.addEventListener("touchend", end);
document.addEventListener("touchcancel", end);
}
}

const [editorArea, setEditorArea] = useState<HTMLDivElement | null>(null);
const [optionPanel, setOptionPanel] = useState<HTMLFormElement | null>(null);
useLayoutEffect(() => {
if (!editorArea || !optionPanel) return;
function setOptionPanelHeight() {
editorArea!.style.height =
(1 - state.optionPanelHeight) * (editorArea!.clientHeight + optionPanel!.clientHeight) + "px";
}
setOptionPanelHeight();
addEventListener("resize", setOptionPanelHeight);
return () => {
removeEventListener("resize", setOptionPanelHeight);
};
}, [editorArea, optionPanel, state.optionPanelHeight]);

return (
<>
<TabBar ref={tabBarRef}>
Expand All @@ -669,7 +748,7 @@ export default function SchemaEditor({ state, setState, commonOptions, evaluateH
<FontAwesomeIcon icon={faPlus} fixedWidth />
</CreateSchemaButton>
</TabBar>
<EditorArea lang="en-x-code">
<EditorArea ref={setEditorArea} lang="en-x-code">
<Editor
path={activeSchema?.name || ".js"}
language="javascript"
Expand Down Expand Up @@ -742,9 +821,14 @@ export default function SchemaEditor({ state, setState, commonOptions, evaluateH
[activeSchema, activeSchemaName, setState],
)}
/>
<SeparatorShadow />
<DividerShadow />
</EditorArea>
<Options className="pure-form">
<Divider
isDragging={isDividerDragging}
onMouseDown={event => dividerDrag(event, true)}
onTouchStart={event => dividerDrag(event.touches[0])}
/>
<Options ref={setOptionPanel} className="pure-form">
<h3>
<span>選項</span>
{activeSchema?.parameters.size || activeSchema?.parameters.errors.length ? (
Expand Down
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type MainState = Readonly<{
convertVariant: boolean;
syncCharPosition: boolean;
activeSchemaName: string;
optionPanelHeight: number;
}>;

export type SchemaState = Readonly<{
Expand Down
35 changes: 14 additions & 21 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,24 @@ import type { MainState } from "./consts";

export const stateStorageLocation = "autoderiver/0.2/state";

function defaultState(): MainState {
return {
schemas: [],
article: defaultArticle,
option: "convertArticle",
convertVariant: false,
syncCharPosition: true,
activeSchemaName: "",
optionPanelHeight: 0.5,
};
}

export default function initialState(): MainState {
const state = localStorage.getItem(stateStorageLocation);
if (state) {
const result: MainState = JSON.parse(state);
return {
...defaultState(),
...result,
schemas: result.schemas.map(schema => ({
...schema,
Expand All @@ -19,25 +32,5 @@ export default function initialState(): MainState {
})),
};
}

/*
return {
schemas: [],
article: defaultArticle,
option: "convertArticle",
convertVariant: false,
autocomplete: true,
syncCharPosition: true,
activeSchemaName: "",
};
*/

return {
schemas: [],
article: defaultArticle,
option: "convertArticle",
convertVariant: false,
syncCharPosition: true,
activeSchemaName: "",
};
return defaultState();
}

0 comments on commit 5577af1

Please sign in to comment.