Skip to content

Commit

Permalink
Feature/open section in writer (#95)
Browse files Browse the repository at this point in the history
* Very basic prototype working to open sections in writer

* Open/close sections and update content in project json, save section history when switching between sections

* Adding to test project, add animation to folder collapse

* Prevent renaming to an existing section id, reset sectionHistory and activeSectionId upon rename

* Add Section title to main pane

* Updating test project
  • Loading branch information
zachhannum authored May 9, 2022
1 parent 06fb1f6 commit c039e67
Show file tree
Hide file tree
Showing 15 changed files with 458 additions and 55 deletions.
113 changes: 113 additions & 0 deletions app/renderer/components/BasicWriter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useEffect, useState, CSSProperties } from 'react';
import {
Plate,
PlateProvider,
usePlateEditorRef,
createPlugins,
} from '@udecode/plate';
import { ReactEditor } from 'slate-react';
import ScrollContainer from './ScrollContainer';
import useStore from 'renderer/store/useStore';
import {
deserializePlainText,
serializePlainText,
createDeserializePlainTextPlugin,
} from '../writer/serialize';
import { findItemDeep } from './TreeView/utilities';

const blankEditorValue = [
{
type: 'p',
children: [
{
text: '',
},
],
},
];

const BasicWriterComp = () => {
const editableProps = {
style: {
width: '100%',
minHeight: '100%',
boxSizing: 'border-box',
paddingBottom: '10vh',
display: 'flex',
flexDirection: 'column',
gap: '1em',
} as CSSProperties,
spellCheck: false,
autoFocus: true,
};
const activeSectionId = useStore((state) => state.activeSectionId);
const [initialValue, setInitialValue] = useState(blankEditorValue);
const [editorId, setEditorId] = useState('');
const editor = usePlateEditorRef();

const plugins = createPlugins([createDeserializePlainTextPlugin()]);

useEffect(() => {
if (editor) {
const { sectionHistory } = useStore.getState();
const history = sectionHistory?.get(activeSectionId);
if (history) {
editor.history = JSON.parse(JSON.stringify(history));
}
ReactEditor.focus(editor);
}
});

useEffect(() => {
if (activeSectionId != '') {
const { content } = useStore.getState();
const sectionContent = findItemDeep(content, activeSectionId)?.content;
if (sectionContent) {
const nodes = deserializePlainText(sectionContent);
if (nodes.length) {
setInitialValue(nodes);
}
} else {
setInitialValue(blankEditorValue);
}
setEditorId(activeSectionId);
}
}, [activeSectionId]);

const handleChange = () => {
if (activeSectionId != '') {
const { setSectionHistory } = useStore.getState();
const { updateSectionContent } = useStore.getState();
setSectionHistory(activeSectionId, editor.history);
updateSectionContent(
activeSectionId,
serializePlainText(editor.children)
);
}
};

return (
<ScrollContainer>
<Plate
key={editorId}
id={editorId}
plugins={plugins}
editableProps={editableProps}
onChange={handleChange}
initialValue={initialValue}
/>
</ScrollContainer>
);
};

const BasicWriter = () => {
const activeSectionId = useStore((state) => state.activeSectionId);

return (
<PlateProvider id={activeSectionId}>
<BasicWriterComp />
</PlateProvider>
);
};

export default BasicWriter;
6 changes: 3 additions & 3 deletions app/renderer/components/ScrollContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ const Scroller = styled.div`
`;

const Padding = styled.div`
max-width: 700px;
padding-top: 10vh;
padding-bottom: 10vh;
max-width: 500px;
height: 100%;
width: 100%;
`;

