Skip to content

Commit

Permalink
Save board type and lane collapse state per view
Browse files Browse the repository at this point in the history
  • Loading branch information
mgmeyers committed Apr 28, 2024
1 parent d1e25d9 commit 8cb9447
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 83 deletions.
44 changes: 40 additions & 4 deletions src/KanbanView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WorkspaceLeaf,
} from 'obsidian';
import PQueue from 'p-queue';
import { Dispatch, StateUpdater, useCallback, useState } from 'preact/hooks';

import { KanbanFormat, SettingsModal } from './Settings';
import { Kanban } from './components/Kanban';
Expand All @@ -28,6 +29,7 @@ export const kanbanIcon = 'lucide-trello';
interface ViewEvents {
showLaneForm: () => void;
hotkey: (commandId: string) => void;
setView: (view: string) => void;
}

export class KanbanView extends TextFileView implements HoverParent {
Expand Down Expand Up @@ -169,6 +171,7 @@ export class KanbanView extends TextFileView implements HoverParent {

onunload(): void {
super.onunload();
this.plugin.cleanupViewStorage();
this.destroy();
}

Expand Down Expand Up @@ -314,28 +317,31 @@ export class KanbanView extends TextFileView implements HoverParent {
'lucide-view',
t('Board view'),
(evt) => {
const view = stateManager.getSetting(frontmatterKey);
const initialValue = this.plugin.getViewValue(this, 'view');
const settingValue = stateManager.getSetting(frontmatterKey);
const view = initialValue || settingValue;

new Menu()
.addItem((item) =>
item
.setTitle(t('View as board'))
.setIcon('lucide-trello')
.setChecked(view === 'basic' || view === 'board')
.onClick(() => this.setView('board'))
.onClick(() => this.emitter.emit('setView', 'board'))
)
.addItem((item) =>
item
.setTitle(t('View as table'))
.setIcon('lucide-table')
.setChecked(view === 'table')
.onClick(() => this.setView('table'))
.onClick(() => this.emitter.emit('setView', 'table'))
)
.addItem((item) =>
item
.setTitle(t('View as list'))
.setIcon('lucide-server')
.setChecked(view === 'list')
.onClick(() => this.setView('list'))
.onClick(() => this.emitter.emit('setView', 'list'))
)
.showAtMouseEvent(evt);
}
Expand Down Expand Up @@ -425,4 +431,34 @@ export class KanbanView extends TextFileView implements HoverParent {
abstract.)
*/
}

useStorage(key: string, defaultValue?: any): [any, Dispatch<StateUpdater<any>>] {
const initialValue = this.plugin.getViewValue(this, key);
const [state, setState] = useState<any>(initialValue ?? defaultValue);

if (initialValue === undefined && defaultValue !== undefined) {
this.plugin.setViewValue(this, key, defaultValue);
}

const override: typeof setState = useCallback(
(s) => {
setState((oldState: any) => {
let newState: any;

if (typeof s === 'function') {
newState = (s as any)(oldState);
} else {
newState = s;
}

this.plugin.setViewValue(this, key, newState);

return newState;
});
},
[key, setState]
);

return [state, override];
}
}
2 changes: 0 additions & 2 deletions src/StateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,6 @@ export class StateManager {
});
});

this.app.workspace.trigger('kanban:board-cards-archived', this.file, archived);

