mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Fix block numbering and switching with arrow keys (#258)
This commit is contained in:
parent
dedfc31344
commit
d5140129cd
@ -52,67 +52,36 @@ function switchBlockIdx(index: number) {
|
||||
const tabId = globalStore.get(atoms.activeTabId);
|
||||
const tabAtom = WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", tabId));
|
||||
const layoutModel = getLayoutModelForTab(tabAtom);
|
||||
if (layoutModel?.leafs == null) {
|
||||
if (!layoutModel) {
|
||||
return;
|
||||
}
|
||||
const leafsOrdered = globalStore.get(layoutModel.leafsOrdered);
|
||||
const newLeafIdx = index - 1;
|
||||
if (newLeafIdx < 0 || newLeafIdx >= layoutModel.leafs.length) {
|
||||
if (newLeafIdx < 0 || newLeafIdx >= leafsOrdered.length) {
|
||||
return;
|
||||
}
|
||||
const leaf = layoutModel.leafs[newLeafIdx];
|
||||
const leaf = leafsOrdered[newLeafIdx];
|
||||
if (leaf?.data?.blockId == null) {
|
||||
return;
|
||||
}
|
||||
setBlockFocus(leaf.data.blockId);
|
||||
}
|
||||
|
||||
function boundsMapMaxX(m: Map<string, Bounds>): number {
|
||||
let max = 0;
|
||||
for (let p of m.values()) {
|
||||
if (p.x + p.width > max) {
|
||||
max = p.x + p.width;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
function boundsMapMaxY(m: Map<string, Bounds>): number {
|
||||
let max = 0;
|
||||
for (let p of m.values()) {
|
||||
if (p.y + p.height > max) {
|
||||
max = p.y + p.height;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
function readBoundsFromTransform(fullTransform: React.CSSProperties): Bounds {
|
||||
const transformProp = fullTransform.transform;
|
||||
if (transformProp == null || fullTransform.width == null || fullTransform.height == null) {
|
||||
return null;
|
||||
}
|
||||
const m = transformRegexp.exec(transformProp);
|
||||
if (m == null) {
|
||||
return null;
|
||||
}
|
||||
function getCenter(dimensions: Dimensions): Point {
|
||||
return {
|
||||
x: parseFloat(m[1]),
|
||||
y: parseFloat(m[2]),
|
||||
width: parseFloatFromCSS(fullTransform.width),
|
||||
height: parseFloatFromCSS(fullTransform.height),
|
||||
x: dimensions.left + dimensions.width / 2,
|
||||
y: dimensions.top + dimensions.height / 2,
|
||||
};
|
||||
}
|
||||
|
||||
function parseFloatFromCSS(s: string | number): number {
|
||||
if (typeof s == "number") {
|
||||
return s;
|
||||
}
|
||||
return parseFloat(s);
|
||||
}
|
||||
|
||||
function findBlockAtPoint(m: Map<string, Bounds>, p: Point): string {
|
||||
for (let [blockId, bounds] of m.entries()) {
|
||||
if (p.x >= bounds.x && p.x <= bounds.x + bounds.width && p.y >= bounds.y && p.y <= bounds.y + bounds.height) {
|
||||
function findBlockAtPoint(m: Map<string, Dimensions>, p: Point): string {
|
||||
for (const [blockId, dimension] of m.entries()) {
|
||||
if (
|
||||
p.x >= dimension.left &&
|
||||
p.x <= dimension.left + dimension.width &&
|
||||
p.y >= dimension.top &&
|
||||
p.y <= dimension.top + dimension.height
|
||||
) {
|
||||
return blockId;
|
||||
}
|
||||
}
|
||||
@ -128,9 +97,10 @@ function switchBlock(tabId: string, offsetX: number, offsetY: number) {
|
||||
const layoutModel = getLayoutModelForTab(tabAtom);
|
||||
const curBlockId = globalStore.get(atoms.waveWindow)?.activeblockid;
|
||||
const addlProps = globalStore.get(layoutModel.additionalProps);
|
||||
const blockPositions: Map<string, Bounds> = new Map();
|
||||
for (const leaf of layoutModel.leafs) {
|
||||
const pos = readBoundsFromTransform(addlProps[leaf.id]?.transform);
|
||||
const blockPositions: Map<string, Dimensions> = new Map();
|
||||
const leafsOrdered = globalStore.get(layoutModel.leafsOrdered);
|
||||
for (const leaf of leafsOrdered) {
|
||||
const pos = addlProps[leaf.id]?.rect;
|
||||
if (pos) {
|
||||
blockPositions.set(leaf.data.blockId, pos);
|
||||
}
|
||||
@ -140,18 +110,22 @@ function switchBlock(tabId: string, offsetX: number, offsetY: number) {
|
||||
return;
|
||||
}
|
||||
blockPositions.delete(curBlockId);
|
||||
const maxX = boundsMapMaxX(blockPositions);
|
||||
const maxY = boundsMapMaxY(blockPositions);
|
||||
const boundingRect = layoutModel.displayContainerRef?.current.getBoundingClientRect();
|
||||
if (!boundingRect) {
|
||||
return;
|
||||
}
|
||||
const maxX = boundingRect.left + boundingRect.width;
|
||||
const maxY = boundingRect.top + boundingRect.height;
|
||||
const moveAmount = 10;
|
||||
let curX = curBlockPos.x + 1;
|
||||
let curY = curBlockPos.y + 1;
|
||||
const curPoint = getCenter(curBlockPos);
|
||||
while (true) {
|
||||
curX += offsetX * moveAmount;
|
||||
curY += offsetY * moveAmount;
|
||||
if (curX < 0 || curX > maxX || curY < 0 || curY > maxY) {
|
||||
console.log("nextPoint", curPoint, curBlockPos);
|
||||
curPoint.x += offsetX * moveAmount;
|
||||
curPoint.y += offsetY * moveAmount;
|
||||
if (curPoint.x < 0 || curPoint.x > maxX || curPoint.y < 0 || curPoint.y > maxY) {
|
||||
return;
|
||||
}
|
||||
const blockId = findBlockAtPoint(blockPositions, { x: curX, y: curY });
|
||||
const blockId = findBlockAtPoint(blockPositions, curPoint);
|
||||
if (blockId != null) {
|
||||
setBlockFocus(blockId);
|
||||
return;
|
||||
|
@ -255,16 +255,14 @@
|
||||
}
|
||||
|
||||
&.is-layoutmode .block-mask-inner {
|
||||
margin-top: 35px; // TODO fix this magic
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
height: calc(100% - 35px);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.bignum {
|
||||
margin-top: -15%;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
opacity: 0.7;
|
||||
|
@ -202,13 +202,9 @@ function BlockNum({ blockId }: { blockId: string }) {
|
||||
const tabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
const tabAtom = WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", tabId));
|
||||
const layoutModel = useLayoutModel(tabAtom);
|
||||
for (let idx = 0; idx < layoutModel.leafs.length; idx++) {
|
||||
const leaf = layoutModel.leafs[idx];
|
||||
if (leaf?.data?.blockId == blockId) {
|
||||
return String(idx + 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
const leafsOrdered = jotai.useAtomValue(layoutModel.leafsOrdered);
|
||||
const index = React.useMemo(() => leafsOrdered.findIndex((leaf) => leaf.data?.blockId == blockId), [leafsOrdered]);
|
||||
return index !== -1 ? index + 1 : null;
|
||||
}
|
||||
|
||||
const BlockMask = ({ blockId, preview, isFocused }: { blockId: string; preview: boolean; isFocused: boolean }) => {
|
||||
|
@ -28,14 +28,16 @@ import type {
|
||||
LayoutTreeStateSetter,
|
||||
LayoutTreeSwapNodeAction,
|
||||
} from "./lib/types";
|
||||
import { LayoutTreeActionType } from "./lib/types";
|
||||
import { DropDirection, LayoutTreeActionType, NavigateDirection } from "./lib/types";
|
||||
|
||||
export {
|
||||
deleteLayoutModelForTab,
|
||||
DropDirection,
|
||||
getLayoutModelForTab,
|
||||
getLayoutModelForTabById,
|
||||
LayoutModel,
|
||||
LayoutTreeActionType,
|
||||
NavigateDirection,
|
||||
newLayoutNode,
|
||||
TileLayout,
|
||||
useLayoutModel,
|
||||
|
@ -139,14 +139,14 @@ interface DisplayNodesWrapperProps {
|
||||
}
|
||||
|
||||
const DisplayNodesWrapper = ({ layoutModel, contents }: DisplayNodesWrapperProps) => {
|
||||
const generation = useAtomValue(layoutModel.generationAtom);
|
||||
const leafs = useAtomValue(layoutModel.leafs);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
layoutModel.leafs.map((leaf) => {
|
||||
leafs.map((leaf) => {
|
||||
return <DisplayNode key={leaf.id} layoutModel={layoutModel} layoutNode={leaf} contents={contents} />;
|
||||
}),
|
||||
[generation]
|
||||
[leafs]
|
||||
);
|
||||
};
|
||||
|
||||
@ -283,15 +283,15 @@ interface OverlayNodeWrapperProps {
|
||||
}
|
||||
|
||||
const OverlayNodeWrapper = ({ layoutModel }: OverlayNodeWrapperProps) => {
|
||||
const generation = useAtomValue(layoutModel.generationAtom);
|
||||
const leafs = useAtomValue(layoutModel.leafs);
|
||||
const overlayTransform = useAtomValue(layoutModel.overlayTransform);
|
||||
|
||||
const overlayNodes = useMemo(
|
||||
() =>
|
||||
layoutModel.leafs.map((leaf) => {
|
||||
leafs.map((leaf) => {
|
||||
return <OverlayNode key={leaf.id} layoutModel={layoutModel} layoutNode={leaf} />;
|
||||
}),
|
||||
[generation]
|
||||
[leafs]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
} from "./layoutTree";
|
||||
import {
|
||||
ContentRenderer,
|
||||
FlexDirection,
|
||||
LayoutNode,
|
||||
LayoutNodeAdditionalProps,
|
||||
LayoutTreeAction,
|
||||
@ -38,7 +39,7 @@ import {
|
||||
TileLayoutContents,
|
||||
WritableLayoutTreeStateAtom,
|
||||
} from "./types";
|
||||
import { FlexDirection, setTransform } from "./utils";
|
||||
import { setTransform } from "./utils";
|
||||
|
||||
interface ResizeContext {
|
||||
handleId: string;
|
||||
@ -86,9 +87,14 @@ export class LayoutModel {
|
||||
gapSizePx: number;
|
||||
|
||||
/**
|
||||
* List of nodes that are leafs and should be rendered as a DisplayNode
|
||||
* List of nodes that are leafs and should be rendered as a DisplayNode.
|
||||
*/
|
||||
leafs: LayoutNode[];
|
||||
leafs: PrimitiveAtom<LayoutNode[]>;
|
||||
/**
|
||||
* List of nodes that are leafs, ordered sequentially by placement in the tree.
|
||||
*/
|
||||
leafsOrdered: Atom<LayoutNode[]>;
|
||||
|
||||
/**
|
||||
* Split atom containing the properties of all of the resize handles that should be placed in the layout.
|
||||
*/
|
||||
@ -163,6 +169,7 @@ export class LayoutModel {
|
||||
* True if the whole TileLayout container is being resized.
|
||||
*/
|
||||
private isContainerResizing: PrimitiveAtom<boolean>;
|
||||
|
||||
/**
|
||||
* An arbitrary generation value that is incremented every time the updateTree function runs. Helps indicate to subscribers that they should update their memoized values.
|
||||
*/
|
||||
@ -188,8 +195,20 @@ export class LayoutModel {
|
||||
this.halfResizeHandleSizePx = this.gapSizePx > 5 ? this.gapSizePx : DefaultGapSizePx;
|
||||
this.resizeHandleSizePx = 2 * this.halfResizeHandleSizePx;
|
||||
|
||||
this.leafs = [];
|
||||
this.leafs = atom([]);
|
||||
this.additionalProps = atom({});
|
||||
this.leafsOrdered = atom((get) => {
|
||||
const leafs = get(this.leafs);
|
||||
const additionalProps = get(this.additionalProps);
|
||||
console.log("additionalProps", additionalProps);
|
||||
const leafsOrdered = leafs.sort((a, b) => {
|
||||
const treeKeyA = additionalProps[a.id].treeKey;
|
||||
const treeKeyB = additionalProps[b.id].treeKey;
|
||||
return treeKeyA.localeCompare(treeKeyB);
|
||||
});
|
||||
console.log("leafsOrdered", leafsOrdered);
|
||||
return leafsOrdered;
|
||||
});
|
||||
|
||||
const resizeHandleListAtom = atom((get) => {
|
||||
const addlProps = get(this.additionalProps);
|
||||
@ -368,7 +387,10 @@ export class LayoutModel {
|
||||
else walkNodes(this.treeState.rootNode, callback);
|
||||
|
||||
this.setter(this.additionalProps, newAdditionalProps);
|
||||
this.leafs = newLeafs.sort((a, b) => a.id.localeCompare(b.id));
|
||||
this.setter(
|
||||
this.leafs,
|
||||
newLeafs.sort((a, b) => a.id.localeCompare(b.id))
|
||||
);
|
||||
|
||||
this.setter(this.generationAtom, this.getter(this.generationAtom) + 1);
|
||||
}
|
||||
@ -426,7 +448,9 @@ export class LayoutModel {
|
||||
|
||||
const additionalProps: LayoutNodeAdditionalProps = additionalPropsMap.hasOwnProperty(node.id)
|
||||
? additionalPropsMap[node.id]
|
||||
: {};
|
||||
: { treeKey: "0" };
|
||||
|
||||
console.log("layoutNode addlProps", node, additionalProps);
|
||||
|
||||
const nodeRect: Dimensions = node.id === this.treeState.rootNode.id ? getBoundingRect() : additionalProps.rect;
|
||||
const nodeIsRow = node.flexDirection === FlexDirection.Row;
|
||||
@ -436,7 +460,7 @@ export class LayoutModel {
|
||||
|
||||
let lastChildRect: Dimensions;
|
||||
const resizeHandles: ResizeHandleProps[] = [];
|
||||
for (const child of node.children) {
|
||||
node.children.forEach((child, i) => {
|
||||
const childSize = getNodeSize(child);
|
||||
const rect: Dimensions = {
|
||||
top: !nodeIsRow && lastChildRect ? lastChildRect.top + lastChildRect.height : nodeRect.top,
|
||||
@ -448,6 +472,7 @@ export class LayoutModel {
|
||||
additionalPropsMap[child.id] = {
|
||||
rect,
|
||||
transform,
|
||||
treeKey: additionalProps.treeKey + i,
|
||||
};
|
||||
|
||||
// We only want the resize handles in between nodes, this ensures we have n-1 handles.
|
||||
@ -475,7 +500,7 @@ export class LayoutModel {
|
||||
});
|
||||
}
|
||||
lastChildRect = rect;
|
||||
}
|
||||
});
|
||||
|
||||
additionalPropsMap[node.id] = {
|
||||
...additionalProps,
|
||||
@ -713,7 +738,7 @@ export class LayoutModel {
|
||||
}
|
||||
|
||||
getNodeByBlockId(blockId: string) {
|
||||
for (const leaf of this.leafs) {
|
||||
for (const leaf of this.getter(this.leafs)) {
|
||||
if (leaf.data.blockId === blockId) {
|
||||
return leaf;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DefaultNodeSize, LayoutNode } from "./types";
|
||||
import { FlexDirection, reverseFlexDirection } from "./utils";
|
||||
import { DefaultNodeSize, FlexDirection, LayoutNode } from "./types";
|
||||
import { reverseFlexDirection } from "./utils";
|
||||
|
||||
/**
|
||||
* Creates a new node.
|
||||
|
@ -13,6 +13,8 @@ import {
|
||||
} from "./layoutNode";
|
||||
import {
|
||||
DefaultNodeSize,
|
||||
DropDirection,
|
||||
FlexDirection,
|
||||
LayoutTreeActionType,
|
||||
LayoutTreeComputeMoveNodeAction,
|
||||
LayoutTreeDeleteNodeAction,
|
||||
@ -25,7 +27,6 @@ import {
|
||||
LayoutTreeSwapNodeAction,
|
||||
MoveOperation,
|
||||
} from "./types";
|
||||
import { DropDirection, FlexDirection } from "./utils";
|
||||
|
||||
/**
|
||||
* Computes an operation for inserting a new node into the tree in the given direction relative to the specified node.
|
||||
|
@ -3,7 +3,30 @@
|
||||
|
||||
import { WritableAtom } from "jotai";
|
||||
import { CSSProperties } from "react";
|
||||
import { DropDirection, FlexDirection } from "./utils.js";
|
||||
|
||||
export enum NavigateDirection {
|
||||
Top = 0,
|
||||
Right = 1,
|
||||
Bottom = 2,
|
||||
Left = 3,
|
||||
}
|
||||
|
||||
export enum DropDirection {
|
||||
Top = 0,
|
||||
Right = 1,
|
||||
Bottom = 2,
|
||||
Left = 3,
|
||||
OuterTop = 4,
|
||||
OuterRight = 5,
|
||||
OuterBottom = 6,
|
||||
OuterLeft = 7,
|
||||
Center = 8,
|
||||
}
|
||||
|
||||
export enum FlexDirection {
|
||||
Row = "row",
|
||||
Column = "column",
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an operation to insert a node into a tree.
|
||||
@ -276,6 +299,7 @@ export interface ResizeHandleProps {
|
||||
}
|
||||
|
||||
export interface LayoutNodeAdditionalProps {
|
||||
treeKey: string;
|
||||
transform?: CSSProperties;
|
||||
rect?: Dimensions;
|
||||
pixelToSizeRatio?: number;
|
||||
|
@ -3,23 +3,7 @@
|
||||
|
||||
import { CSSProperties } from "react";
|
||||
import { XYCoord } from "react-dnd";
|
||||
|
||||
export enum DropDirection {
|
||||
Top = 0,
|
||||
Right = 1,
|
||||
Bottom = 2,
|
||||
Left = 3,
|
||||
OuterTop = 4,
|
||||
OuterRight = 5,
|
||||
OuterBottom = 6,
|
||||
OuterLeft = 7,
|
||||
Center = 8,
|
||||
}
|
||||
|
||||
export enum FlexDirection {
|
||||
Row = "row",
|
||||
Column = "column",
|
||||
}
|
||||
import { DropDirection, FlexDirection } from "./types";
|
||||
|
||||
export function reverseFlexDirection(flexDirection: FlexDirection): FlexDirection {
|
||||
return flexDirection === FlexDirection.Row ? FlexDirection.Column : FlexDirection.Row;
|
||||
|
7
frontend/types/custom.d.ts
vendored
7
frontend/types/custom.d.ts
vendored
@ -44,13 +44,6 @@ declare global {
|
||||
blockId: string;
|
||||
};
|
||||
|
||||
type Bounds = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
type ElectronApi = {
|
||||
getAuthKey(): string;
|
||||
getIsDev(): boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user