type Props = {
Expand Down
56 changes: 47 additions & 9 deletions app/renderer/components/TreeView/SortableTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { createPortal } from 'react-dom';
import {
Announcements,
Expand All @@ -15,7 +15,6 @@ import {
MeasuringStrategy,
DropAnimation,
defaultDropAnimation,
Modifier,
} from '@dnd-kit/core';
import {
SortableContext,
Expand All @@ -27,14 +26,14 @@ import {
buildTree,
flattenTree,
getProjection,
getChildCount,
removeItem,
removeChildrenOf,
setProperty,
} from './utilities';
import type { FlattenedItem, SensorContext } from './types';
import { Sections } from 'types/types';
import { SortableTreeItem } from './components';
import useStore from 'renderer/store/useStore';

const measuring = {
droppable: {
Expand Down Expand Up @@ -62,7 +61,6 @@ export function SortableTree({
indentationWidth = 20,
removable,
}: Props) {
// const [items, setItems] = useState(() => defaultItems);
const [activeId, setActiveId] = useState<string | null>(null);
const [overId, setOverId] = useState<string | null>(null);
const [offsetLeft, setOffsetLeft] = useState(0);
Expand Down Expand Up @@ -114,6 +112,29 @@ export function SortableTree({
? flattenedItems.find(({ id }) => id === activeId)
: null;

const animateOnCollapseRefCount = useStore(
(state) => state.animatingCollapseRefCount
);
const [animateInFromCollapseStartIdx, setAnimateInFromCollapseStartIdx] =
useState(-1);
const [animateInFromCollapseEndIdx, setAnimateInFromCollapseEndIdx] =
useState(-1);
useEffect(() => {
if (animateOnCollapseRefCount === 0) {
setAnimateInFromCollapseStartIdx(-1);
setAnimateInFromCollapseEndIdx(-1);
}
}, [animateOnCollapseRefCount]);
const isIndexBetweenStartAndEndAnimationIdx = useCallback(
(val: number) => {
return (
val > animateInFromCollapseStartIdx &&
val <= animateInFromCollapseEndIdx
);
},
[animateInFromCollapseEndIdx, animateInFromCollapseStartIdx]
);

useEffect(() => {
sensorContext.current = {
items: flattenedItems,
Expand Down Expand Up @@ -153,18 +174,22 @@ export function SortableTree({
>
<SortableContext items={sortedIds} strategy={verticalListSortingStrategy}>
{flattenedItems.map(
({ id, children, canHaveChildren, collapsed, depth }) => (
({ id, canHaveChildren, collapsed, depth }, index) => (
<SortableTreeItem
key={id}
id={id}
animateIndex={
isIndexBetweenStartAndEndAnimationIdx(index) ? index - animateInFromCollapseStartIdx : 0
}
animateIn={isIndexBetweenStartAndEndAnimationIdx(index)}
value={id}
depth={id === activeId && projected ? projected.depth : depth}
indentationWidth={indentationWidth}
indicator={indicator}
collapsed={Boolean(collapsed && children.length)}
collapsed={Boolean(collapsed && canHaveChildren)}
canHaveChildren={canHaveChildren}
onCollapse={
children.length ? () => handleCollapse(id) : undefined
canHaveChildren ? () => handleCollapse(id) : undefined
}
onRemove={removable ? () => handleRemove(id) : undefined}
/>
Expand All @@ -178,6 +203,8 @@ export function SortableTree({
{activeId && activeItem ? (
<SortableTreeItem
id={activeId}
animateIndex={-1}
animateIn={false}
depth={activeItem.depth}
clone
value={activeId}
Expand Down Expand Up @@ -253,7 +280,18 @@ export function SortableTree({
}

function handleCollapse(id: string) {
console.log("handling collapse");
const item = flattenedItems.find((item) => item.id === id);
if (item?.collapsed) {
const startIdx = flattenedItems.findIndex((item) => item.id === id);
const endIdx = startIdx + item.children.length;
setAnimateInFromCollapseStartIdx(startIdx);
setAnimateInFromCollapseEndIdx(endIdx);
} else {
//fallback in case item is collapsed before animation finishes
setAnimateInFromCollapseStartIdx(-1);
setAnimateInFromCollapseEndIdx(-1);
useStore.getState().resetAnimatingCollapseRefCount();
}
onItemsSorted(
setProperty(items, id, 'collapsed', (value) => {
return !value;
Expand Down Expand Up @@ -282,7 +320,7 @@ export function SortableTree({
}
}

const clonedItems: FlattenedItem[] =flattenTree(items);
const clonedItems: FlattenedItem[] = flattenTree(items);
const overIndex = clonedItems.findIndex(({ id }) => id === overId);
const activeIndex = clonedItems.findIndex(({ id }) => id === activeId);
const sortedItems = arrayMove(clonedItems, activeIndex, overIndex);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {CSSProperties} from 'react';
import {AnimateLayoutChanges, useSortable} from '@dnd-kit/sortable';
import {CSS} from '@dnd-kit/utilities';
import { CSSProperties, useEffect } from 'react';
import { AnimateLayoutChanges, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

import {TreeItem, Props as TreeItemProps} from './TreeItem';
import { TreeItem, Props as TreeItemProps } from './TreeItem';

interface Props extends TreeItemProps {
id: string;
}

const animateLayoutChanges: AnimateLayoutChanges = ({isSorting, wasDragging}) =>
isSorting || wasDragging ? false : true;
const animateLayoutChanges: AnimateLayoutChanges = ({
isSorting,
wasDragging,
}) => (isSorting || wasDragging ? false : true);

export function SortableTreeItem({id, depth, ...props}: Props) {
export function SortableTreeItem({ id, depth, ...props }: Props) {
const {
attributes,
isDragging,
Expand Down
Loading

0 comments on commit c039e67

Please sign in to comment.