try {
this.setState(
update(board, {
Expand Down
10 changes: 8 additions & 2 deletions src/components/Item/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,19 @@ export const DraggableItem = memo(function DraggableItem(props: DraggableItemPro

const { itemIndex, ...innerProps } = props;

useDragHandle(measureRef, measureRef);
const bindHandle = useDragHandle(measureRef, measureRef);

const isMatch = search?.query ? innerProps.item.data.titleSearch.includes(search.query) : false;
const classModifiers: string[] = getItemClassModifiers(innerProps.item);

return (
<div ref={measureRef} className={c('item-wrapper')}>
<div
ref={(el) => {
measureRef.current = el;
bindHandle(el);
}}
className={c('item-wrapper')}
>
<div ref={elementRef} className={classcat([c('item'), ...classModifiers])}>
{props.isStatic ? (
<ItemInner
Expand Down
16 changes: 15 additions & 1 deletion src/components/Kanban.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classcat from 'classcat';
import update from 'immutability-helper';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/compat';
import { KanbanView } from 'src/KanbanView';
import { KanbanFormat } from 'src/Settings';
import { StateManager } from 'src/StateManager';
import { useIsAnythingDragging } from 'src/dnd/components/DragOverlay';
import { ScrollContainer } from 'src/dnd/components/ScrollContainer';
Expand Down Expand Up @@ -48,7 +49,20 @@ export const Kanban = ({ view, stateManager }: KanbanProps) => {
const maxArchiveLength = stateManager.useSetting('max-archive-size');
const dateColors = stateManager.useSetting('date-colors');
const tagColors = stateManager.useSetting('tag-colors');
const boardView = stateManager.useSetting(frontmatterKey);

const [boardView, setBoardView] = view.useStorage(
'view',
stateManager.getSetting(frontmatterKey)
);

useEffect(() => {
const onViewChange = (type: KanbanFormat) => {
setBoardView(type);
view.setView(type);
};
view.emitter.on('setView', onViewChange);
return () => view.emitter.off('setView', onViewChange);
}, [view]);

const closeLaneForm = useCallback(() => {
if (boardData?.children.length > 0) {
Expand Down
44 changes: 39 additions & 5 deletions src/components/Lane/Lane.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import animateScrollTo from 'animated-scroll-to';
import classcat from 'classcat';
import update from 'immutability-helper';
import { Fragment, memo, useCallback, useContext, useMemo, useRef, useState } from 'preact/compat';
import {
Fragment,
memo,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'preact/compat';
import {
DraggableProps,
Droppable,
Expand Down Expand Up @@ -58,14 +67,39 @@ function DraggableLaneRaw({
const measureRef = useRef<HTMLDivElement>(null);
const dragHandleRef = useRef<HTMLDivElement>(null);

useDragHandle(measureRef, dragHandleRef);
const bindHandle = useDragHandle(measureRef, dragHandleRef);

const shouldMarkItemsComplete = !!lane.data.shouldMarkItemsComplete;
const isCollapsed = !!lane.data.isCollapsed || !!forceCollapse;
const isCompactPrepend = insertionMethod === 'prepend-compact';
const shouldPrepend = isCompactPrepend || insertionMethod === 'prepend';

const titleRef = useRef(lane.data.title);
const indexRef = useRef(laneIndex);

const [isLaneCollapsed, setIsLaneCollapsed] = view.useStorage(
laneIndex.toString() + lane.data.title + '###' + 'isCollapsed',
view.plugin.getViewValue(
view,
indexRef.current.toString() + titleRef.current + '###' + 'isCollapsed'
) ?? !!lane.data.isCollapsed
);
const isCollapsed = isLaneCollapsed || !!forceCollapse;

useEffect(() => {
if (titleRef.current !== lane.data.title || indexRef.current !== laneIndex) {
delete view.plugin.storage[
indexRef.current.toString() + titleRef.current + '###' + 'isCollapsed'
];
view.plugin.saveLocalStorage();
titleRef.current = lane.data.title;
indexRef.current = laneIndex;
}
}, [lane.data.title, laneIndex]);

const toggleIsCollapsed = useCallback(() => {
setIsLaneCollapsed((collapsed: boolean) => {
return !collapsed;
});
boardModifiers.updateLane(
path,
update(lane, {
Expand All @@ -74,7 +108,7 @@ function DraggableLaneRaw({
},
})
);
}, [path, lane, boardModifiers]);
}, [path, lane, boardModifiers, setIsLaneCollapsed]);

const addItems = useCallback(
(items: Item[]) => {
Expand Down Expand Up @@ -152,7 +186,7 @@ function DraggableLaneRaw({
>
<CollapsedDropArea {...dropAreaProps}>
<LaneHeader
dragHandleRef={dragHandleRef}
bindHandle={bindHandle}
laneIndex={laneIndex}
lane={lane}
setIsItemInputVisible={isCompactPrepend ? setEditState : undefined}
Expand Down
8 changes: 4 additions & 4 deletions src/components/Lane/LaneHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import update from 'immutability-helper';
import { Menu } from 'obsidian';
import { RefObject, memo } from 'preact/compat';
import { memo } from 'preact/compat';
import { Dispatch, StateUpdater, useCallback, useContext, useEffect, useState } from 'preact/hooks';
import { useNestedEntityPath } from 'src/dnd/components/Droppable';
import { t } from 'src/lang/helpers';
Expand All @@ -19,7 +19,7 @@ import { LaneLimitCounter, LaneTitle } from './LaneTitle';
interface LaneHeaderProps {
lane: Lane;
laneIndex: number;
dragHandleRef: RefObject<HTMLDivElement>;
bindHandle: (el: HTMLElement) => void;
setIsItemInputVisible?: Dispatch<StateUpdater<EditState>>;
isCollapsed: boolean;
toggleIsCollapsed: () => void;
Expand Down Expand Up @@ -83,7 +83,7 @@ function LaneButtons({
export const LaneHeader = memo(function LaneHeader({
lane,
laneIndex,
dragHandleRef,
bindHandle,
setIsItemInputVisible,
isCollapsed,
toggleIsCollapsed,
Expand Down Expand Up @@ -134,7 +134,7 @@ export const LaneHeader = memo(function LaneHeader({
onDblClick={onDoubleClick}
className={c('lane-header-wrapper')}
>
<div className={c('lane-grip')} ref={dragHandleRef}>
<div className={c('lane-grip')} ref={bindHandle}>
<GripIcon />
</div>

Expand Down
20 changes: 11 additions & 9 deletions src/dnd/managers/DragManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import boxIntersect from 'box-intersect';
import { RefObject, useCallback, useContext, useEffect } from 'preact/compat';
import { RefObject, useCallback, useContext, useRef } from 'preact/compat';
import { StateManager } from 'src/StateManager';
import { handleDragOrPaste } from 'src/components/Item/helpers';

Expand Down Expand Up @@ -360,17 +360,19 @@ export function useDragHandle(
handleElement: RefObject<HTMLElement | null>
) {
const dndManager = useContext(DndManagerContext);
const unbind = useRef(() => {});

useEffect(() => {
const droppable = droppableElement.current;
const handle = handleElement.current;

if (!dndManager || !droppable || !handle) {
return;
return useCallback((el: HTMLElement) => {
if (handleElement.current !== el) {
unbind.current();
unbind.current = () => {};
}
if (!el) return;

const handle = el;
const onPointerDown = (e: PointerEvent) => {
if (e.defaultPrevented) return;
if (e.defaultPrevented || !dndManager || !droppableElement.current) return;
const droppable = droppableElement.current;

let node = e.targetNode;
while (node) {
Expand Down Expand Up @@ -486,7 +488,7 @@ export function useDragHandle(
handle.removeEventListener('pointerdown', onPointerDown);
handle.removeEventListener('touchstart', swallowTouchEvent);
};
}, [droppableElement, handleElement, dndManager]);
}, []);
}

export function createHTMLDndHandlers(stateManager: StateManager) {
Expand Down
Loading

0 comments on commit 8cb9447

Please sign in to comment.