Skip to content

Commit

Permalink
feat: column DnD
Browse files Browse the repository at this point in the history
  • Loading branch information
dineug committed Dec 18, 2023
1 parent abb0deb commit 9c2211c
Show file tree
Hide file tree
Showing 20 changed files with 476 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const root = css`
&[data-selected] {
border: 1px solid var(--table-select);
}
.column-row-move {
transition: transform 0.3s;
}
`;

export const header = css`
Expand Down
150 changes: 145 additions & 5 deletions packages/erd-editor/src/components/erd/canvas/table/Table.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
import { FC, html, repeat } from '@dineug/r-html';
import {
createRef,
FC,
html,
observable,
onMounted,
onUpdated,
ref,
repeat,
} from '@dineug/r-html';
import { Subscription } from 'rxjs';

import { useAppContext } from '@/components/appContext';
import Column from '@/components/erd/canvas/table/column/Column';
import EditInput from '@/components/primitives/edit-input/EditInput';
import Icon from '@/components/primitives/icon/Icon';
import { Show } from '@/constants/schema';
import {
dragendColumnAction,
editTableAction,
editTableEndAction,
focusTableAction,
} from '@/engine/modules/editor/atom.actions';
import {
dragoverColumnAction$,
dragstartColumnAction$,
} from '@/engine/modules/editor/generator.actions';
import { FocusType } from '@/engine/modules/editor/state';
import {
changeTableCommentAction,
changeTableNameAction,
} from '@/engine/modules/table/atom.actions';
import { removeTableAction$ } from '@/engine/modules/table/generator.actions';
import { addColumnAction$ } from '@/engine/modules/table-column/generator.actions';
import { useUnmounted } from '@/hooks/useUnmounted';
import { Table } from '@/internal-types';
import { bHas } from '@/utils/bit';
import { calcTableHeight, calcTableWidths } from '@/utils/calcTable';
import { query } from '@/utils/collection/query';
import { onPrevent } from '@/utils/domEvent';
import { openColorPickerAction } from '@/utils/emitter';
import { simpleShortcutToString } from '@/utils/keyboard-shortcut';
import { dragendColumnAllAction, openColorPickerAction } from '@/utils/emitter';
import { FlipAnimation } from '@/utils/flipAnimation';
import { isMod, simpleShortcutToString } from '@/utils/keyboard-shortcut';
import { fromShadowDraggable } from '@/utils/rx-operators/fromShadowDraggable';
import { takeUnsubscribe } from '@/utils/rx-operators/takeUnsubscribe';

import * as styles from './Table.styles';
import { useFocusTable } from './useFocusTable';
Expand All @@ -35,11 +54,23 @@ export type TableProps = {

const Table: FC<TableProps> = (props, ctx) => {
const app = useAppContext(ctx);
const root = createRef<HTMLDivElement>();
const { hasEdit, hasFocus, hasSelectColumn } = useFocusTable(
ctx,
props.table.id
);
const { onMoveStart } = useMoveTable(ctx, props);
const { addUnsubscribe } = useUnmounted();

const state = observable({
dragstartId: null as string | null,
});

const flipAnimation = new FlipAnimation(
root,
'.column-row',
'column-row-move'
);

const handleAddColumn = () => {
const { store } = app.value;
Expand Down Expand Up @@ -96,6 +127,98 @@ const Table: FC<TableProps> = (props, ctx) => {
);
};

const handleMoveColumn = (targetId: string, targetTableId: string) => {
const { store } = app.value;
const {
editor: { draggableColumn },
} = store.state;
if (!draggableColumn || draggableColumn.columnIds.includes(targetId)) {
return;
}

flipAnimation.snapshot();
store.dispatch(dragoverColumnAction$(targetId, targetTableId));
};

let draggableColumnSubscription: Subscription | null = null;

const draggableColumnSubscribe = () => {
const $root = root.value;
if (!$root || draggableColumnSubscription) return;

const elements = Array.from<HTMLElement>(
$root.querySelectorAll('.column-row')
);
elements.forEach(el => el.classList.add('none-hover'));

const cleanup = () => {
elements.forEach(el => el.classList.remove('none-hover'));
draggableColumnSubscription = null;
state.dragstartId = null;
};

draggableColumnSubscription = fromShadowDraggable(elements, el => ({
targetId: el.dataset.id as string,
targetTableId: el.dataset.tableId as string,
}))
.pipe(takeUnsubscribe(cleanup))
.subscribe({
next: ({ targetId, targetTableId }) => {
handleMoveColumn(targetId, targetTableId);
},
complete: cleanup,
});
};

const handleDragstartColumn = (event: DragEvent) => {
const { store } = app.value;
const {
editor: { focusTable },
} = store.state;
const $target = event.target as HTMLElement | null;
if (!$target || !focusTable || !focusTable.columnId) {
return;
}

const dragstartId = $target.dataset?.id;
if (!dragstartId) return;

state.dragstartId = dragstartId;

store.dispatch(dragstartColumnAction$(isMod(event)));
draggableColumnSubscribe();
};

const handleDragendColumn = () => {
const { store, emitter } = app.value;
store.dispatch(dragendColumnAction());
emitter.emit(dragendColumnAllAction());
};

const handleDragenter = () => {
const { store } = app.value;
const {
editor: { draggableColumn },
} = store.state;
if (!draggableColumn) return;

draggableColumnSubscribe();
};

onUpdated(() => flipAnimation.play());

onMounted(() => {
const { emitter } = app.value;

addUnsubscribe(
emitter.on({
dragendColumnAll: () => {
draggableColumnSubscription?.unsubscribe();
},
})
);
});

return () => {
const { store, keyBindingMap } = app.value;
const { editor, settings, collections } = store.state;
Expand All @@ -104,9 +227,17 @@ const Table: FC<TableProps> = (props, ctx) => {
const tableWidths = calcTableWidths(table, store.state);
const height = calcTableHeight(table);

const isGhostColumn =
state.dragstartId !== null &&
!table.columnIds.includes(state.dragstartId);

const columns = query(collections)
.collection('tableColumnEntities')
.selectByIds(table.columnIds);
.selectByIds(
isGhostColumn
? [...table.columnIds, state.dragstartId as string]
: table.columnIds
);

return html`
<div
Expand All @@ -118,6 +249,7 @@ const Table: FC<TableProps> = (props, ctx) => {
width: `${tableWidths.width}px`,
height: `${height}px`,
}}
${ref(root)}
?data-selected=${selected}
?data-focus-border=${selected}
data-id=${table.id}
Expand Down Expand Up @@ -199,7 +331,11 @@ const Table: FC<TableProps> = (props, ctx) => {
: null}
</div>
</div>
<div @dragenter=${onPrevent} @dragover=${onPrevent}>
<div
@dragenter=${handleDragenter}
@dragenter=${onPrevent}
@dragover=${onPrevent}
>
${repeat(
columns,
column => column.id,
Expand All @@ -226,6 +362,10 @@ const Table: FC<TableProps> = (props, ctx) => {
editDataType=${hasEdit(FocusType.columnDataType, column.id)}
editDefault=${hasEdit(FocusType.columnDefault, column.id)}
editComment=${hasEdit(FocusType.columnComment, column.id)}
draggable=${true}
ghost=${isGhostColumn && column.id === state.dragstartId}
.onDragstart=${handleDragstartColumn}
.onDragend=${handleDragendColumn}
/>
`
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,29 @@ export const root = css`
background-color: var(--column-hover);
}
&.hover {
&[data-hover] {
background-color: var(--column-hover);
}
&.selected {
&[data-selected] {
background-color: var(--column-select);
}
& > .column-col {
padding: ${COLUMN_PADDING}px ${INPUT_MARGIN_RIGHT}px ${COLUMN_PADDING}px 0;
}
&.none-hover {
background-color: transparent;
}
&[data-dragging] {
opacity: 0.5;
}
&[data-ghost] {
visibility: hidden;
}
`;

export const iconButton = css`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export type ColumnProps = {
editDataType: boolean;
editDefault: boolean;
editComment: boolean;
draggable?: boolean;
ghost?: boolean;
onDragstart?: (event: DragEvent) => void;
onDragend?: (event: DragEvent) => void;
};

type ColumnOrderTpl = {
Expand Down Expand Up @@ -324,11 +328,20 @@ const Column: FC<ColumnProps> = (props, ctx) => {
const { editor } = store.state;
const { column, selected } = props;
const hover = Boolean(editor.hoverColumnMap[column.id]);
const dragging = editor.draggingColumnMap[column.id];

return html`
<div
class=${['column-row', styles.root, { selected, hover }]}
class=${['column-row', styles.root]}
data-id=${column.id}
data-table-id=${column.tableId}
?data-selected=${selected}
?data-hover=${hover}
?data-dragging=${dragging}
?data-ghost=${props.ghost}
draggable=${props.draggable ? 'true' : 'false'}
@dragstart=${props.onDragstart}
@dragend=${props.onDragend}
>
<${ColumnKey} keys=${column.ui.keys} />
${repeat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const row = css`
background-color: transparent;
}
&.draggable {
&.dragging {
opacity: 0.5;
}
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,21 @@ const IndexesColumn: FC<IndexesColumnProps> = (props, ctx) => {
const $target = event.target as HTMLElement | null;
if (!$root || !$target) return;

const id = $target.dataset.id as string;
const id = $target.dataset?.id;
if (!id) return;

const elements = Array.from<HTMLElement>(
$root.querySelectorAll(`.${styles.row}`)
);
elements.forEach(el => el.classList.add('none-hover'));
$target.classList.add('draggable');
$target.classList.add('dragging');

fromShadowDraggable(elements).subscribe({
fromShadowDraggable(elements, el => el.dataset.id as string).subscribe({
next: targetId => {
handleMove(id, targetId);
},
complete: () => {
$target.classList.remove('draggable');
$target.classList.remove('dragging');
elements.forEach(el => el.classList.remove('none-hover'));
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const columnOrderItem = css`
fill: var(--foreground);
}
&.draggable {
&.dragging {
opacity: 0.5;
}
`;
15 changes: 9 additions & 6 deletions packages/erd-editor/src/components/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,22 @@ const Settings: FC<SettingsProps> = (props, ctx) => {
const $target = event.target as HTMLElement | null;
if (!$root || !$target) return;

const columnType = Number($target.dataset.id);
const id = $target.dataset?.id;
if (!id) return;

const columnType = Number(id);
const elements = Array.from<HTMLElement>(
$root.querySelectorAll(`.${styles.columnOrderItem}`)
);
elements.forEach(el => el.classList.add('none-hover'));
$target.classList.add('draggable');
$target.classList.add('dragging');

fromShadowDraggable(elements).subscribe({
next: id => {
handleChangeColumnOrderAction(columnType, Number(id));
fromShadowDraggable(elements, el => el.dataset.id as string).subscribe({
next: target => {
handleChangeColumnOrderAction(columnType, Number(target));
},
complete: () => {
$target.classList.remove('draggable');
$target.classList.remove('dragging');
elements.forEach(el => el.classList.remove('none-hover'));
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ const Column: FC<ColumnProps> = (props, ctx) => {

return html`
<div
class=${['column-row', styles.root, { selected }]}
class=${['column-row', styles.root]}
data-id=${column.id}
?data-selected=${selected}
>
<${ColumnKey} keys=${column.ui.keys} />
${repeat(
Expand Down
1 change: 1 addition & 0 deletions packages/erd-editor/src/engine/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const ChangeActionTypes: ReadonlyArray<ActionType> = [
'column.changePrimaryKey',
'column.changeUnique',
'column.changeNotNull',
'column.move',
// relationship
'relationship.add',
'relationship.remove',
Expand Down
Loading

0 comments on commit 9c2211c

Please sign in to comment.