diff options
author | bobzel <zzzman@gmail.com> | 2022-08-04 17:03:21 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2022-08-04 17:03:21 -0400 |
commit | abdbf5c657b9d1d9a26a4d46dda2097be152f4ff (patch) | |
tree | 778cc9af4160e416b6236a8b072bbc64533ce969 /src | |
parent | dbf21b19ecc70047a580023104d05aed0d43c946 (diff) |
fixed undo/redo for notetaking view. fixed list undo/redo for schemaheaderfields -- not very elegant though.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/collections/CollectionNoteTakingView.tsx | 52 | ||||
-rw-r--r-- | src/client/views/collections/CollectionNoteTakingViewColumn.tsx | 39 | ||||
-rw-r--r-- | src/fields/List.ts | 72 | ||||
-rw-r--r-- | src/fields/util.ts | 12 |
4 files changed, 80 insertions, 95 deletions
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index f24b98621..1bf5b7d86 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -42,12 +42,10 @@ export type collectionNoteTakingViewProps = { @observer export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() { - _autoHeightDisposer?: IReactionDisposer; + _disposers: { [key: string]: IReactionDisposer } = {}; _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); - // _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = []; - // @observable _docsByColumnHeader = new Map<string, Doc[]>(); - //TODO: need to make sure that we save the mapping + @observable columnStartXCoords: number[] = []; @observable docsDraggedRowCol: number[] = []; @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; // used to force the document decoration to update when scrolling @@ -86,7 +84,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @computed get numGroupColumns() { return this.columnHeaders.length; } - @observable columnStartXCoords: number[] = []; @computed get PanelWidth() { return this.props.PanelWidth(); } @@ -100,11 +97,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti super(props); if (this.columnHeaders === undefined) { this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]); - this.columnStartXCoords = [0]; // add all of the docs that have not been added to a column to this new column } else { - const numHeaders = this.columnHeaders.length; - this.resizeColumns(numHeaders); + this.resizeColumns(this.columnHeaders.length); } } @@ -126,7 +121,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti // [CAVEATS] (1) keep track of the offsetting // (2) documentView gets unmounted as you remove it from the list - get Sections() { + @computed get Sections() { + TraceMobx(); const columnHeaders = this.columnHeaders; let docs = this.childDocs; const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); @@ -169,16 +165,21 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti componentDidMount() { super.componentDidMount?.(); document.addEventListener('pointerup', this.removeDocDragHighlight, true); - this._autoHeightDisposer = reaction( + this._disposers.autoHeight = reaction( () => this.layoutDoc._autoHeight, autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))))) ); + this._disposers.headers = reaction( + () => this.columnHeaders.slice(), + headers => this.resizeColumns(headers.length), + { fireImmediately: true } + ); } componentWillUnmount() { document.removeEventListener('pointerup', this.removeDocDragHighlight, true); super.componentWillUnmount(); - this._autoHeightDisposer?.(); + Object.keys(this._disposers).forEach(key => this._disposers[key]()); } @action @@ -315,9 +316,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti // how to get the width of a document. Currently returns the width of the column (minus margins) // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) getDocWidth(d: Doc) { - const heading = (d[this.notetakingCategoryField] as object) ?? 'unset'; - const castedSectionValue = heading.toString(); - const existingHeader = this.columnHeaders.find(sh => sh.heading === castedSectionValue); + const heading = StrCast(d[this.notetakingCategoryField], 'unset'); + const existingHeader = this.columnHeaders.find(sh => sh.heading === heading); const colStartXCoords = this.columnStartXCoords; if (!existingHeader) { return 1000; @@ -352,6 +352,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } // called when a column is either added or deleted. This function creates n evenly spaced columns + @action resizeColumns = (n: number) => { const totalWidth = this.PanelWidth; const dividerWidth = 32; @@ -368,8 +369,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti // This function is used to preview where a document will drop in a column once a drag is complete. @action - onPointerOver = (ex: number, ey: number) => { - if (this.childDocList) { + onPointerOver = (buttons: boolean, ex: number, ey: number) => { + if (this.childDocList && buttons) { // get the current docs for the column based on the mouse's x coordinate // will use again later, which is why we're saving as local const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap; @@ -500,7 +501,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti onExternalDrop = async (e: React.DragEvent): Promise<void> => { const targInd = this.docsDraggedRowCol?.[0] || 0; super.onExternalDrop(e, {}, docus => { - this.onPointerOver(e.clientX, e.clientY); + this.onPointerOver(true, e.clientX, e.clientY); docus?.map(doc => this.addDocument(doc)); const newDoc = this.childDocs.lastElement(); const docs = this.childDocList; @@ -515,6 +516,11 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti headings = () => Array.from(this.Sections); refList: any[] = []; + editableViewProps = () => ({ + GetValue: () => '', + SetValue: this.addGroup, + contents: '+ New Column', + }); sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const type = 'number'; @@ -544,7 +550,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti columnHeaders={this.columnHeaders} Document={this.props.Document} DataDoc={this.props.DataDoc} - resizeColumns={this.resizeColumns.bind(this)} + resizeColumns={this.resizeColumns} renderChildren={this.children} numGroupColumns={this.numGroupColumns} gridGap={this.gridGap} @@ -561,11 +567,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti type={type} createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} - editableViewProps={{ - GetValue: () => '', - SetValue: this.addGroup, - contents: '+ New Column', - }} + editableViewProps={this.editableViewProps} /> ); }; @@ -622,7 +624,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const col = this.sectionNoteTaking(sections[i][0], sections[i][1]); eles.push(col); if (i < sections.length - 1) { - eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i + 1} setColumnStartXCoords={this.setColumnStartXCoords.bind(this)} xMargin={this.xMargin} />); + eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i + 1} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />); } } return eles; @@ -705,7 +707,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti }} onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))} - onPointerOver={e => this.onPointerOver(e.clientX, e.clientY)} + onPointerOver={e => this.onPointerOver(e.buttons ? true : false, e.clientX, e.clientY)} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}> diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 8452d895f..9b7518c60 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -47,7 +47,7 @@ interface CSVFieldColumnProps { screenToLocalTransform: () => Transform; observeHeight: (myref: any) => void; unobserveHeight: (myref: any) => void; - editableViewProps: any; + editableViewProps: () => any; resizeColumns: (n: number) => void; columnStartXCoords: number[]; PanelWidth: number; @@ -112,7 +112,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu @action headingChanged = (value: string, shiftDown?: boolean) => { - console.log('HEADING CH'); const castedValue = this.getValue(value); if (castedValue) { if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { @@ -144,43 +143,17 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu return this.props.addDocument?.(newDoc) || false; }; + @undoBatch @action deleteColumn = () => { - if (!this.props.columnHeaders) { - return; - } - if (this.props.headingObject) { + if (this.props.columnHeaders && this.props.headingObject) { const index = this.props.columnHeaders.indexOf(this.props.headingObject); - const newIndex = index == 0 ? 1 : index - 1; - const newHeader = this.props.columnHeaders[newIndex]; - this.props.docList.forEach(d => (d[this.props.pivotField] = newHeader.heading.toString())); + this.props.docList.forEach(d => (d[this.props.pivotField] = 'unset')); this.props.columnHeaders.splice(index, 1); this.props.resizeColumns(this.props.columnHeaders.length); } }; - headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); - - //TODO: I think this is where I'm supposed to edit stuff - startDrag = (e: PointerEvent, down: number[], delta: number[]) => { - console.log('in startDrag'); - // is MakeAlias a way to make a copy of a doc without rendering it? - const alias = Doc.MakeAlias(this.props.Document); - // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); - alias._width = this.columnWidth; - alias._pivotField = undefined; - let value = this.getValue(this._heading); - value = typeof value === 'string' ? `"${value}"` : value; - alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); - if (alias.viewSpecScript) { - const options = { hideSource: false }; - DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options); - console.log('in startDrag'); - return true; - } - return false; - }; - menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; @@ -256,7 +229,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu @computed get innards() { TraceMobx(); - console.log('INNARD START'); const key = this.props.pivotField; const heading = this._heading; const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin; @@ -274,7 +246,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu }}> <div className="collectionNoteTakingView-sectionHeader-subCont" - onPointerDown={this.headerDown} title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''} style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}> <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} /> @@ -314,7 +285,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> </div> <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> - <EditableView {...this.props.editableViewProps} /> + <EditableView {...this.props.editableViewProps()} /> </div> {this.props.columnHeaders?.length && this.props.columnHeaders.length > 1 && ( <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> diff --git a/src/fields/List.ts b/src/fields/List.ts index b15548327..5cc4ca543 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -1,25 +1,25 @@ -import { action, observable } from "mobx"; -import { alias, list, serializable } from "serializr"; -import { DocServer } from "../client/DocServer"; -import { ScriptingGlobals } from "../client/util/ScriptingGlobals"; -import { afterDocDeserialize, autoObject, Deserializable } from "../client/util/SerializationHelper"; -import { Field } from "./Doc"; -import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; -import { ObjectField } from "./ObjectField"; -import { ProxyField } from "./Proxy"; -import { RefField } from "./RefField"; -import { listSpec } from "./Schema"; -import { Cast } from "./Types"; -import { deleteProperty, getter, setter, updateFunction } from "./util"; +import { action, observable } from 'mobx'; +import { alias, list, serializable } from 'serializr'; +import { DocServer } from '../client/DocServer'; +import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; +import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper'; +import { Field } from './Doc'; +import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; +import { ObjectField } from './ObjectField'; +import { ProxyField } from './Proxy'; +import { RefField } from './RefField'; +import { listSpec } from './Schema'; +import { Cast } from './Types'; +import { deleteProperty, getter, setter, updateFunction } from './util'; const listHandlers: any = { /// Mutator methods copyWithin() { - throw new Error("copyWithin not supported yet"); + throw new Error('copyWithin not supported yet'); }, fill(value: any, start?: number, end?: number) { if (value instanceof RefField) { - throw new Error("fill with RefFields not supported yet"); + throw new Error('fill with RefFields not supported yet'); } const res = this[Self].__fields.fill(value, start, end); this[Update](); @@ -44,7 +44,7 @@ const listHandlers: any = { } } const res = list.__fields.push(...items); - this[Update]({ op: "$addToSet", items, length: length + items.length }); + this[Update]({ op: '$addToSet', items, length: length + items.length }); return res; }), reverse() { @@ -78,8 +78,13 @@ const listHandlers: any = { } } const res = list.__fields.splice(start, deleteCount, ...items); - this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed, length: list.__fields.length } : - items.length && !deleteCount && start === list.__fields.length ? { op: "$addToSet", items, length: list.__fields.length } : undefined); + this[Update]( + items.length === 0 && deleteCount + ? { op: '$remFromSet', items: removed, length: list.__fields.length } + : items.length && !deleteCount && start === list.__fields.length + ? { op: '$addToSet', items, length: list.__fields.length } + : undefined + ); return res.map(toRealField); }), unshift(...items: any[]) { @@ -98,7 +103,6 @@ const listHandlers: any = { const res = this[Self].__fields.unshift(...items); this[Update](); return res; - }, /// Accessor methods concat: action(function (this: any, ...items: any[]) { @@ -198,7 +202,7 @@ const listHandlers: any = { }, [Symbol.iterator]() { return this[Self].__realFields().values(); - } + }, }; function toObjectField(field: Field) { @@ -217,14 +221,14 @@ function listGetter(target: any, prop: string | number | symbol, receiver: any): } interface ListSpliceUpdate<T> { - type: "splice"; + type: 'splice'; index: number; added: T[]; removedCount: number; } interface ListIndexUpdate<T> { - type: "update"; + type: 'update'; index: number; newValue: T; } @@ -233,7 +237,7 @@ type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>; type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T; -@Deserializable("list") +@Deserializable('list') class ListImpl<T extends Field> extends ObjectField { constructor(fields?: T[]) { super(); @@ -244,14 +248,16 @@ class ListImpl<T extends Field> extends ObjectField { getOwnPropertyDescriptor: (target, prop) => { if (prop in target.__fields) { return { - configurable: true,//TODO Should configurable be true? + configurable: true, //TODO Should configurable be true? enumerable: true, }; } return Reflect.getOwnPropertyDescriptor(target, prop); }, deleteProperty: deleteProperty, - defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, + defineProperty: () => { + throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); + }, }); this[SelfProxy] = list; if (fields) { @@ -265,7 +271,7 @@ class ListImpl<T extends Field> extends ObjectField { // this requests all ProxyFields at the same time to avoid the overhead // of separate network requests and separate updates to the React dom. private __realFields() { - const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: (f instanceof ProxyField) ? f.promisedValue() : "" })); + const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: f instanceof ProxyField ? f.promisedValue() : '' })); // if we find any ProxyFields that don't have a current value, then // start the server request for all of them if (promised.length) { @@ -282,7 +288,7 @@ class ListImpl<T extends Field> extends ObjectField { return this.__fields.map(toRealField); } - @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize }))) + @serializable(alias('fields', list(autoObject(), { afterDeserialize: afterDocDeserialize }))) private get __fields() { return this.___fields; } @@ -299,7 +305,7 @@ class ListImpl<T extends Field> extends ObjectField { } [Copy]() { - const copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f); + const copiedData = this[Self].__fields.map(f => (f instanceof ObjectField ? f[Copy]() : f)); const deepCopy = new ListImpl<T>(copiedData as any); return deepCopy; } @@ -313,7 +319,7 @@ class ListImpl<T extends Field> extends ObjectField { const update = this[OnUpdate]; // update && update(diff); update?.(diff); - } + }; private [Self] = this; private [SelfProxy]: any; @@ -328,9 +334,9 @@ class ListImpl<T extends Field> extends ObjectField { export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[]; export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any; -ScriptingGlobals.add("List", List); +ScriptingGlobals.add('List', List); ScriptingGlobals.add(function compareLists(l1: any, l2: any) { - const L1 = Cast(l1, listSpec("string"), []); - const L2 = Cast(l2, listSpec("string"), []); + const L1 = Cast(l1, listSpec('string'), []); + const L2 = Cast(l2, listSpec('string'), []); return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true); -}, "compare two lists");
\ No newline at end of file +}, 'compare two lists'); diff --git a/src/fields/util.ts b/src/fields/util.ts index 41e723119..d87bb6656 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -33,6 +33,7 @@ import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; +import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; @@ -455,7 +456,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ? { redo: action(() => { diff.items.forEach((item: any) => { - const ind = receiver[prop].indexOf(item.value ? item.value() : item); + const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ? item.value() : item); ind !== -1 && receiver[prop].splice(ind, 1); }); lastValue = ObjectField.MakeCopy(receiver[prop]); @@ -463,8 +464,13 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any undo: () => { // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo diff.items.forEach((item: any) => { - const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item); - ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); + if (item instanceof SchemaHeaderField) { + const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); + ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item); + } else { + const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item); + ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); + } }); lastValue = ObjectField.MakeCopy(receiver[prop]); }, |