Skip to content

Commit

Permalink
fix: minimap scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
dineug committed Nov 11, 2023
1 parent e00cbcc commit ce8f162
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 69 deletions.
14 changes: 6 additions & 8 deletions packages/erd-editor-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,19 @@
"scripts": {
"build": "vite build"
},
"dependencies": {
"@dineug/shared": "workspace:*",
"lodash-es": "^4.17.21",
"uuid": "^9.0.0"
},
"devDependencies": {
"@dineug/shared": "workspace:*",
"@rollup/plugin-typescript": "^11.1.2",
"@types/lodash-es": "^4.17.8",
"@types/uuid": "^9.0.2",
"lodash-es": "^4.17.21",
"rollup-plugin-visualizer": "^5.9.2",
"ts-patch": "^3.0.2",
"tslib": "^2.6.1",
"typescript": "5.1.6",
"typescript-transform-paths": "^3.4.6",
"vite": "^4.4.9",
"vite-tsconfig-paths": "^4.2.0"
"typescript": "5.1.6",
"uuid": "^9.0.0",
"vite-tsconfig-paths": "^4.2.0",
"vite": "^4.4.9"
}
}
1 change: 0 additions & 1 deletion packages/erd-editor-schema/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default defineConfig({
formats: ['es', 'umd'],
},
rollupOptions: {
external: Object.keys(pkg.dependencies),
output: {
banner,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/erd-editor/src/components/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
InjectEngineContext,
} from '@/engine/context';
import { createRxStore, RxStore } from '@/engine/rx-store';
import { Ctx } from '@/internal-types';
import {
createKeyBindingMap,
KeyBindingMap,
Expand Down Expand Up @@ -52,5 +53,4 @@ export function createAppContext(ctx: InjectAppContext): AppContext {

export const appContext = createContext<AppContext>({} as AppContext);

export const useAppContext = (ctx: Parameters<typeof useContext>[0]) =>
useContext(ctx, appContext);
export const useAppContext = (ctx: Ctx) => useContext(ctx, appContext);
3 changes: 3 additions & 0 deletions packages/erd-editor/src/components/erd/Erd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const Erd: FC<ErdProps> = (props, ctx) => {
const handleMove = ({ event, movementX, movementY }: DragMove) => {
const { store } = app.value;
event.type === 'mousemove' && event.preventDefault();
if (movementX === 0 && movementY === 0) {
return;
}
store.dispatch(streamScrollToAction({ movementX, movementY }));
resetScroll();
};
Expand Down
10 changes: 8 additions & 2 deletions packages/erd-editor/src/components/erd/minimap/Minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { useAppContext } from '@/components/context';
import * as CanvasStyle from '@/components/erd/canvas/Canvas.styles';
import Memo from '@/components/erd/minimap/memo/Memo';
import Table from '@/components/erd/minimap/table/Table';
import { useMinimapScroll } from '@/components/erd/minimap/useMinimapScroll';
import Viewport from '@/components/erd/minimap/viewport/Viewport';
import { MINIMAP_MARGIN, MINIMAP_SIZE } from '@/constants/layout';
import { scrollToAction } from '@/engine/modules/settings/atom.actions';
import { query } from '@/utils/collection/query';
import { isMouseEvent } from '@/utils/domEvent';
import { forwardMoveStartEvent } from '@/utils/internalEvents';

import * as styles from './Minimap.styles';

Expand All @@ -19,6 +21,7 @@ export type MinimapProps = {};
const Minimap: FC<MinimapProps> = (props, ctx) => {
const app = useAppContext(ctx);
const minimap = createRef<HTMLDivElement>();
const { state, onScrollStart } = useMinimapScroll(ctx);

const getRatio = () => {
const { store } = app.value;
Expand All @@ -40,7 +43,7 @@ const Minimap: FC<MinimapProps> = (props, ctx) => {
const top = y + MINIMAP_MARGIN;

return {
transform: `scale(${ratio}, ${ratio})`,
transform: `scale(${ratio})`,
width: `${width}px`,
height: `${height}px`,
right: `${right}px`,
Expand Down Expand Up @@ -87,6 +90,9 @@ const Minimap: FC<MinimapProps> = (props, ctx) => {
scrollTop: -1 * scrollTop,
})
);

ctx.host.dispatchEvent(forwardMoveStartEvent({ originEvent: event }));
onScrollStart(event);
};

return () => {
Expand Down Expand Up @@ -136,7 +142,7 @@ const Minimap: FC<MinimapProps> = (props, ctx) => {
</div>
</div>
<div class=${styles.border} style=${borderStyleMap()}></div>
<${Viewport} />
<${Viewport} selected=${state.selected} />
`;
};
};
Expand Down
137 changes: 137 additions & 0 deletions packages/erd-editor/src/components/erd/minimap/useMinimapScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { observable } from '@dineug/r-html';

import { useAppContext } from '@/components/context';
import { MINIMAP_SIZE } from '@/constants/layout';
import { streamScrollToAction } from '@/engine/modules/settings/atom.actions';
import { Ctx, ValuesType } from '@/internal-types';
import { isMouseEvent } from '@/utils/domEvent';
import { drag$, DragMove } from '@/utils/globalEventObservable';

const Direction = {
left: 'left',
right: 'right',
top: 'top',
bottom: 'bottom',
} as const;
type Direction = ValuesType<typeof Direction>;

export function useMinimapScroll(ctx: Ctx) {
const app = useAppContext(ctx);
const state = observable({
selected: false,
});

let clientX = 0;
let clientY = 0;

const getRatio = () => {
const { store } = app.value;
const {
settings: { width },
} = store.state;
return MINIMAP_SIZE / width;
};

const absoluteMovement = (movement: number) => {
const ratio = getRatio();
return -1 * (movement / ratio);
};

const getMovementX = ({ movementX, x }: DragMove) => {
const { store } = app.value;
const {
settings,
editor: { viewport },
} = store.state;
const scrollLeft = settings.scrollLeft + absoluteMovement(movementX);
const min = viewport.width - settings.width;
const max = 0;
const direction: Direction = movementX < 0 ? 'left' : 'right';
let change = false;

switch (direction) {
case 'left':
if (scrollLeft < max && x < clientX) {
clientX += movementX;
change = true;
}
break;
case 'right':
if (scrollLeft > min && x > clientX) {
clientX += movementX;
change = true;
}
break;
}

return change ? movementX : 0;
};

const getMovementY = ({ movementY, y }: DragMove) => {
const { store } = app.value;
const {
settings,
editor: { viewport },
} = store.state;
const scrollTop = settings.scrollTop + absoluteMovement(movementY);
const min = viewport.height - settings.height;
const max = 0;
const direction: Direction = movementY < 0 ? 'top' : 'bottom';
let change = false;

switch (direction) {
case 'top':
if (scrollTop < max && y < clientY) {
clientY += movementY;
change = true;
}
break;
case 'bottom':
if (scrollTop > min && y > clientY) {
clientY += movementY;
change = true;
}
break;
}

return change ? movementY : 0;
};

const handleScroll = (dragMove: DragMove) => {
const { event } = dragMove;
event.type === 'mousemove' && event.preventDefault();
const movementX = getMovementX(dragMove);
const movementY = getMovementY(dragMove);

if (movementX === 0 && movementY === 0) {
return;
}

const { store } = app.value;
store.dispatch(
streamScrollToAction({
movementX: absoluteMovement(movementX),
movementY: absoluteMovement(movementY),
})
);
};

const onScrollStart = (event: MouseEvent | TouchEvent) => {
state.selected = true;

clientX = isMouseEvent(event) ? event.clientX : event.touches[0].clientX;
clientY = isMouseEvent(event) ? event.clientY : event.touches[0].clientY;

drag$.subscribe({
next: handleScroll,
complete: () => {
state.selected = false;
},
});
};

return {
state,
onScrollStart,
};
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { FC, html, observable } from '@dineug/r-html';
import { FC, html } from '@dineug/r-html';

import { useAppContext } from '@/components/context';
import { useMinimapScroll } from '@/components/erd/minimap/useMinimapScroll';
import { MINIMAP_MARGIN, MINIMAP_SIZE } from '@/constants/layout';
import { streamScrollToAction } from '@/engine/modules/settings/atom.actions';
import { drag$, DragMove } from '@/utils/globalEventObservable';

import * as styles from './Viewport.styles';

export type ViewportProps = {};
export type ViewportProps = {
selected: boolean;
};

const Viewport: FC<ViewportProps> = (props, ctx) => {
const app = useAppContext(ctx);
const state = observable({
selected: false,
});
const { state, onScrollStart } = useMinimapScroll(ctx);

const getRatio = () => {
const { store } = app.value;
Expand Down Expand Up @@ -45,35 +44,12 @@ const Viewport: FC<ViewportProps> = (props, ctx) => {
};
};

const handleMove = ({ event, movementX, movementY }: DragMove) => {
event.type === 'mousemove' && event.preventDefault();
const { store } = app.value;
const ratio = getRatio();

store.dispatch(
streamScrollToAction({
movementX: -1 * (movementX / ratio),
movementY: -1 * (movementY / ratio),
})
);
};

const handleMoveStart = () => {
state.selected = true;
drag$.subscribe({
next: handleMove,
complete: () => {
state.selected = false;
},
});
};

return () => html`
<div
class=${[styles.viewport, { selected: state.selected }]}
class=${[styles.viewport, { selected: state.selected || props.selected }]}
style=${styleMap()}
@mousedown=${handleMoveStart}
@touchstart=${handleMoveStart}
@mousedown=${onScrollStart}
@touchstart=${onScrollStart}
></div>
`;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '@dineug/r-html';
import { Subject } from 'rxjs';

import { Ctx } from '@/internal-types';

export type ContextMenuRootContext = {
show: boolean;
x: number;
Expand All @@ -24,13 +26,10 @@ export const contextMenuRootContext = createContext<ContextMenuRootContext>({
change$: new Subject(),
});

export const useContextMenuRootContext = (
ctx: Parameters<typeof useContext>[0]
) => useContext(ctx, contextMenuRootContext);
export const useContextMenuRootContext = (ctx: Ctx) =>
useContext(ctx, contextMenuRootContext);

export function useContextMenuRootProvider(
ctx: Parameters<typeof useContext>[0]
) {
export function useContextMenuRootProvider(ctx: Ctx) {
const state = observable<ContextMenuRootContext>({
show: false,
x: 0,
Expand Down
3 changes: 3 additions & 0 deletions packages/erd-editor/src/internal-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ERDEditorSchemaV3 } from '@dineug/erd-editor-schema';
import { useContext } from '@dineug/r-html';

export type ValuesType<T extends Record<string, string>> = T[keyof T];

Expand Down Expand Up @@ -39,3 +40,5 @@ export type EntityMeta = {
};

export type Unsubscribe = () => void;

export type Ctx = Parameters<typeof useContext>[0];
21 changes: 15 additions & 6 deletions packages/erd-editor/src/utils/globalEventObservable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { filter, fromEvent, map, merge, takeUntil } from 'rxjs';

import { isMouseEvent } from '@/utils/domEvent';
import { forwardMoveStartEvent } from '@/utils/internalEvents';

export const mousedown$ = fromEvent<MouseEvent>(window, 'mousedown');
export const mousemove$ = fromEvent<MouseEvent>(window, 'mousemove');
export const mouseup$ = fromEvent<MouseEvent>(window, 'mouseup');
Expand All @@ -8,18 +11,24 @@ export const touchstart$ = fromEvent<TouchEvent>(window, 'touchstart');
export const touchmove$ = fromEvent<TouchEvent>(window, 'touchmove');
export const touchend$ = fromEvent<TouchEvent>(window, 'touchend');

export const moveStart$ = merge(mousedown$, touchstart$);
export const moveEnd$ = merge(mouseup$, touchend$);
const forwardMoveStartEvent$ = fromEvent<
CustomEvent<Parameters<typeof forwardMoveStartEvent>[0]>
>(window, forwardMoveStartEvent.type).pipe(
map(event => event.detail.originEvent)
);

function isMousedownEvent(event: MouseEvent | TouchEvent): event is MouseEvent {
return event.type === 'mousedown';
}
export const moveStart$ = merge(
mousedown$,
touchstart$,
forwardMoveStartEvent$
);
export const moveEnd$ = merge(mouseup$, touchend$);

let prevX = 0;
let prevY = 0;

const subscription = moveStart$.subscribe(event => {
if (isMousedownEvent(event)) {
if (isMouseEvent(event)) {
prevX = event.clientX;
prevY = event.clientY;
} else {
Expand Down
Loading

0 comments on commit ce8f162

Please sign in to comment.