From 5a425e5cf18115921ecb4e7cf931e65f45dab8e2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Aug 2022 11:18:14 -0400 Subject: fixed up presboxelement to not reference presbox.instance. fixing layout of presboxelement in treeview. fixing "removing" an inkMask without removing it from the collection so that it can be turned on and off. --- src/client/views/PropertiesView.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ef0e057dc..33f17047b 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -15,7 +15,7 @@ import { ComputedField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; -import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; @@ -1614,9 +1614,6 @@ export class PropertiesView extends React.Component { if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0; const type = PresBox.Instance.activeItem?.type; - const viewType = PresBox.Instance.activeItem?._viewType; - const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG; - const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking; return (
-- cgit v1.2.3-70-g09d2 From a4ce2913b8a15cdd4670002a4a74f1d86601348e Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 18:34:41 -0400 Subject: a bunch of mostly decorative cleanup to presBox --- src/client/views/PropertiesView.tsx | 6 +- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 558 +++++++---------------- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/client/views/nodes/trails/PresEnums.ts | 42 +- 5 files changed, 186 insertions(+), 424 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 33f17047b..2708c561d 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -58,7 +58,7 @@ export class PropertiesView extends React.Component { } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; - if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); return undefined; } @computed get isPres(): boolean { @@ -1612,7 +1612,7 @@ export class PropertiesView extends React.Component { ); } if (this.isPres) { - const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0; + const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0; const type = PresBox.Instance.activeItem?.type; return (
@@ -1622,7 +1622,7 @@ export class PropertiesView extends React.Component {
{this.editableTitle}
-
{PresBox.Instance?._selectedArray.size} selected
+
{PresBox.Instance?.selectedArray.size} selected
{PresBox.Instance?.listOfSelected}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 49228a808..bead5825c 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -239,7 +239,7 @@ export class TabDocView extends React.Component { pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; + const size: number = PresBox.Instance?.selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index ade098917..ac68ea281 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -12,7 +12,7 @@ import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -86,6 +86,7 @@ export class PresBox extends ViewBoxBaseComponent() { } private _disposers: { [name: string]: IReactionDisposer } = {}; + public selectedArray = new ObservableSet(); constructor(props: any) { super(props); @@ -93,6 +94,7 @@ export class PresBox extends ViewBoxBaseComponent() { } @observable public static Instance: PresBox; + @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @@ -103,14 +105,13 @@ export class PresBox extends ViewBoxBaseComponent() { @observable _dragArray: HTMLElement[] = []; @observable _pathBoolean: boolean = false; @observable _expandBoolean: boolean = false; - - @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView - @observable private transitionTools: boolean = false; - @observable private newDocumentTools: boolean = false; - @observable private progressivizeTools: boolean = false; - @observable private openMovementDropdown: boolean = false; - @observable private openEffectDropdown: boolean = false; - @observable private presentTools: boolean = false; + @observable _transitionTools: boolean = false; + @observable _newDocumentTools: boolean = false; + @observable _progressivizeTools: boolean = false; + @observable _openMovementDropdown: boolean = false; + @observable _openEffectDropdown: boolean = false; + @observable _presentTools: boolean = false; + @observable _treeViewMap: Map = new Map(); @computed get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); } @@ -123,8 +124,6 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } - @observable _treeViewMap: Map = new Map(); - @computed get tagDocs() { return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)); } @@ -137,22 +136,21 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } - @computed get scrollable(): boolean { + @computed get scrollable() { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; return false; } - @computed get panable(): boolean { + @computed get panable() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; return false; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; - if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); + if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } - @computed get isPres(): boolean { + @computed get isPres() { document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); document.addEventListener('keydown', PresBox.keyEventsWrapper, true); return true; } @@ -161,10 +159,9 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } - _selectedArray = new ObservableSet(); - clearSelectedArray = () => this._selectedArray.clear(); - addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc); - removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc); + clearSelectedArray = () => this.selectedArray.clear(); + addToSelectedArray = (doc: Doc) => this.selectedArray.add(doc); + removeFromSelectedArray = (doc: Doc) => this.selectedArray.delete(doc); _unmounting = false; @action @@ -448,7 +445,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc.presCollection = srcContext; } const presStatus = this.rootDoc.presStatus; - const selViewCache = Array.from(this._selectedArray); + const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); const self = this; @@ -756,27 +753,11 @@ export class PresBox extends ViewBoxBaseComponent() { } }); - setMovementName = action((movement: any, activeItem: Doc): string => { - let output: string = 'none'; - switch (movement) { - case PresMovement.Zoom: - output = 'Pan & Zoom'; - break; //Pan and zoom - case PresMovement.Pan: - output = 'Pan'; - break; //Pan - case PresMovement.Jump: - output = 'Jump cut'; - break; //Jump Cut - case PresMovement.None: - output = 'None'; - break; //None - default: - output = 'Zoom'; - activeItem.presMovement = 'zoom'; - break; //default set as zoom + movementName = action((activeItem: Doc) => { + if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { + activeItem.presMovement = 'zoom'; } - return output; + return StrCast(activeItem.presMovement); }); whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive))); @@ -822,16 +803,13 @@ export class PresBox extends ViewBoxBaseComponent() { /** * For sorting the array so that the order is maintained when it is dropped. */ - @action - sortArray = (): Doc[] => { - return this.childDocs.filter(doc => this._selectedArray.has(doc)); - }; + sortArray = () => this.childDocs.filter(doc => this.selectedArray.has(doc)); /** * Method to get the list of selected items in the order in which they have been selected */ @computed get listOfSelected() { - return Array.from(this._selectedArray).map((doc: Doc, index: any) => { + return Array.from(this.selectedArray).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) @@ -874,24 +852,18 @@ export class PresBox extends ViewBoxBaseComponent() { //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - if (!this._selectedArray.has(doc)) { + if (!this.selectedArray.has(doc)) { this.addToSelectedArray(doc); this._eleArray.push(ref); this._dragArray.push(drag); } else { this.removeFromSelectedArray(doc); - this.removeFromArray(this._eleArray, doc); - this.removeFromArray(this._dragArray, doc); + this._eleArray.splice(this._eleArray.indexOf(ref)); + this._dragArray.splice(this._dragArray.indexOf(drag)); } this.selectPres(); }; - removeFromArray = (arr: any[], val: any) => { - const index: number = arr.indexOf(val); - const ret: any[] = arr.splice(index, 1); - arr = ret; - }; - //Shift click @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { @@ -924,9 +896,7 @@ export class PresBox extends ViewBoxBaseComponent() { else this.regularSelect(doc, ref, drag, focus); }; - static keyEventsWrapper = (e: KeyboardEvent) => { - PresBox.Instance.keyEvents(e); - }; + static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance.keyEvents(e); // Key for when the presentaiton is active @action @@ -940,7 +910,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.layoutDoc.presStatus === 'edit') { undoBatch( action(() => { - for (const doc of this._selectedArray) { + for (const doc of this.selectedArray) { this.removeDocument(doc); } this.clearSelectedArray(); @@ -1027,13 +997,7 @@ export class PresBox extends ViewBoxBaseComponent() { } }; - getAllIndexes = (arr: Doc[], val: Doc): number[] => { - const indexes = []; - for (let i = 0; i < arr.length; i++) { - arr[i] === val && indexes.push(i); - } - return indexes; - }; + getAllIndexes = (arr: Doc[], val: Doc) => arr.map((doc, i) => (doc === val ? i : -1)).filter(i => i !== -1); // Adds the index in the pres path graphically @computed get order() { @@ -1152,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - this._selectedArray.forEach(doc => (doc.presTransition = timeInMS)); + this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)); }; // Converts seconds to ms and updates presTransition @@ -1161,7 +1125,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1.5) scale = 1.5; - this._selectedArray.forEach(doc => (doc.presZoom = scale)); + this.selectedArray.forEach(doc => (doc.presZoom = scale)); }; // Converts seconds to ms and updates presDuration @@ -1170,100 +1134,43 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - this._selectedArray.forEach(doc => (doc.presDuration = timeInMS)); + this.selectedArray.forEach(doc => (doc.presDuration = timeInMS)); }; /** * When the movement dropdown is changes */ @undoBatch - updateMovement = action((movement: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - switch (movement) { - case PresMovement.Zoom: //Pan and zoom - doc.presMovement = PresMovement.Zoom; - break; - case PresMovement.Pan: //Pan - doc.presMovement = PresMovement.Pan; - break; - case PresMovement.Jump: //Jump Cut - doc.presJump = true; - doc.presMovement = PresMovement.Jump; - break; - case PresMovement.None: - default: - doc.presMovement = PresMovement.None; - break; - } - }); - }); + updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement))); @undoBatch @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + this.selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + this.selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; - this._selectedArray.forEach(doc => { - doc.openDocument = activeItem.openDocument; - }); + this.selectedArray.forEach(doc => (doc.openDocument = activeItem.openDocument)); }; @undoBatch @action - updateEffectDirection = (effect: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); - switch (effect) { - case PresEffect.Left: - tagDoc.presEffectDirection = PresEffect.Left; - break; - case PresEffect.Right: - tagDoc.presEffectDirection = PresEffect.Right; - break; - case PresEffect.Top: - tagDoc.presEffectDirection = PresEffect.Top; - break; - case PresEffect.Bottom: - tagDoc.presEffectDirection = PresEffect.Bottom; - break; - case PresEffect.Center: - default: - tagDoc.presEffectDirection = PresEffect.Center; - break; - } - }); - }; + updateEffectDirection = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect)); @undoBatch @action - updateEffect = (effect: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); - //prettier-ignore - switch (effect) { - default: - case PresEffect.None: tagDoc.presEffect = PresEffect.None; break; - case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break; - case PresEffect.Fade: tagDoc.presEffect = PresEffect.Fade; break; - case PresEffect.Flip: tagDoc.presEffect = PresEffect.Flip; break; - case PresEffect.Roll: tagDoc.presEffect = PresEffect.Roll; break; - case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; - } - }); - }; + updateEffect = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffect = effect)); _batch: UndoManager.Batch | undefined = undefined; @@ -1272,6 +1179,46 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection; const isPinWithView: boolean = BoolCast(activeItem.presPinView); + const presEffect = (effect: PresEffect) => ( +
this.updateEffect(effect)}> + {effect} +
+ ); + const presMovement = (movement: PresMovement) => ( +
this.updateMovement(movement)}> + {movement} +
+ ); + const presDirection = (diretion: PresEffect, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const color = this.activeItem.presEffectDirection === diretion || (diretion === PresEffect.Center && !this.activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + return ( + {diretion}
}> +
this.updateEffectDirection(diretion)}> + {icon ? : null} +
+ + ); + }; + const inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void) => { + return ( + (this._batch = UndoManager.StartBatch('pres slider'))} + onPointerUp={() => this._batch?.end()} + onChange={e => { + e.stopPropagation(); + change(e.target.value); + }} + /> + ); + }; if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; @@ -1282,13 +1229,13 @@ export class PresBox extends ViewBoxBaseComponent() { activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return (
e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} + className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={StopEvent} + onPointerUp={StopEvent} onClick={action(e => { e.stopPropagation(); - this.openMovementDropdown = false; - this.openEffectDropdown = false; + this._openMovementDropdown = false; + this._openEffectDropdown = false; })}>
Movement @@ -1301,24 +1248,16 @@ export class PresBox extends ViewBoxBaseComponent() { className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); - this.openMovementDropdown = !this.openMovementDropdown; + this._openMovementDropdown = !this._openMovementDropdown; })} - style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> - {this.setMovementName(activeItem.presMovement, activeItem)} - -
e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}> -
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> - None -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> - Pan {'&'} Zoom -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> - Pan -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> - Jump cut -
+ style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {this.movementName(activeItem)} + +
+ {presMovement(PresMovement.None)} + {presMovement(PresMovement.Zoom)} + {presMovement(PresMovement.Pan)} + {presMovement(PresMovement.Jump)}
)} @@ -1336,21 +1275,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- (this._batch = UndoManager.StartBatch('presZoom'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setZoom(e.target.value); - }} - /> + {inputter('0', '1', '150', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
Movement Speed
@@ -1365,21 +1290,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- (this._batch = UndoManager.StartBatch('presTransition'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setTransitionTime(e.target.value); - }} - /> + {inputter('0.1', '0.1', '10', transitionSpeed, [PresMovement.Pan, PresMovement.Zoom].includes(activeItem.presMovement as any), this.setTransitionTime)}
Fast
Medium
@@ -1390,35 +1301,20 @@ export class PresBox extends ViewBoxBaseComponent() { Visibility {'&'} Duration
{isPresCollection ? null : ( - -
{'Hide before presented'}
- - }> + {'Hide before presented'}
}>
this.updateHideBefore(activeItem)}> Hide before
)} {isPresCollection ? null : ( - -
{'Hide after presented'}
- - }> + {'Hide after presented'}
}>
this.updateHideAfter(activeItem)}> Hide after
)} - -
{'Open in lightbox view'}
- - }> + {'Open in lightbox view'}}>
this.updateOpenDoc(activeItem)}> Lightbox
@@ -1440,22 +1336,7 @@ export class PresBox extends ViewBoxBaseComponent() { - (this._batch = UndoManager.StartBatch('presDuration'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setDurationTime(e.target.value); - }} - /> + {inputter('0.1', '0.1', '20', duration, targetDoc.type !== DocumentType.AUDIO, this.setDurationTime)}
Short
Medium
@@ -1471,98 +1352,30 @@ export class PresBox extends ViewBoxBaseComponent() { className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); - this.openEffectDropdown = !this.openEffectDropdown; + this._openEffectDropdown = !this._openEffectDropdown; })} - style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + style={{ borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, border: this._openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {effect.toString()} - -
e.stopPropagation()}> -
e.stopPropagation()} - onClick={() => this.updateEffect(PresEffect.None)}> - None -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> - Fade In -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> - Flip -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> - Rotate -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> - Bounce -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> - Roll -
+ +
e.stopPropagation()}> + {presEffect(PresEffect.None)} + {presEffect(PresEffect.Fade)} + {presEffect(PresEffect.Flip)} + {presEffect(PresEffect.Rotate)} + {presEffect(PresEffect.Bounce)} + {presEffect(PresEffect.Roll)}
Effect direction
-
{this.effectDirection}
+
{StrCast(this.activeItem.presEffectDirection)}
- {'Enter from left'}
}> -
this.updateEffectDirection(PresEffect.Left)}> - -
- - {'Enter from right'}
}> -
this.updateEffectDirection(PresEffect.Right)}> - -
-
- -
{'Enter from top'}
- - }> -
this.updateEffectDirection(PresEffect.Top)}> - -
-
- -
{'Enter from bottom'}
- - }> -
this.updateEffectDirection(PresEffect.Bottom)}> - -
-
- -
{'Enter from center'}
- - }> -
this.updateEffectDirection(PresEffect.Center)}>
-
+ {presDirection(PresEffect.Left, 'angle-right', 1, 2, {})} + {presDirection(PresEffect.Right, 'angle-left', 3, 2, {})} + {presDirection(PresEffect.Top, 'angle-down', 2, 1, {})} + {presDirection(PresEffect.Bottom, 'angle-up', 2, 3, {})} + {presDirection(PresEffect.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} )} @@ -1576,34 +1389,18 @@ export class PresBox extends ViewBoxBaseComponent() { } } - @computed get effectDirection() { - // prettier-ignore - switch (this.activeItem.presEffectDirection) { - case 'left': return 'Enter from left'; - case 'right': return 'Enter from right'; - case 'top': return'Enter from top'; - case 'bottom': return 'Enter from bottom'; - } - return 'Enter from center'; - } - @undoBatch @action applyTo = (array: Doc[]) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - this.updateMovement(activeItem.presMovement, true); - this.updateEffect(activeItem.presEffect, true); - this.updateEffectDirection(activeItem.presEffectDirection, true); - array.forEach(doc => { - const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - if (tagDoc && targetDoc) { - curDoc.presTransition = activeItem.presTransition; - curDoc.presDuration = activeItem.presDuration; - curDoc.presHideBefore = activeItem.presHideBefore; - curDoc.presHideAfter = activeItem.presHideAfter; - } + this.updateMovement(this.activeItem.presMovement as PresMovement, true); + this.updateEffect(this.activeItem.presEffect as PresEffect, true); + this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffect, true); + const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; + array.forEach(curDoc => { + curDoc.presTransition = presTransition; + curDoc.presDuration = presDuration; + curDoc.presHideBefore = presHideBefore; + curDoc.presHideAfter = presHideAfter; }); }; @@ -1785,7 +1582,7 @@ export class PresBox extends ViewBoxBaseComponent() { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> @@ -1956,7 +1753,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
() { const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
{this.stringType} selected
() { @undoBatch @action switchActive = (color: ColorState) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const val = String(color.hex); - targetDoc['pres-text-color'] = val; + this.targetDoc['pres-text-color'] = String(color.hex); return true; }; @undoBatch @action switchPresented = (color: ColorState) => { - const targetDoc: Doc = this.targetDoc; - const val = String(color.hex); - targetDoc['pres-text-viewed-color'] = val; + this.targetDoc['pres-text-viewed-color'] = String(color.hex); return true; }; @@ -2228,12 +2020,11 @@ export class PresBox extends ViewBoxBaseComponent() { //Toggle whether the user edits or not @action editScrollProgressivize = (e: React.MouseEvent) => { - const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; - activeItem.scrollProgressivize = true; + this.activeItem.scrollProgressivize = true; } targetDoc.editScrollProgressivize = true; } else { @@ -2245,8 +2036,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action progressivizeScroll = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem: Doc = this.activeItem; - activeItem.scrollProgressivize = !activeItem.scrollProgressivize; + this.activeItem.scrollProgressivize = !this.activeItem.scrollProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); @@ -2353,13 +2143,14 @@ export class PresBox extends ViewBoxBaseComponent() { @action checkList = (doc: Doc, list: any): number => { const x: List = list; - if (x && x.length >= NumCast(doc._currentFrame) + 1) { + if (x?.length >= NumCast(doc._currentFrame) + 1) { return x[NumCast(doc._currentFrame)]; } else if (x) { x.length = NumCast(doc._currentFrame) + 1; x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; return x[NumCast(doc._currentFrame)]; - } else return 100; + } + return 100; }; @computed get progressivizeChildDocs() { @@ -2413,26 +2204,14 @@ export class PresBox extends ViewBoxBaseComponent() { } @action - nextAppearFrame = (doc: Doc, i: number): void => { - // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, 'number', null); - if (appearFrame === undefined) { - doc.appearFrame = 0; - } - doc.appearFrame = appearFrame + 1; + nextAppearFrame = (doc: Doc, i: number) => { + doc.appearFrame = (Cast(doc.appearFrame, 'number', null) ?? 0) + 1; this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @action - prevAppearFrame = (doc: Doc, i: number): void => { - // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, 'number', null); - if (appearFrame === undefined) { - doc.appearFrame = 0; - } - doc.appearFrame = Math.max(0, appearFrame - 1); + prevAppearFrame = (doc: Doc, i: number) => { + doc.appearFrame = Math.max(0, (Cast(doc.appearFrame, 'number', null) ?? 0) - 1); this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @@ -2483,12 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} - -
{'View paths'}
- - }> + {'View paths'}
}>
1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className={'toolbar-button'} @@ -2507,22 +2281,12 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} - -
{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
- - }> + {presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
}>
- -
{propTitle}
- - }> + {propTitle}
}>
0 ? activeColor : inactiveColor }} />
@@ -2545,14 +2309,14 @@ export class PresBox extends ViewBoxBaseComponent() {
{isMini ? null : ( this._rowDoc[this.renderFieldKey] = e.target.checked} /> + (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
); } } - @observer export class CollectionSchemaButtons extends CollectionSchemaCell { // the navigation buttons for schema view when it is used for search. render() { - return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <> : -
+ return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? ( + <> + ) : ( +
- -
; +
+ ); } } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx index f872637e5..3cb2df7d3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx @@ -10,6 +10,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { OpenWhere } from '../../nodes/DocumentView'; import './CollectionSchemaView.scss'; export interface MovableRowProps { @@ -138,7 +139,7 @@ export class MovableRow extends React.Component
-
this.props.addDocTab(this.props.rowInfo.original, 'add:right')}> +
this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index fafea5ce3..45ad4f86b 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -23,7 +23,7 @@ import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; import { ContextMenu } from '../../ContextMenu'; import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { DefaultStyleProvider } from '../../StyleProvider'; import { CollectionView } from '../CollectionView'; import { @@ -86,7 +86,7 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: (outsideReaction: boolean | undefined) => boolean | undefined; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, where: string) => boolean; + addDocTab: (document: Doc, where: OpenWhere) => boolean; pinToPres: (document: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; isFocused: (document: Doc, outsideReaction: boolean) => boolean; @@ -625,7 +625,7 @@ export class SchemaTable extends React.Component { }; onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, 'add:right'); + this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight); }; getPreviewTransform = (): Transform => { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 570039550..868822fbf 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -16,7 +16,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; -import { DocumentView, DocumentViewProps } from './DocumentView'; +import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -199,7 +199,7 @@ export class CollectionFreeFormDocumentView extends DocComponent string; whenChildContentsActiveChanged: (isActive: boolean) => void; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; @@ -474,7 +495,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: "map-pin", selected: -1 }); const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ @@ -485,7 +506,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRight), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 }); RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 }); @@ -586,7 +607,7 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); } else if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isLinkButton) { - UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, 'lightbox', this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap'); + UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } @@ -857,7 +878,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, 'add:right'), icon: 'eye' }); + !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !Doc.noviceMode && appearanceItems.push({ description: 'Add a Field', @@ -957,8 +978,8 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' }); - !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' }); + helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); + !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 7d04c4b64..18c5b81ec 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -20,6 +20,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import e = require('express'); import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { ImageBox } from './ImageBox'; +import { OpenWhere } from './DocumentView'; export type KVPScript = { script: CompiledScript; @@ -259,8 +260,8 @@ export class KeyValueBox extends React.Component { openItems.push({ description: 'Default Perspective', event: () => { - this.props.addDocTab(this.props.Document, 'close'); - this.props.addDocTab(this.fieldDocToLayout, 'add:right'); + this.props.addDocTab(this.props.Document, OpenWhere.close); + this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight); }, icon: 'image', }); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 80def3025..e74ef4a39 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,18 +1,19 @@ import { action, observable } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { Doc, Field, Opt } from '../../../fields/Doc'; import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; -import "./KeyValueBox.scss"; -import "./KeyValuePair.scss"; -import React = require("react"); +import './KeyValueBox.scss'; +import './KeyValuePair.scss'; +import React = require('react'); import { DefaultStyleProvider } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; // Represents one row in a key value plane @@ -23,7 +24,7 @@ export interface KeyValuePairProps { keyWidth: number; PanelHeight: () => number; PanelWidth: () => number; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; } @observer export class KeyValuePair extends React.Component { @@ -34,23 +35,23 @@ export class KeyValuePair extends React.Component { @action handleCheck = (e: React.ChangeEvent) => { this.isChecked = e.currentTarget.checked; - } + }; @action uncheck = () => { this.checkbox.current!.checked = false; this.isChecked = false; - } + }; onContextMenu = (e: React.MouseEvent) => { const value = this.props.doc[this.props.keyName]; if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } - } + }; render() { const props: FieldViewProps = { @@ -68,7 +69,7 @@ export class KeyValuePair extends React.Component { isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, - dropAction: "alias", + dropAction: 'alias', bringToFront: emptyFunction, renderDepth: 1, isContentActive: returnFalse, @@ -92,30 +93,30 @@ export class KeyValuePair extends React.Component { doc = doc.proto; } const parenCount = Math.max(0, protoCount - 1); - const keyStyle = protoCount === 0 ? "black" : "blue"; + const keyStyle = protoCount === 0 ? 'black' : 'blue'; - const hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; + const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; return ( - this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}> + (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
- - -
{"(".repeat(parenCount)}{props.fieldKey}{")".repeat(parenCount)}
+ +
+ {'('.repeat(parenCount)} + {props.fieldKey} + {')'.repeat(parenCount)} +
@@ -123,13 +124,13 @@ export class KeyValuePair extends React.Component { Field.toKeyValueString(props.Document, props.fieldKey)} - SetValue={(value: string) => - KeyValueBox.SetField(props.Document, props.fieldKey, value)} /> + SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} + />
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index d6cf79f87..be9565452 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -17,6 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import React = require('react'); +import { OpenWhere } from './DocumentView'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -88,13 +89,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { }; openLinkDocOnRight = (e: React.MouseEvent) => { - this.props.addDocTab(this.rootDoc, 'add:right'); + this.props.addDocTab(this.rootDoc, OpenWhere.addRight); }; openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias._isLinkButton = undefined; alias.layoutKey = 'layout'; - this.props.addDocTab(alias, 'add:right'); + this.props.addDocTab(alias, OpenWhere.addRight); }; @action openLinkEditor = action((e: React.MouseEvent) => { diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index a47577701..135fbca31 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -14,7 +14,7 @@ import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; -import { DocumentView, DocumentViewSharedProps } from './DocumentView'; +import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView'; import './LinkDocPreview.scss'; import React = require('react'); import { LinkEditor } from '../linking/LinkEditor'; @@ -156,7 +156,7 @@ export class LinkDocPreview extends React.Component { LinkDocPreview.Clear(); LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); } else if (this.props.hrefs?.length) { - this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right'); + this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), OpenWhere.addRight); } }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 70ac84fa4..82d5b00f9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -31,6 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; import { ObjectField } from '../../../fields/ObjectField'; +import { OpenWhere } from './DocumentView'; const path = require('path'); /** @@ -273,7 +274,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { } showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, 'add:right'); + dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); }; dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 1e7cb6ea5..63347015b 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -17,6 +17,7 @@ import { Tooltip } from '@material-ui/core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { NodeSelection } from 'prosemirror-state'; +import { OpenWhere } from '../DocumentView'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -227,7 +228,7 @@ export class DashFieldViewInternal extends React.Component c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey; - this.props.tbox.props.addDocTab(alias, 'add:right'); + this.props.tbox.props.addDocTab(alias, OpenWhere.addRight); } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fdd61463d..ce4639b76 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; -import { DocumentViewInternal } from '../DocumentView'; +import { DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -1428,7 +1428,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const docView = DocumentManager.Instance.getDocumentView(audiodoc); if (!docView) { - this.props.addDocTab(audiodoc, 'add:bottom'); + this.props.addDocTab(audiodoc, OpenWhere.addBottom); setTimeout(func); } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that }; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 3d9bd6add..68b0488a2 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -9,6 +9,7 @@ import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { SelectionManager } from '../../../util/SelectionManager'; +import { OpenWhere } from '../DocumentView'; import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; @@ -135,7 +136,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //Command to create a new Tab with a PDF of all the command shortcuts bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); - props.addDocTab(newDoc, 'add:right'); + props.addDocTab(newDoc, OpenWhere.addRight); }); //Commands to modify BlockType diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index e19b53f50..adfd2fda1 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -23,7 +23,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { ViewBoxBaseComponent } from '../../DocComponent'; @@ -35,6 +35,7 @@ import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; import { map } from 'bluebird'; +import { OpenWhere, OpenWhereMod } from '../DocumentView'; const { Howl } = require('howler'); export interface PinProps { @@ -196,7 +197,7 @@ export class PresBox extends ViewBoxBaseComponent() { ); this.props.setContentView?.(this); this._unmounting = false; - this.rootDoc._forceRenderEngine = 'timeline'; + this.rootDoc._forceRenderEngine = computeTimelineLayout.name; this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; @@ -563,7 +564,7 @@ export class PresBox extends ViewBoxBaseComponent() { self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { - (collectionDocView ?? this).props.addDocTab(doc, ''); + (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.add); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); @@ -725,7 +726,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); - CollectionDockingView.AddSplit(this.rootDoc, 'right'); + CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right); } else { this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); @@ -1760,7 +1761,7 @@ export class PresBox extends ViewBoxBaseComponent() { TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { - this.props.addDocTab(doc, 'add:right'); + this.props.addDocTab(doc, OpenWhere.addRight); } } }; @@ -2322,7 +2323,7 @@ export class PresBox extends ViewBoxBaseComponent() { static NavigateToDoc(bestTarget: Doc, activeItem: Doc) { const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); const openInTab = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddSplit(doc, 'right'); + CollectionDockingView.AddSplit(doc, OpenWhereMod.right); finished?.(); }; PresBox.NavigateToTarget(bestTarget, activeItem, openInTab, srcContext); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 8265de445..2ae597b0b 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -700,7 +700,7 @@ export class MobileInterface extends React.Component { className="docButton" title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'} style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }} - onClick={e => TabDocView.PinDoc(this._activeDoc)}> + onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
); -- cgit v1.2.3-70-g09d2 From cdd1dd95c67fcb95914913bb5346c5117c0abc27 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 18 Nov 2022 17:26:17 -0500 Subject: migration of link properties to propertiesView --- src/client/util/LinkFollower.ts | 14 +- src/client/util/LinkManager.ts | 3 +- src/client/util/SelectionManager.ts | 4 + src/client/views/DocumentButtonBar.tsx | 4 +- src/client/views/PropertiesView.scss | 8 +- src/client/views/PropertiesView.tsx | 307 +++++++++++++++------ .../CollectionFreeFormLinkView.tsx | 27 +- src/client/views/linking/LinkEditor.tsx | 16 +- src/client/views/linking/LinkMenu.scss | 8 +- src/client/views/linking/LinkMenuItem.scss | 24 +- src/client/views/linking/LinkMenuItem.tsx | 42 ++- src/client/views/nodes/LinkDocPreview.tsx | 2 + src/client/views/nodes/trails/PresBox.tsx | 30 +- src/client/views/nodes/trails/PresEnums.ts | 3 + src/fields/Doc.ts | 2 +- src/fields/documentSchemas.ts | 2 +- 16 files changed, 327 insertions(+), 169 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index a3eb7ed7a..52885e428 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -111,7 +111,19 @@ export class LinkFollower { containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; } const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); + DocumentManager.Instance.jumpToDocument( + target, + zoom, + (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished), + targetContexts, + linkDoc, + undefined, + sourceDoc, + allFinished, + undefined, + undefined, + Cast(target.presZoom, 'number', null) + ); } } else { allFinished(); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 7a12a8580..49cc3218d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -19,7 +19,8 @@ import { Cast, StrCast } from '../../fields/Types'; export class LinkManager { @observable static _instance: LinkManager; @observable static userLinkDBs: Doc[] = []; - public static currentLink: Opt; + @observable public static currentLink: Opt; + @observable public static currentLinkAnchor: Opt; public static get Instance() { return LinkManager._instance; } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index a3d6f5227..1a2dda953 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,6 +4,7 @@ import { Doc, Opt } from '../../fields/Doc'; import { DocCast } from '../../fields/Types'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; +import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export namespace SelectionManager { @@ -15,6 +16,7 @@ export namespace SelectionManager { @action SelectSchemaViewDoc(doc: Opt) { manager.SelectedSchemaDocument = doc; + if (doc?.type === DocumentType.LINK) LinkManager.currentLink = doc; } @action SelectView(docView: DocumentView, ctrlPressed: boolean): void { @@ -32,6 +34,7 @@ export namespace SelectionManager { manager.SelectedViews.clear(); manager.SelectedViews.set(docView, docView.rootDoc); } + if (docView.rootDoc.type === DocumentType.LINK) LinkManager.currentLink = docView.rootDoc; } @action DeselectView(docView: DocumentView): void { @@ -42,6 +45,7 @@ export namespace SelectionManager { } @action DeselectAll(): void { + LinkManager.currentLink = undefined; manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedViews.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViews.clear(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ecf330792..5969d55e9 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -21,7 +21,7 @@ import './DocumentButtonBar.scss'; import { Colors } from './global/globalEnums'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; @@ -180,7 +180,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV googleDoc = Docs.Create.WebDocument(googleDocUrl, options); dataDoc.googleDoc = googleDoc; } - CollectionDockingView.AddSplit(googleDoc, 'right'); + CollectionDockingView.AddSplit(googleDoc, OpenWhereMod.right); } else if (e.altKey) { e.preventDefault(); window.open(googleDocUrl); diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 437df4739..30806f718 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -1,9 +1,9 @@ -@import "./global/globalCssVariables.scss"; +@import './global/globalCssVariables.scss'; .propertiesView { height: 100%; width: 250; - font-family: "Roboto"; + font-family: 'Roboto'; cursor: auto; overflow-x: hidden; @@ -843,7 +843,7 @@ } .propertiesView-section { - padding: 10px 0; + padding-left: 20px; } .propertiesView-input { @@ -877,4 +877,4 @@ color: white; padding-left: 8px; background-color: rgb(51, 51, 51); -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 93a3fd253..076dc8261 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -4,7 +4,7 @@ import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@material-ui/core'; import { intersection } from 'lodash'; -import { action, autorun, computed, Lambda, observable } from 'mobx'; +import { action, autorun, computed, Lambda, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, DocListCast, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; @@ -27,12 +27,13 @@ import { InkStrokeProperties } from './InkStrokeProperties'; import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; import { FilterBox } from './nodes/FilterBox'; import { KeyValueBox } from './nodes/KeyValueBox'; -import { PresBox } from './nodes/trails'; +import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; import { PropertiesButtons } from './PropertiesButtons'; import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; +import { Colors } from './global/globalEnums'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -54,7 +55,7 @@ export class PropertiesView extends React.Component { } @computed get selectedDoc() { - return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; + return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -405,7 +406,7 @@ export class PropertiesView extends React.Component { */ @computed get expansionIcon() { return ( - {'Show more permissions'}
}> + Show more permissions
}>
{ @@ -1406,6 +1407,37 @@ export class PropertiesView extends React.Component { } }); + @undoBatch + changeAnimationBehavior = action((behavior: string) => { + const lanch = this.destinationAnchor; + if (lanch) { + lanch.presEffect = behavior; + } + }); + @undoBatch + @action + updateEffectDirection = (effect: PresEffectDirection) => { + const lanch = this.destinationAnchor; + if (lanch) { + lanch.presEffectDirection = effect; + } + }; + + @undoBatch + animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const lanch = this.destinationAnchor; + const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + return ( + {direction}
}> +
this.updateEffectDirection(direction)}> + {icon ? : null} +
+
+ ); + }; + @undoBatch changeFollowBehavior = action((follow: string) => { if (LinkManager.currentLink && this.selectedDoc) { @@ -1438,19 +1470,23 @@ export class PropertiesView extends React.Component { } }; - toggleAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)))); + toggleProp = (e: React.PointerEvent, prop: string) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop])))); }; - toggleArrow = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)))); - }; + @computed get destinationAnchor() { + const ldoc = LinkManager.currentLink; + const lanch = LinkManager.currentLinkAnchor; + if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; + return ldoc ? DocCast(ldoc.anchor2) : ldoc; + } - toggleZoomToTarget1 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom)))); - }; - toggleZoomToTarget2 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom)))); + @computed get sourceAnchor() { + return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + } + + toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { + anchor && setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (anchor[prop] = !anchor[prop])))); }; @computed @@ -1485,6 +1521,23 @@ export class PropertiesView extends React.Component { ); } + // Converts seconds to ms and updates presTransition + setZoom = (number: String, change?: number) => { + let scale = Number(number) / 100; + if (change) scale += change; + if (scale < 0.01) scale = 0.01; + if (scale > 1) scale = 1; + this.destinationAnchor && (this.destinationAnchor.presZoom = scale); + }; + // Converts seconds to ms and updates presTransition + setZoomSrc = (number: String, change?: number) => { + let scale = Number(number) / 100; + if (change) scale += change; + if (scale < 0.01) scale = 0.01; + if (scale > 1) scale = 1; + this.sourceAnchor && (this.sourceAnchor.presZoom = scale); + }; + /** * Handles adding and removing members from the sharing panel */ @@ -1498,6 +1551,10 @@ export class PropertiesView extends React.Component { render() { const isNovice = Doc.noviceMode; + const zoom = Number((NumCast(this.destinationAnchor?.presZoom) * 100).toPrecision(3)); + const zoomSrc = Number((NumCast(this.sourceAnchor?.presZoom) * 100).toPrecision(3)); + const targZoom = this.destinationAnchor?.followLinkZoom; + const srcZooom = this.sourceAnchor?.followLinkZoom; if (!this.selectedDoc && !this.isPres) { return (
@@ -1507,81 +1564,6 @@ export class PropertiesView extends React.Component {
); } else { - if (this.selectedDoc && this.isLink) { - return ( -
-
Linking
-
-

Information

-
-

Link Relationship

- {this.editRelationship} -
-
-

Description

- {this.editDescription} -
-
-
-

Behavior

-
-

Follow

- -
-
-

Auto-move anchor

- -
-
-

Display arrow

- -
-
-

Zoom to target

- -
-
-

Zoom to source

- -
-
-
- ); - } if (this.selectedDoc && !this.isPres) { return (
{ {this.contextsSubMenu} {this.linksSubMenu} + {!this.selectedDoc || !LinkManager.currentLink ? null : ( + <> +
+
+

Link Relationship

+ {this.editRelationship} +
+
+

Description

+ {this.editDescription} +
+
+
+
+

Follow Behavior

+ +
+
+

Target Effect

+ +
+ {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} +
+
+ {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor?.presTransition) / 1000, true, (val: string) => + PresBox.SetTransitionTime(val, (timeInMS: number) => this.destinationAnchor && (this.destinationAnchor.presTransition = timeInMS)) + )}{' '} +
+
Fast
+
Medium
+
Slow
+
{' '} +
+

Play Target Audio

+ +
+
+

Auto-move anchor

+ +
+
+

Show link

+ +
+
+

Display arrow

+ +
+
+

Zoom to target (% screen filled)

+
+ +
+
this.setZoom(String(zoom), 0.1))}> + +
+
this.setZoom(String(zoom), -0.1))}> + +
+
+
+ +
+ {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom)} +
+

Zoom to source (% screen filled)

+
+ +
+
this.setZoomSrc(String(zoomSrc), 0.1))}> + +
+
this.setZoomSrc(String(zoomSrc), -0.1))}> + +
+
+
+ +
+ {!srcZooom ? null : PresBox.inputter('0', '1', '100', zoomSrc, true, this.setZoomSrc)} +
+ + )} {this.inkSubMenu} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index bf9de6760..a4131d7c0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -44,7 +44,7 @@ export class CollectionFreeFormLinkView extends React.Component { - if (SettingsManager.propertiesWidth > 0) { - SettingsManager.propertiesWidth = 0; - } else { + if ((SettingsManager.propertiesWidth ?? 0) < 100) { SettingsManager.propertiesWidth = 250; } }; @@ -217,14 +215,25 @@ export class CollectionFreeFormLinkView extends React.Component (LinkManager.currentLink = this.props.LinkDocs[0]))); if (!this.renderData) return null; const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; - LinkManager.currentLink = this.props.LinkDocs[0]; const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List; const linkColorList = Doc.UserDoc().linkColorList as List; @@ -241,8 +250,8 @@ export class CollectionFreeFormLinkView extends React.Component {textX === undefined ? null : ( diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 8c4d756d2..07c20dae4 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -327,21 +327,7 @@ export class LinkEditor extends React.Component {
- {[ - PresEffect.None, - PresEffect.Zoom, - PresEffect.Lightspeed, - PresEffect.Fade, - PresEffect.Flip, - PresEffect.Rotate, - PresEffect.Bounce, - PresEffect.Roll, - PresEffect.Left, - PresEffect.Right, - PresEffect.Center, - PresEffect.Top, - PresEffect.Bottom, - ].map(effect => ( + {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
this.changeEffect(effect.toString())}> {effect.toString()}
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss index 77c16a28f..80cf93ed8 100644 --- a/src/client/views/linking/LinkMenu.scss +++ b/src/client/views/linking/LinkMenu.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables'; .linkMenu { width: auto; @@ -13,7 +13,6 @@ border: 1px solid #e4e4e4; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); background: white; - min-width: 170px; max-height: 230px; overflow-y: scroll; z-index: 10; @@ -22,7 +21,7 @@ .linkMenu-list { white-space: nowrap; overflow-x: hidden; - width: 240px; + width: 100%; scrollbar-color: white; &:last-child { @@ -39,7 +38,6 @@ border-bottom: 0.5px solid lightgray; //@extend: 5px 0; - &:last-child { border-bottom: none; } @@ -76,4 +74,4 @@ display: none; } } -} \ No newline at end of file +} diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index 8333aa374..2ca97a27d 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -1,7 +1,7 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables'; .linkMenu-item { - // border-top: 0.5px solid $medium-gray; + // border-top: 0.5px solid $medium-gray; position: relative; display: flex; border-top: 0.5px solid #cdcdcd; @@ -14,7 +14,6 @@ background-color: white; - .linkMenu-name { position: relative; width: auto; @@ -22,9 +21,7 @@ display: flex; .linkMenu-text { - - // padding: 4px 2px; - //display: inline; + width: 100%; .linkMenu-source-title { text-decoration: none; @@ -35,9 +32,7 @@ margin-left: 20px; } - .linkMenu-title-wrapper { - display: flex; align-items: center; min-height: 20px; @@ -59,7 +54,7 @@ .linkMenu-destination-title { text-decoration: none; - color: #4476F7; + color: #4476f7; font-size: 13px; line-height: 0.9; padding-bottom: 2px; @@ -96,9 +91,7 @@ //overflow-wrap: break-word; user-select: none; } - } - } .linkMenu-item-content { @@ -114,7 +107,6 @@ } &:hover { - background-color: rgb(201, 239, 252); .linkMenu-item-buttons { @@ -122,7 +114,6 @@ } .linkMenu-item-content { - .linkMenu-destination-title { text-decoration: underline; color: rgb(60, 90, 156); @@ -135,10 +126,7 @@ .linkMenu-item-buttons { //@extend: right; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); + position: relative; display: none; .button { @@ -172,4 +160,4 @@ cursor: pointer; } } -} \ No newline at end of file +} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 387e0e3d5..c3b5fa997 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -1,12 +1,12 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, observable } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { Cast, StrCast } from '../../../fields/Types'; +import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; -import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; @@ -16,6 +16,9 @@ import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; import React = require('react'); +import { SettingsManager } from '../../util/SettingsManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import { undoBatch } from '../../util/UndoManager'; interface LinkMenuItemProps { groupType: string; @@ -76,8 +79,19 @@ export class LinkMenuItem extends React.Component { this._showMore = !this._showMore; } - onEdit = (e: React.PointerEvent): void => { - LinkManager.currentLink = this.props.linkDoc; + @computed get sourceAnchor() { + const ldoc = this.props.linkDoc; + if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) { + if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); + if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); + } + return this.props.sourceDoc; + } + @action + onEdit = (e: React.PointerEvent) => { + const sel = SelectionManager.Views(); + LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc; + LinkManager.currentLinkAnchor = this.sourceAnchor; setupMoveUpEvents( this, e, @@ -88,7 +102,12 @@ export class LinkMenuItem extends React.Component { return true; }, emptyFunction, - () => this.props.showEditor(this.props.linkDoc) + action(() => { + if ((SettingsManager.propertiesWidth ?? 0) < 100) { + SettingsManager.propertiesWidth = 250; + } + //this.props.showEditor(this.props.linkDoc); + }) ); }; @@ -123,6 +142,8 @@ export class LinkMenuItem extends React.Component { ); }; + deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc)))); + render() { const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp; @@ -141,7 +162,7 @@ export class LinkMenuItem extends React.Component { : undefined; return ( -
+
{
Edit Link
}>
e.stopPropagation()}> - + +
+ + Delete Link
}> +
e.stopPropagation()}> +
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 135fbca31..b326865b3 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -124,6 +124,8 @@ export class LinkDocPreview extends React.Component { return undefined; } @observable _showEditor = false; + + @action editLink = (e: React.PointerEvent): void => { LinkManager.currentLink = this.props.linkDoc; setupMoveUpEvents( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index adfd2fda1..1cb6bc3a2 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -33,7 +33,7 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; -import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; import { map } from 'bluebird'; import { OpenWhere, OpenWhereMod } from '../DocumentView'; const { Howl } = require('howler'); @@ -71,10 +71,10 @@ export class PresBox extends ViewBoxBaseComponent() { */ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Doc, root: Doc) { const effectProps = { - left: presEffectDoc?.presEffectDirection === PresEffect.Left, - right: presEffectDoc?.presEffectDirection === PresEffect.Right, - top: presEffectDoc?.presEffectDirection === PresEffect.Top, - bottom: presEffectDoc?.presEffectDirection === PresEffect.Bottom, + left: presEffectDoc?.presEffectDirection === PresEffectDirection.Left, + right: presEffectDoc?.presEffectDirection === PresEffectDirection.Right, + top: presEffectDoc?.presEffectDirection === PresEffectDirection.Top, + bottom: presEffectDoc?.presEffectDirection === PresEffectDirection.Bottom, opposite: true, delay: 0, duration: Cast(presEffectDoc?.presTransition, 'number', null), @@ -1191,7 +1191,7 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch @action - updateEffectDirection = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect)); + updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect)); @undoBatch @action @@ -1236,12 +1236,12 @@ export class PresBox extends ViewBoxBaseComponent() { {movement}
); - const presDirection = (direction: PresEffect, icon: string, gridColumn: number, gridRow: number, opts: object) => { - const color = this.activeItem.presEffectDirection === direction || (direction === PresEffect.Center && !this.activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const color = this.activeItem.presEffectDirection === direction || (direction === PresEffectDirection.Center && !this.activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; return ( {direction}
}>
this.updateEffectDirection(direction)}> {icon ? : null}
@@ -1405,11 +1405,11 @@ export class PresBox extends ViewBoxBaseComponent() {
{StrCast(this.activeItem.presEffectDirection)}
- {presDirection(PresEffect.Left, 'angle-right', 1, 2, {})} - {presDirection(PresEffect.Right, 'angle-left', 3, 2, {})} - {presDirection(PresEffect.Top, 'angle-down', 2, 1, {})} - {presDirection(PresEffect.Bottom, 'angle-up', 2, 3, {})} - {presDirection(PresEffect.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} + {presDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {presDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {presDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {presDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
)} @@ -1428,7 +1428,7 @@ export class PresBox extends ViewBoxBaseComponent() { applyTo = (array: Doc[]) => { this.updateMovement(this.activeItem.presMovement as PresMovement, true); this.updateEffect(this.activeItem.presEffect as PresEffect, true); - this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffect, true); + this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true); const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; array.forEach(curDoc => { curDoc.presTransition = presTransition; diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts index c6a222c3a..034f7588b 100644 --- a/src/client/views/nodes/trails/PresEnums.ts +++ b/src/client/views/nodes/trails/PresEnums.ts @@ -14,6 +14,9 @@ export enum PresEffect { Bounce = 'Bounce', Roll = 'Roll', None = 'None', +} + +export enum PresEffectDirection { Left = 'Enter from left', Right = 'Enter from right', Center = 'Enter from center', diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 75801b68c..c10751698 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1268,7 +1268,7 @@ export namespace Doc { let _lastDate = 0; export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) { - linkFollowUnhighlight(); + //linkFollowUnhighlight(); // runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = undefined)); // setTimeout(() => runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect))); runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect)); diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 24b5a359d..10324449f 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -93,7 +93,7 @@ export const documentSchema = createSchema({ layers: listSpec('string'), // which layers the document is part of _lockedPosition: 'boolean', // whether the document can be moved (dragged) _lockedTransform: 'boolean', // whether a freeformview can pan/zoom - displayArrow: 'boolean', // toggles directed arrows + linkDisplayArrow: 'boolean', // toggles directed arrows // drag drop properties _stayInCollection: 'boolean', // whether document can be dropped into a different collection -- cgit v1.2.3-70-g09d2 From 1f5db9cfc594dbf337d752ec94dab5fca7d8b6f7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 21 Nov 2022 14:50:25 -0500 Subject: working cleaned up version of link editor properties in propertiesView --- src/client/util/DocumentManager.ts | 6 +- src/client/util/LinkFollower.ts | 2 +- src/client/util/SelectionManager.ts | 3 - .../views/PropertiesDocBacklinksSelector.tsx | 5 +- src/client/views/PropertiesView.tsx | 161 ++++++++++----------- .../CollectionFreeFormLinkView.tsx | 1 - .../collectionSchema/CollectionSchemaCells.tsx | 3 +- .../collections/collectionSchema/SchemaTable.tsx | 3 +- src/client/views/linking/LinkEditor.tsx | 22 +-- src/client/views/linking/LinkMenu.tsx | 31 ++-- src/client/views/linking/LinkMenuGroup.tsx | 65 +++++---- src/client/views/linking/LinkMenuItem.scss | 6 +- src/client/views/linking/LinkMenuItem.tsx | 57 ++++---- src/client/views/nodes/LinkDocPreview.tsx | 33 +++-- src/client/views/nodes/LoadingBox.scss | 15 +- src/client/views/nodes/trails/PresBox.tsx | 3 +- 16 files changed, 205 insertions(+), 211 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 00f6bc40a..7120eeb88 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,7 +5,7 @@ import { Cast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; @@ -167,7 +167,7 @@ export class DocumentManager { } static addView = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddSplit(doc, 'right'); + CollectionDockingView.AddSplit(doc, OpenWhereMod.right); finished?.(); }; public jumpToDocument = ( @@ -347,7 +347,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { } else { const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; - CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {})); + CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), OpenWhereMod.right) && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {})); } } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 52885e428..5374bf44b 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -122,7 +122,7 @@ export class LinkFollower { allFinished, undefined, undefined, - Cast(target.presZoom, 'number', null) + Cast(sourceDoc.presZoom, 'number', null) ); } } else { diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1a2dda953..02d672a65 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -16,7 +16,6 @@ export namespace SelectionManager { @action SelectSchemaViewDoc(doc: Opt) { manager.SelectedSchemaDocument = doc; - if (doc?.type === DocumentType.LINK) LinkManager.currentLink = doc; } @action SelectView(docView: DocumentView, ctrlPressed: boolean): void { @@ -34,7 +33,6 @@ export namespace SelectionManager { manager.SelectedViews.clear(); manager.SelectedViews.set(docView, docView.rootDoc); } - if (docView.rootDoc.type === DocumentType.LINK) LinkManager.currentLink = docView.rootDoc; } @action DeselectView(docView: DocumentView): void { @@ -45,7 +43,6 @@ export namespace SelectionManager { } @action DeselectAll(): void { - LinkManager.currentLink = undefined; manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedViews.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViews.clear(); diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 25ac44078..f7173a593 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../fields/Doc'; import { Cast } from '../../fields/Types'; -import { emptyFunction } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; @@ -47,9 +46,9 @@ export class PropertiesDocBacklinksSelector extends React.Component +
{this.props.hideTitle ? null :

Contexts:

} - +
); } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 076dc8261..ad3f62990 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1409,7 +1409,7 @@ export class PropertiesView extends React.Component { @undoBatch changeAnimationBehavior = action((behavior: string) => { - const lanch = this.destinationAnchor; + const lanch = this.sourceAnchor; if (lanch) { lanch.presEffect = behavior; } @@ -1417,7 +1417,7 @@ export class PropertiesView extends React.Component { @undoBatch @action updateEffectDirection = (effect: PresEffectDirection) => { - const lanch = this.destinationAnchor; + const lanch = this.sourceAnchor; if (lanch) { lanch.presEffectDirection = effect; } @@ -1425,7 +1425,7 @@ export class PropertiesView extends React.Component { @undoBatch animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { - const lanch = this.destinationAnchor; + const lanch = this.sourceAnchor; const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; return ( {direction}}> @@ -1451,7 +1451,7 @@ export class PropertiesView extends React.Component { document.getElementById('link_description_input')?.blur(); }; - onDescriptionKey = (e: React.KeyboardEvent) => { + onDescriptionKey = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { this.setDescripValue(this.description); document.getElementById('link_description_input')?.blur(); @@ -1508,29 +1508,21 @@ export class PropertiesView extends React.Component { @computed get editDescription() { return ( - this.handleDescriptionChange(e.currentTarget.value)} className="text" - type="text" /> ); } // Converts seconds to ms and updates presTransition setZoom = (number: String, change?: number) => { - let scale = Number(number) / 100; - if (change) scale += change; - if (scale < 0.01) scale = 0.01; - if (scale > 1) scale = 1; - this.destinationAnchor && (this.destinationAnchor.presZoom = scale); - }; - // Converts seconds to ms and updates presTransition - setZoomSrc = (number: String, change?: number) => { let scale = Number(number) / 100; if (change) scale += change; if (scale < 0.01) scale = 0.01; @@ -1551,11 +1543,11 @@ export class PropertiesView extends React.Component { render() { const isNovice = Doc.noviceMode; - const zoom = Number((NumCast(this.destinationAnchor?.presZoom) * 100).toPrecision(3)); - const zoomSrc = Number((NumCast(this.sourceAnchor?.presZoom) * 100).toPrecision(3)); - const targZoom = this.destinationAnchor?.followLinkZoom; - const srcZooom = this.sourceAnchor?.followLinkZoom; - if (!this.selectedDoc && !this.isPres) { + const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3)); + const targZoom = this.sourceAnchor?.followLinkZoom; + const selectedDoc = this.selectedDoc; + const indent = 30; + if (!selectedDoc && !this.isPres) { return (
@@ -1564,7 +1556,7 @@ export class PropertiesView extends React.Component {
); } else { - if (this.selectedDoc && !this.isPres) { + if (selectedDoc && !this.isPres) { return (
{ {this.contextsSubMenu} {this.linksSubMenu} - {!this.selectedDoc || !LinkManager.currentLink ? null : ( + {!selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(dv.rootDoc.links).includes(LinkManager.currentLink!)) ? null : ( <>
-
-

Link Relationship

+
+

Relationship

{this.editRelationship}
-
+

Description

{this.editDescription}
-
-

Follow Behavior

- this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(selectedDoc.followLinkLocation, 'default')}> - - - - - - - - - {this.selectedDoc.linksToAnnotation ? : null} + + + + + + + + + {selectedDoc.linksToAnnotation ? : null}
-
-

Target Effect

- this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.presEffect, 'default')}> {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( ))} -
+
{this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} @@ -1625,20 +1617,27 @@ export class PropertiesView extends React.Component { {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
- {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor?.presTransition) / 1000, true, (val: string) => - PresBox.SetTransitionTime(val, (timeInMS: number) => this.destinationAnchor && (this.destinationAnchor.presTransition = timeInMS)) + {PresBox.inputter( + '0.1', + '0.1', + '10', + NumCast(this.sourceAnchor?.presTransition) / 1000, + true, + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.presTransition = timeInMS)), + indent )}{' '}
Fast
-
Medium
Slow
{' '}
@@ -1652,39 +1651,35 @@ export class PropertiesView extends React.Component {
+

Show link

+ +
+

Auto-move anchor

-
-

Show link

- -
-
+

Display arrow

-
-

Zoom to target (% screen filled)

-
- +
+

Zoom % screen

+
+
this.setZoom(String(zoom), 0.1))}> @@ -1695,36 +1690,28 @@ export class PropertiesView extends React.Component {
-
- {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom)} -
-

Zoom to source (% screen filled)

-
- -
-
this.setZoomSrc(String(zoomSrc), 0.1))}> - -
-
this.setZoomSrc(String(zoomSrc), -0.1))}> - -
-
-
-
- {!srcZooom ? null : PresBox.inputter('0', '1', '100', zoomSrc, true, this.setZoomSrc)} + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} +
+
0%
+
100%
+
{' '}
)} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index a4131d7c0..b39fcbd48 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -230,7 +230,6 @@ export class CollectionFreeFormLinkView extends React.Component (LinkManager.currentLink = this.props.LinkDocs[0]))); if (!this.renderData) return null; const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index ef75fb159..a22999f52 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -31,6 +31,7 @@ import { OverlayView } from '../../OverlayView'; import { CollectionView } from '../CollectionView'; import './CollectionSchemaView.scss'; import { OpenWhere } from '../../nodes/DocumentView'; +import { PinProps } from '../../nodes/trails'; // intialize cell properties export interface CellProps { @@ -48,7 +49,7 @@ export interface CellProps { renderDepth: number; // called when a button is pressed on the node itself addDocTab: (document: Doc, where: OpenWhere) => boolean; - pinToPres: (document: Doc) => void; + pinToPres: (document: Doc, pinProps: PinProps) => void; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 45ad4f86b..16910cc83 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -24,6 +24,7 @@ import '../../../views/DocumentDecorations.scss'; import { ContextMenu } from '../../ContextMenu'; import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; +import { PinProps } from '../../nodes/trails'; import { DefaultStyleProvider } from '../../StyleProvider'; import { CollectionView } from '../CollectionView'; import { @@ -87,7 +88,7 @@ export interface SchemaTableProps { active: (outsideReaction: boolean | undefined) => boolean | undefined; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, where: OpenWhere) => boolean; - pinToPres: (document: Doc) => void; + pinToPres: (document: Doc, pinProps: PinProps) => void; isSelected: (outsideReaction?: boolean) => boolean; isFocused: (document: Doc, outsideReaction: boolean) => boolean; setFocused: (document: Doc) => void; diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 07c20dae4..01e33708a 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -252,7 +252,7 @@ export class LinkEditor extends React.Component { get followingDropdown() { return (
-
Follow Behavior:
+
Follow by:
{StrCast(this.props.linkDoc.followLinkLocation, 'default')} @@ -263,28 +263,28 @@ export class LinkEditor extends React.Component { Default
this.changeFollowBehavior('add:left')}> - Always open in new left pane + Always opening in new left pane
this.changeFollowBehavior('add:right')}> - Always open in new right pane + Always opening in new right pane
this.changeFollowBehavior('replace:right')}> - Always replace right tab + Always replacing right tab
this.changeFollowBehavior('replace:left')}> - Always replace left tab + Always replacing left tab
this.changeFollowBehavior('fullScreen')}> - Always open full screen + Always opening full screen
this.changeFollowBehavior('add')}> - Always open in a new tab + Always opening in a new tab
this.changeFollowBehavior('replace')}> - Replace Tab + Replacing Tab
this.changeFollowBehavior('inPlace')}> - In Place + Opening in Place
{this.props.linkDoc.linksToAnnotation ? (
this.changeFollowBehavior('openExternal')}> @@ -320,7 +320,7 @@ export class LinkEditor extends React.Component { get effectDropdown() { return (
-
Transition Effect:
+
Animation:
{StrCast(this.destinationAnchor.presEffect, 'default')} @@ -437,6 +437,8 @@ export class LinkEditor extends React.Component {
void; - clearLinkEditor: () => void; + clearLinkEditor?: () => void; } /** @@ -25,26 +26,24 @@ export class LinkMenu extends React.Component { @observable _editingLink?: Doc; @observable _linkMenuRef = React.createRef(); - @computed get position() { - return this.props.position ?? (dv => ({ x: dv?.left || 0, y: (dv?.bottom || 0) + 15 }))(this.props.docView.getBounds()); - } - - clear = action(() => { - this.props.clearLinkEditor(); - this._editingLink = undefined; - }); + clear = !this.props.clearLinkEditor + ? undefined + : action(() => { + this.props.clearLinkEditor?.(); + this._editingLink = undefined; + }); componentDidMount() { - document.addEventListener('pointerdown', this.onPointerDown, true); + this.props.clearLinkEditor && document.addEventListener('pointerdown', this.onPointerDown, true); } componentWillUnmount() { - document.removeEventListener('pointerdown', this.onPointerDown, true); + this.props.clearLinkEditor && document.removeEventListener('pointerdown', this.onPointerDown, true); } onPointerDown = action((e: PointerEvent) => { LinkDocPreview.Clear(); if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) { - this.clear(); + this.clear?.(); } }); @@ -67,13 +66,15 @@ export class LinkMenu extends React.Component { /> )); - return linkItems.length ? linkItems : this.props.position ? [<>] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; + return linkItems.length ? linkItems : this.props.style ? [<>] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; }; render() { const sourceDoc = this.props.docView.props.Document; + const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds()); + return ( -
+
{this._editingLink ? (
(this._editingLink = undefined))} /> diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index fa6a2f506..9d2082e21 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,19 +1,19 @@ -import { observer } from "mobx-react"; -import { observable, action } from "mobx"; -import { Doc, StrListCast } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { Cast } from "../../../fields/Types"; -import { LinkManager } from "../../util/LinkManager"; -import { DocumentView } from "../nodes/DocumentView"; +import { observer } from 'mobx-react'; +import { observable, action } from 'mobx'; +import { Doc, StrListCast } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { Cast } from '../../../fields/Types'; +import { LinkManager } from '../../util/LinkManager'; +import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; -import { LinkMenuItem } from "./LinkMenuItem"; -import React = require("react"); +import { LinkMenuItem } from './LinkMenuItem'; +import React = require('react'); interface LinkMenuGroupProps { sourceDoc: Doc; group: Doc[]; groupType: string; - clearLinkEditor: () => void; + clearLinkEditor?: () => void; showEditor: (linkDoc: Doc) => void; docView: DocumentView; itemHandler?: (doc: Doc) => void; @@ -26,49 +26,52 @@ export class LinkMenuGroup extends React.Component { getBackgroundColor = (): string => { const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - let color = "white"; + let color = 'white'; // if this link's relationship property is not default "link", set its color if (linkRelationshipList) { const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType); const RGBcolor: string = linkColorList[relationshipIndex]; if (RGBcolor) { //set opacity to 0.25 by modifiying the rgb string - color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)"; + color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)'; } } return color; - } + }; @observable _collapsed = false; render() { const set = new Set(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { - const destination = LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) || + const destination = + LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) || LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); if (destination && this.props.sourceDoc) { - return ; + return ( + + ); } }); return (
-
this._collapsed = !this._collapsed)} style={{ background: this.getBackgroundColor() }}> -

{this.props.groupType}:

+
(this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}> +

{this.props.groupType}:

- {this._collapsed ? (null) :
- {groupItems} -
} -
+ {this._collapsed ? null :
{groupItems}
} +
); } -} \ No newline at end of file +} diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index 2ca97a27d..806a2c381 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -79,7 +79,6 @@ font-size: 9px; line-height: 0.9; margin-left: 20px; - max-width: 125px; height: auto; white-space: break-spaces; } @@ -110,7 +109,7 @@ background-color: rgb(201, 239, 252); .linkMenu-item-buttons { - display: flex; + opacity: 1; } .linkMenu-item-content { @@ -127,7 +126,8 @@ .linkMenu-item-buttons { //@extend: right; position: relative; - display: none; + display: flex; + opacity: 0; .button { width: 20px; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index c3b5fa997..c3705b0e1 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -12,13 +12,12 @@ import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; +import { SettingsManager } from '../../util/SettingsManager'; +import { undoBatch } from '../../util/UndoManager'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; import React = require('react'); -import { SettingsManager } from '../../util/SettingsManager'; -import { SelectionManager } from '../../util/SelectionManager'; -import { undoBatch } from '../../util/UndoManager'; interface LinkMenuItemProps { groupType: string; @@ -26,7 +25,7 @@ interface LinkMenuItemProps { docView: DocumentView; sourceDoc: Doc; destinationDoc: Doc; - clearLinkEditor: () => void; + clearLinkEditor?: () => void; showEditor: (linkDoc: Doc) => void; menuRef: React.Ref; itemHandler?: (doc: Doc) => void; @@ -71,7 +70,6 @@ export class LinkMenuItem extends React.Component { private _drag = React.createRef(); _editRef = React.createRef(); - _buttonRef = React.createRef(); @observable private _showMore: boolean = false; @action toggleShowMore(e: React.PointerEvent) { @@ -89,7 +87,6 @@ export class LinkMenuItem extends React.Component { } @action onEdit = (e: React.PointerEvent) => { - const sel = SelectionManager.Views(); LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc; LinkManager.currentLinkAnchor = this.sourceAnchor; setupMoveUpEvents( @@ -119,12 +116,12 @@ export class LinkMenuItem extends React.Component { const eleClone: any = this._drag.current!.cloneNode(true); eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]); - this.props.clearLinkEditor(); + this.props.clearLinkEditor?.(); return true; }, emptyFunction, () => { - this.props.clearLinkEditor(); + this.props.clearLinkEditor?.(); if (this.props.itemHandler) { this.props.itemHandler?.(this.props.linkDoc); } else { @@ -167,20 +164,29 @@ export class LinkMenuItem extends React.Component {
- this.props.linkDoc && - LinkDocPreview.SetLinkInfo({ - docProps: this.props.docView.props, - linkSrc: this.props.sourceDoc, - linkDoc: this.props.linkDoc, - showHeader: false, - location: [this._drag.current?.getBoundingClientRect().right ?? 100, this._drag.current?.getBoundingClientRect().top ?? e.clientY], - noPreview: false, - }) - } onPointerDown={this.onLinkButtonDown}> -
+
+ Edit Link
}> +
e.stopPropagation()}> + +
+ +
+
+ this.props.linkDoc && + this.props.clearLinkEditor && + LinkDocPreview.SetLinkInfo({ + docProps: this.props.docView.props, + linkSrc: this.props.sourceDoc, + linkDoc: this.props.linkDoc, + showHeader: false, + location: [this._drag.current?.getBoundingClientRect().right ?? 100, this._drag.current?.getBoundingClientRect().top ?? e.clientY], + noPreview: false, + }) + }> {source ? (

{' '} @@ -198,14 +204,9 @@ export class LinkMenuItem extends React.Component { {!this.props.linkDoc.description ? null :

{StrCast(this.props.linkDoc.description)}

}
-
- Edit Link
}> -
e.stopPropagation()}> - -
- +
Delete Link
}> -
e.stopPropagation()}> +
e.stopPropagation()}>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index b326865b3..097035661 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -12,12 +12,11 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; +import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView'; import './LinkDocPreview.scss'; import React = require('react'); -import { LinkEditor } from '../linking/LinkEditor'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -123,17 +122,21 @@ export class LinkDocPreview extends React.Component { } return undefined; } - @observable _showEditor = false; @action editLink = (e: React.PointerEvent): void => { - LinkManager.currentLink = this.props.linkDoc; setupMoveUpEvents( this, e, returnFalse, emptyFunction, - action(() => (this._showEditor = !this._showEditor)) + action(() => { + LinkManager.currentLink = this._linkDoc === LinkManager.currentLink ? undefined : this._linkDoc; + LinkManager.currentLinkAnchor = this._linkSrc; + if ((SettingsManager.propertiesWidth ?? 0) < 100) { + SettingsManager.propertiesWidth = 250; + } + }) ); }; nextHref = (e: React.PointerEvent) => { @@ -181,22 +184,23 @@ export class LinkDocPreview extends React.Component { @computed get previewHeader() { return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
+
+ Edit Link
} placement="top"> +
+ +
+ +
{StrCast(this._markerTargetDoc.title).length > 16 ? StrCast(this._markerTargetDoc.title).substr(0, 16) + '...' : StrCast(this._markerTargetDoc.title)}

{StrCast(this._linkDoc.description)}

-
+
Next Link
} placement="top">
- - Edit Link
} placement="top"> -
- -
-
); @@ -280,9 +284,8 @@ export class LinkDocPreview extends React.Component { className="linkDocPreview" ref={this._linkDocRef} onPointerDown={this.followLinkPointerDown} - style={{ left: this.props.location[0], top: this.props.location[1], width: this._showEditor ? 'auto' : this.width() + borders, height: this._showEditor ? 'max-content' : this.height() + borders + (this.props.showHeader ? 37 : 0) }}> - {this._showEditor ? null : this.docPreview} - {!this._showEditor || !this._linkSrc || !this._linkDoc ? null : (this._showEditor = !this._showEditor))} />} + style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> + {this.docPreview}
); } diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index d63ed2575..4c3b8dabe 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -6,6 +6,13 @@ background-color: #fdfdfd; height: 100%; align-items: center; + .textContainer, + .text { + overflow: hidden; + text-overflow: ellipsis; + max-width: 80%; + text-align: center; + } } .textContainer { @@ -17,14 +24,6 @@ align-content: center; } -.textContainer, -.text { - overflow: hidden; - text-overflow: ellipsis; - max-width: 80%; - text-align: center; -} - .headerText { text-align: center; font-weight: bold; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1cb6bc3a2..c04b79a1e 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1199,7 +1199,7 @@ export class PresBox extends ViewBoxBaseComponent() { _batch: UndoManager.Batch | undefined = undefined; - public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void) => { + public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?:number) => { let batch: any; return ( () { min={min} max={max} value={value} + style={{marginLeft: hmargin, marginRight:hmargin, width: `calc(100% - ${2*(hmargin??0)}px)`}} className={`toolbar-slider ${active ? '' : 'none'}`} onPointerDown={e => { batch = UndoManager.StartBatch('pres slider'); -- cgit v1.2.3-70-g09d2 From c6d1059e24f362a167b9ac24e6f13d1e45361da9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Nov 2022 12:05:29 -0500 Subject: changes to streamline link editing UI (got rid of LinkEditor). cleaned up link (un)highlighting. --- src/client/documents/Documents.ts | 1 - src/client/util/DocumentManager.ts | 19 +- src/client/util/LinkFollower.ts | 2 +- src/client/util/LinkManager.ts | 2 +- src/client/util/SelectionManager.ts | 1 - src/client/views/DocumentDecorations.tsx | 14 +- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesView.scss | 15 +- src/client/views/PropertiesView.tsx | 94 +++-- .../CollectionFreeFormLinksView.tsx | 2 +- src/client/views/global/globalCssVariables.scss | 1 + .../views/global/globalCssVariables.scss.d.ts | 1 + src/client/views/linking/LinkEditor.scss | 334 --------------- src/client/views/linking/LinkEditor.tsx | 454 --------------------- src/client/views/linking/LinkMenu.tsx | 37 +- src/client/views/linking/LinkMenuGroup.tsx | 50 ++- src/client/views/linking/LinkMenuItem.tsx | 4 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 16 +- src/client/views/nodes/LinkAnchorBox.tsx | 110 ++--- src/fields/Doc.ts | 64 +-- 21 files changed, 194 insertions(+), 1031 deletions(-) delete mode 100644 src/client/views/linking/LinkEditor.scss delete mode 100644 src/client/views/linking/LinkEditor.tsx (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c5e08eeea..eed839520 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1375,7 +1375,6 @@ export namespace DocUtils { 'acl-Public': SharingPermissions.Augment, '_acl-Public': SharingPermissions.Augment, linkDisplay: true, - _hidden: true, _linkAutoMove: true, linkRelationship, _showCaption: 'description', diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7120eeb88..4f02a8202 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,7 +1,7 @@ import { action, observable, runInAction } from 'mobx'; import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { Cast } from '../../fields/Types'; +import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; @@ -36,15 +36,14 @@ export class DocumentManager { //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString); if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1'; - DocListCast(view.rootDoc.links).forEach(link => { - this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => - this.LinkedDocumentViews.push({ - a: viewAnchorIndex === 'anchor2' ? otherView : view, - b: viewAnchorIndex === 'anchor2' ? view : otherView, - l: link, - }) - ); - }); + const link = view.rootDoc; + this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => + this.LinkedDocumentViews.push({ + a: viewAnchorIndex === 'anchor2' ? otherView : view, + b: viewAnchorIndex === 'anchor2' ? view : otherView, + l: link, + }) + ); this.LinkAnchorBoxViews.push(view); // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString)); diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 5374bf44b..282116f1b 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -67,7 +67,7 @@ export class LinkFollower { docViewProps.ContainingCollectionDoc, action(() => { batch.end(); - DocumentDecorations.Instance.overrideBounds = false; + Doc.AddUnlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false))); }), altKey ? true : undefined ); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 49cc3218d..01f4df723 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -44,7 +44,7 @@ export class LinkManager { if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { Doc.GetProto(a1)[DirectLinksSym].add(link); Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); + //Doc.GetProto(link)[DirectLinksSym].add(link); // bcz: links are not linked to themself, so this was a hack } }) ); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 02d672a65..a3d6f5227 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,7 +4,6 @@ import { Doc, Opt } from '../../fields/Doc'; import { DocCast } from '../../fields/Types'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; -import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export namespace SelectionManager { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3efb5fb37..d1f0bf2ac 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); @@ -253,17 +253,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (selectedDocs.length) { if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key - CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), 'right'); + CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), OpenWhereMod.right); } else if (e.shiftKey) { // open centered in a new workspace with Shift Key const alias = Doc.MakeAlias(selectedDocs[0].props.Document); alias.context = undefined; alias.x = -alias[WidthSym]() / 2; alias.y = -alias[HeightSym]() / 2; - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right'); + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), OpenWhereMod.right); } else if (e.altKey) { // open same document in new tab - CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right'); + CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.right); } else { var openDoc = selectedDocs[0].props.Document; if (openDoc.layoutKey === 'layout_icon') { @@ -720,13 +720,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button - const hideOpenButton =hideDecorations || + const hideOpenButton = + hideDecorations || seldocview.props.hideOpenButton || seldocview.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; - const hideDeleteButton =hideDecorations || + const hideDeleteButton = + hideDecorations || this._isRounding || this._isRotating || seldocview.props.hideDeleteButton || diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 98d0378be..09063901d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -484,7 +484,7 @@ export class MainView extends React.Component { } globalPointerDown = action((e: PointerEvent) => { - runInAction(() => (Doc.HighlightBrush.linkFollowEffect = undefined)); + Doc.linkFollowUnhighlight(); AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 30806f718..897be9a32 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -4,8 +4,13 @@ height: 100%; width: 250; font-family: 'Roboto'; + font-size: 12px; cursor: auto; + .slider-text { + font-size: 8px; + } + overflow-x: hidden; overflow-y: auto; @@ -865,7 +870,15 @@ } .propertiesButton { - width: 4rem; + width: 2rem; + height: 2rem; + display: flex; + justify-content: center; + align-items: center; + > svg { + width: 15px; + height: 15px; + } } } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ad3f62990..e8fd540a8 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -55,7 +55,7 @@ export class PropertiesView extends React.Component { } @computed get selectedDoc() { - return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; + return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -306,7 +306,8 @@ export class PropertiesView extends React.Component { } @computed get links() { - return !this.selectedDoc ? null : ; + const selAnchor = this.selectedDocumentView?.anchorViewDoc; + return !selAnchor ? null : ; } @computed get layoutPreview() { @@ -1426,11 +1427,11 @@ export class PropertiesView extends React.Component { @undoBatch animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { const lanch = this.sourceAnchor; - const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : ''; return ( {direction}
}>
this.updateEffectDirection(direction)}> {icon ? : null}
@@ -1470,19 +1471,21 @@ export class PropertiesView extends React.Component { } }; - toggleProp = (e: React.PointerEvent, prop: string) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop])))); + toggleLinkProp = (e: React.PointerEvent, prop: string) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop])))); }; @computed get destinationAnchor() { const ldoc = LinkManager.currentLink; - const lanch = LinkManager.currentLinkAnchor; + const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; return ldoc ? DocCast(ldoc.anchor2) : ldoc; } @computed get sourceAnchor() { - return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + + return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); } toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { @@ -1545,9 +1548,8 @@ export class PropertiesView extends React.Component { const isNovice = Doc.noviceMode; const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; - const selectedDoc = this.selectedDoc; const indent = 30; - if (!selectedDoc && !this.isPres) { + if (!this.selectedDoc && !this.isPres) { return (
@@ -1556,7 +1558,7 @@ export class PropertiesView extends React.Component {
); } else { - if (selectedDoc && !this.isPres) { + if (this.selectedDoc && !this.isPres) { return (
{ {this.contextsSubMenu} {this.linksSubMenu} - {!selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(dv.rootDoc.links).includes(LinkManager.currentLink!)) ? null : ( + {!this.selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)) ? null : ( <> -
+

Relationship

{this.editRelationship} @@ -1584,11 +1586,41 @@ export class PropertiesView extends React.Component {

Description

{this.editDescription}
+
+

Show link

+ +
+
+

Auto-move anchors

+ +
+
+

Display arrow

+ +

Follow by

- this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkLocation, 'default')}> @@ -1598,7 +1630,7 @@ export class PropertiesView extends React.Component { - {selectedDoc.linksToAnnotation ? : null} + {LinkManager.currentLink?.linksToAnnotation ? : null}
@@ -1609,7 +1641,7 @@ export class PropertiesView extends React.Component { ))} -
+
{this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} @@ -1650,34 +1682,8 @@ export class PropertiesView extends React.Component {
-
-

Show link

- -
-
-

Auto-move anchor

- -
-
-

Display arrow

- -
-
-

Zoom % screen

+
+

Zoom %

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index b8344dc0c..9e360f557 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -2,10 +2,10 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import { Id } from '../../../../fields/FieldSymbols'; import { DocumentManager } from '../../../util/DocumentManager'; +import { LightboxView } from '../../LightboxView'; import './CollectionFreeFormLinksView.scss'; import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; import React = require('react'); -import { LightboxView } from '../../LightboxView'; @observer export class CollectionFreeFormLinksView extends React.Component> { diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index e68f9abe3..430a36dce 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -84,4 +84,5 @@ $TREE_BULLET_WIDTH: 20px; LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH; TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH; INK_MASK_SIZE: $INK_MASK_SIZE; + MEDIUM_GRAY: $medium-gray; } diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts index 76259113c..3375579d6 100644 --- a/src/client/views/global/globalCssVariables.scss.d.ts +++ b/src/client/views/global/globalCssVariables.scss.d.ts @@ -11,6 +11,7 @@ interface IGlobalScss { LEFT_MENU_WIDTH: string; TREE_BULLET_WIDTH: string; INK_MASK_SIZE: number; + MEDIUM_GRAY: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss deleted file mode 100644 index b0ee4e46d..000000000 --- a/src/client/views/linking/LinkEditor.scss +++ /dev/null @@ -1,334 +0,0 @@ -@import '../global/globalCssVariables'; - -.linkEditor { - width: 100%; - height: auto; - font-size: 13px; // TODO - user-select: none; - max-width: 280px; -} - -.linkEditor-button-back { - //margin-bottom: 6px; - border-radius: 10px; - width: 18px; - height: 18px; - padding: 0; - - &:hover { - cursor: pointer; - } -} - -.linkEditor-info { - padding-top: 12px; - padding-left: 5px; - padding-bottom: 3px; - //margin-bottom: 6px; - display: flex; - justify-content: space-between; - color: black; - - .linkEditor-linkedTo { - width: calc(100% - 46px); - overflow: hidden; - position: relative; - text-overflow: ellipsis; - white-space: pre; - - .linkEditor-downArrow { - &:hover { - cursor: pointer; - } - } - } -} - -.linkEditor-moreInfo { - margin-left: 12px; - padding-left: 13px; - padding-right: 6.5px; - padding-bottom: 4px; - font-size: 9px; - //font-style: italic; - text-decoration-color: grey; - - .button { - color: black; - - &:hover { - cursor: pointer; - } - } -} - -.linkEditor-zoomFollow { - padding-left: 26px; - padding-right: 6.5px; - padding-bottom: 3.5px; - display: flex; - - .linkEditor-zoomFollow-label { - text-decoration-color: black; - color: black; - line-height: 1.7; - } - - .linkEditor-zoomFollow-input { - display: block; - width: 20px; - } -} -.linkEditor-deleteBtn { - padding-left: 3px; -} - -.linkEditor-description { - padding-left: 26px; - padding-bottom: 3.5px; - display: flex; - - .linkEditor-description-label { - text-decoration-color: black; - color: black; - } - - .linkEditor-description-input { - display: flex; - - .linkEditor-description-editing { - min-width: 85%; - //border: 1px solid grey; - //border-radius: 4px; - padding-left: 2px; - //margin-right: 4px; - color: black; - text-decoration-color: grey; - } - - .linkEditor-description-add-button { - display: inline; - border-radius: 7px; - font-size: 9px; - background: black; - height: 80%; - color: white; - padding: 3px; - margin-left: 3px; - - &:hover { - cursor: pointer; - background: grey; - } - } - } -} - -.linkEditor-relationship-dropdown { - position: absolute; - width: 154px; - max-height: 90px; - overflow: auto; - background: white; - - p { - padding: 3px; - cursor: pointer; - border: 1px solid $medium-gray; - } - - p:hover { - background: $light-blue; - } -} - -.linkEditor-followingDropdown { - padding-left: 26px; - padding-right: 6.5px; - padding-bottom: 15px; - display: flex; - - &:hover { - cursor: pointer; - } - - .linkEditor-followingDropdown-label { - color: black; - padding-right: 3px; - } - - .linkEditor-followingDropdown-dropdown { - .linkEditor-followingDropdown-header { - border: 1px solid grey; - border-radius: 4px; - //background-color: rgb(236, 236, 236); - padding-left: 2px; - padding-right: 2px; - text-decoration-color: black; - color: rgb(94, 94, 94); - - .linkEditor-followingDropdown-icon { - float: right; - color: black; - } - } - - .linkEditor-followingDropdown-optionsList { - padding-left: 3px; - padding-right: 3px; - - &:last-child { - border-bottom: none; - } - - .linkEditor-followingDropdown-option { - border: 0.25px solid grey; - //background-color: rgb(236, 236, 236); - padding-left: 2px; - padding-right: 2px; - color: grey; - text-decoration-color: grey; - font-size: 9px; - border-top: none; - - &:hover { - background-color: rgb(187, 220, 231); - } - } - } - } -} - -.linkEditor-button, -.linkEditor-addbutton { - width: 15%; - border-radius: 7px; - font-size: 9px; - background: black; - padding: 3px; - height: 80%; - color: white; - text-align: center; - margin: auto; - margin-left: 3px; - > svg { - margin: auto; - } - &:disabled { - background-color: gray; - } -} - -.linkEditor-addbutton { - margin-left: 0px; -} - -.linkEditor-groupsLabel { - display: flex; - justify-content: space-between; -} - -.linkEditor-group { - background-color: $light-gray; - padding: 6px; - margin: 3px 0; - border-radius: 3px; - - .linkEditor-group-row { - display: flex; - margin-bottom: 3px; - } - - .linkEditor-group-row-label { - margin-right: 6px; - display: inline-block; - } - - .linkEditor-metadata-row { - display: flex; - justify-content: space-between; - margin-bottom: 6px; - - .linkEditor-error { - border-color: red; - } - - input { - width: calc(50% - 16px); - height: 20px; - } - - button { - width: 20px; - height: 20px; - margin-left: 3px; - padding: 0; - font-size: 10px; - } - } -} - -.linkEditor-dropdown { - width: 100%; - position: relative; - z-index: 999; - - input { - width: 100%; - } - - .linkEditor-options-wrapper { - width: 100%; - position: absolute; - top: 19px; - left: 0; - display: flex; - flex-direction: column; - } - - .linkEditor-option { - background-color: $light-gray; - border: 1px solid $medium-gray; - border-top: 0; - padding: 3px; - cursor: pointer; - - &:hover { - background-color: lightgray; - } - - &.onDown { - background-color: gray; - } - } -} - -.linkEditor-typeButton { - background-color: transparent; - color: $dark-gray; - height: 20px; - padding: 0 3px; - padding-bottom: 2px; - text-align: left; - text-transform: none; - letter-spacing: normal; - font-size: 12px; - font-weight: bold; - display: inline-block; - width: calc(100% - 40px); - - &:hover { - background-color: $white; - } -} - -.linkEditor-group-buttons { - height: 20px; - display: flex; - justify-content: flex-end; - margin-top: 5px; - - .linkEditor-button { - margin-left: 3px; - } -} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx deleted file mode 100644 index 01e33708a..000000000 --- a/src/client/views/linking/LinkEditor.tsx +++ /dev/null @@ -1,454 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc'; -import { DateCast, StrCast, Cast, BoolCast, DocCast, NumCast } from '../../../fields/Types'; -import { LinkManager } from '../../util/LinkManager'; -import { undoBatch } from '../../util/UndoManager'; -import './LinkEditor.scss'; -import { LinkRelationshipSearch } from './LinkRelationshipSearch'; -import React = require('react'); -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; -import { PresBox, PresEffect } from '../nodes/trails'; - -interface LinkEditorProps { - sourceDoc: Doc; - linkDoc: Doc; - showLinks?: () => void; - hideback?: boolean; -} -@observer -export class LinkEditor extends React.Component { - @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); - @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); - @observable zoomFollow = BoolCast(this.props.sourceDoc.followLinkZoom); - @observable audioFollow = BoolCast(this.props.sourceDoc.followLinkAudio); - @observable openDropdown: boolean = false; - @observable openEffectDropdown: boolean = false; - @observable private buttonColor: string = ''; - @observable private relationshipButtonColor: string = ''; - @observable private relationshipSearchVisibility: string = 'none'; - @observable private searchIsActive: boolean = false; - - //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; - - @undoBatch - setRelationshipValue = action((value: string) => { - if (LinkManager.currentLink) { - const prevRelationship = LinkManager.currentLink.linkRelationship as string; - LinkManager.currentLink.linkRelationship = value; - Doc.GetProto(LinkManager.currentLink).linkRelationship = value; - const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); - const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color - if (!linkRelationshipList?.includes(value)) { - linkRelationshipList.push(value); - linkRelationshipSizes.push(1); - const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; - linkColorList.push(randColor); - // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes - } else if (linkRelationshipList && value !== prevRelationship) { - const index = linkRelationshipList.indexOf(value); - //increment size of new relationship size - if (index !== -1 && index < linkRelationshipSizes.length) { - const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; - } - //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) - if (linkRelationshipList.includes(prevRelationship)) { - const pindex = linkRelationshipList.indexOf(prevRelationship); - if (pindex !== -1 && pindex < linkRelationshipSizes.length) { - const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); - } - } - } - this.relationshipButtonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.relationshipButtonColor = '')), - 750 - ); - return true; - } - }); - - /** - * returns list of strings with possible existing relationships that contain what is currently in the input field - */ - @action - getRelationshipResults = () => { - const query = this.relationship; //current content in input box - const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - if (linkRelationshipList) { - return linkRelationshipList.filter(rel => rel.includes(query)); - } - }; - - /** - * toggles visibility of the relationship search results when the input field is focused on - */ - @action - toggleRelationshipResults = () => { - this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none'; - }; - - @undoBatch - setDescripValue = action((value: string) => { - if (LinkManager.currentLink) { - Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.buttonColor = '')), - 750 - ); - return true; - } - }); - - onDescriptionKey = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.setDescripValue(this.description); - document.getElementById('input')?.blur(); - } - e.stopPropagation(); - }; - - onRelationshipKey = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.setRelationshipValue(this.relationship); - document.getElementById('input')?.blur(); - } - e.stopPropagation(); - }; - - onDescriptionDown = () => this.setDescripValue(this.description); - onRelationshipDown = () => this.setRelationshipValue(this.relationship); - - onBlur = () => { - //only hide the search results if the user clicks out of the input AND not on any of the search results - // i.e. if search is not active - if (!this.searchIsActive) { - this.toggleRelationshipResults(); - } - }; - onFocus = () => { - this.toggleRelationshipResults(); - }; - toggleSearchIsActive = () => { - this.searchIsActive = !this.searchIsActive; - }; - - @action - handleDescriptionChange = (e: React.ChangeEvent) => { - this.description = e.target.value; - }; - @action - handleRelationshipChange = (e: React.ChangeEvent) => { - this.relationship = e.target.value; - }; - @action - handleZoomFollowChange = () => { - this.props.sourceDoc.followLinkZoom = !this.props.sourceDoc.followLinkZoom; - }; - @action - handleAudioFollowChange = () => { - this.props.sourceDoc.followLinkAudio = !this.props.sourceDoc.followLinkAudio; - }; - @action - handleRelationshipSearchChange = (result: string) => { - this.setRelationshipValue(result); - this.toggleRelationshipResults(); - this.relationship = result; - }; - @computed - get editRelationship() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Relationship:
-
-
- - -
-
- Set -
-
-
- ); - } - @computed - get editZoomFollow() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Zoom To Link Target:
-
-
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleZoomFollowChange)} defaultChecked={this.zoomFollow} /> -
-
-
- ); - } - - @computed - get editAudioFollow() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Play Target Audio:
-
-
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleAudioFollowChange)} defaultChecked={this.audioFollow} /> -
-
-
- ); - } - - @computed - get editDescription() { - return ( -
-
Description:
-
-
- -
-
- Set -
-
-
- ); - } - - @action - changeDropdown = () => { - this.openDropdown = !this.openDropdown; - }; - - @undoBatch - changeFollowBehavior = action((follow: string) => { - this.openDropdown = false; - Doc.GetProto(this.props.linkDoc).followLinkLocation = follow; - }); - - @computed - get followingDropdown() { - return ( -
-
Follow by:
-
-
- {StrCast(this.props.linkDoc.followLinkLocation, 'default')} - -
-
-
this.changeFollowBehavior('default')}> - Default -
-
this.changeFollowBehavior('add:left')}> - Always opening in new left pane -
-
this.changeFollowBehavior('add:right')}> - Always opening in new right pane -
-
this.changeFollowBehavior('replace:right')}> - Always replacing right tab -
-
this.changeFollowBehavior('replace:left')}> - Always replacing left tab -
-
this.changeFollowBehavior('fullScreen')}> - Always opening full screen -
-
this.changeFollowBehavior('add')}> - Always opening in a new tab -
-
this.changeFollowBehavior('replace')}> - Replacing Tab -
-
this.changeFollowBehavior('inPlace')}> - Opening in Place -
- {this.props.linkDoc.linksToAnnotation ? ( -
this.changeFollowBehavior('openExternal')}> - Always open in external page -
- ) : null} -
-
-
- ); - } - - @computed get destinationAnchor() { - const ldoc = this.props.linkDoc; - if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) { - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); - } - return LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc) ?? this.props.sourceDoc; - } - @action - changeEffectDropdown = () => { - this.openEffectDropdown = !this.openEffectDropdown; - }; - - @undoBatch - changeEffect = action((follow: string) => { - this.openEffectDropdown = false; - this.destinationAnchor.presEffect = follow; - }); - - @computed - get effectDropdown() { - return ( -
-
Animation:
-
-
- {StrCast(this.destinationAnchor.presEffect, 'default')} - -
-
- {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( -
this.changeEffect(effect.toString())}> - {effect.toString()} -
- ))} -
-
-
- ); - } - - autoMove = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)))); - }; - - showAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden)))); - }; - - showLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)))); - }; - - deleteLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc)))); - }; - - render() { - const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - - return !destination ? null : ( -
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> -
- {!this.props.showLinks ? null : ( - Return to link menu
} placement="top"> - - - )} -

- Editing Link to: {StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))} -

- Delete Link
}> -
e.stopPropagation()}> - -
- -
-
- {this.props.linkDoc.author ? ( - <> - {' '} - Author: {StrCast(this.props.linkDoc.author)} - - ) : null} - {this.props.linkDoc.creationDate ? ( - <> - {' '} - Creation Date: - {DateCast(this.props.linkDoc.creationDate).toString()} - - ) : null} -
- {this.editDescription} - {this.editRelationship} - {this.editZoomFollow} - {this.editAudioFollow} -
- Show Anchor: - {this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}
}> -
e.stopPropagation()}> - -
- -
-
- Show Link Line: - {this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}
}> -
e.stopPropagation()}> - -
- -
-
- Freeze Anchor: - {this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}
}> -
e.stopPropagation()}> - -
- -
- {this.followingDropdown} - {this.effectDropdown} - {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.destinationAnchor.presTransition = timeInMS)))} -
-
Fast
-
Medium
-
Slow
-
{' '} -
- ); - } -} diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 0c46a6d96..c9112eec3 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -1,14 +1,13 @@ -import { action, computed, observable } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; +import { DocCast } from '../../../fields/Types'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import { LinkEditor } from './LinkEditor'; import './LinkMenu.scss'; import { LinkMenuGroup } from './LinkMenuGroup'; import React = require('react'); -import { emptyFunction } from '../../../Utils'; interface Props { docView: DocumentView; @@ -23,15 +22,9 @@ interface Props { @observer export class LinkMenu extends React.Component { _editorRef = React.createRef(); - @observable _editingLink?: Doc; @observable _linkMenuRef = React.createRef(); - clear = !this.props.clearLinkEditor - ? undefined - : action(() => { - this.props.clearLinkEditor?.(); - this._editingLink = undefined; - }); + clear = () => this.props.clearLinkEditor?.(); componentDidMount() { this.props.clearLinkEditor && document.addEventListener('pointerdown', this.onPointerDown, true); @@ -43,7 +36,7 @@ export class LinkMenu extends React.Component { onPointerDown = action((e: PointerEvent) => { LinkDocPreview.Clear(); if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) { - this.clear?.(); + this.clear(); } }); @@ -54,34 +47,20 @@ export class LinkMenu extends React.Component { */ renderAllGroups = (groups: Map>): Array => { const linkItems = Array.from(groups.entries()).map(group => ( - (this._editingLink = linkDoc))} - /> + )); return linkItems.length ? linkItems : this.props.style ? [<>] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; }; render() { - const sourceDoc = this.props.docView.props.Document; + const sourceDoc = this.props.docView.rootDoc; + const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc; const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds()); return (
- {this._editingLink ? ( -
- (this._editingLink = undefined))} /> -
- ) : ( -
{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
- )} +
{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceAnchor))}
); } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 9d2082e21..d02a1c4eb 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -2,19 +2,19 @@ import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; import { Doc, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { Cast } from '../../../fields/Types'; +import { Cast, DocCast } from '../../../fields/Types'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; import { LinkMenuItem } from './LinkMenuItem'; import React = require('react'); +import { DocumentType } from '../../documents/DocumentTypes'; interface LinkMenuGroupProps { sourceDoc: Doc; group: Doc[]; groupType: string; clearLinkEditor?: () => void; - showEditor: (linkDoc: Doc) => void; docView: DocumentView; itemHandler?: (doc: Doc) => void; } @@ -44,25 +44,33 @@ export class LinkMenuGroup extends React.Component { render() { const set = new Set(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { - const destination = - LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) || - LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); - if (destination && this.props.sourceDoc) { - return ( - - ); - } + const sourceDoc = + this.props.docView.anchorViewDoc ?? + (this.props.docView.rootDoc.type === DocumentType.LINK // + ? this.props.docView.props.LayoutTemplateString?.includes('anchor1') + ? DocCast(linkDoc.anchor1) + : DocCast(linkDoc.anchor2) + : this.props.sourceDoc); + const destDoc = !sourceDoc + ? undefined + : this.props.docView.rootDoc.type === DocumentType.LINK + ? this.props.docView.props.LayoutTemplateString?.includes('anchor1') + ? DocCast(linkDoc.anchor2) + : DocCast(linkDoc.anchor1) + : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); + return !destDoc || !sourceDoc ? null : ( + + ); }); return ( diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index c3705b0e1..fb4c6873e 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; import React = require('react'); +import { SelectionManager } from '../../util/SelectionManager'; interface LinkMenuItemProps { groupType: string; @@ -26,7 +27,6 @@ interface LinkMenuItemProps { sourceDoc: Doc; destinationDoc: Doc; clearLinkEditor?: () => void; - showEditor: (linkDoc: Doc) => void; menuRef: React.Ref; itemHandler?: (doc: Doc) => void; } @@ -100,10 +100,10 @@ export class LinkMenuItem extends React.Component { }, emptyFunction, action(() => { + SelectionManager.SelectView(this.props.docView, false); if ((SettingsManager.propertiesWidth ?? 0) < 100) { SettingsManager.propertiesWidth = 250; } - //this.props.showEditor(this.props.linkDoc); }) ); }; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 627487a9e..99fa62fa7 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -305,7 +305,7 @@ export class DocumentLinksButton extends React.Component - {title}
: <>}>{this.linkButtonInner} + {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : {title}
}>{this.linkButtonInner}}
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2729a5047..a8bea61c9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer, renderReporter } from 'mobx-react'; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -11,7 +11,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; @@ -1156,7 +1156,7 @@ export class DocumentViewInternal extends DocComponent this.props.PanelHeight() || 1; anchorStyleProvider = (doc: Opt, props: Opt, property: string): any => { // prettier-ignore - switch (property) { + switch (property.split(':')[0]) { case StyleProp.ShowTitle: return ''; case StyleProp.PointerEvents: return 'none'; case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox @@ -1188,7 +1188,7 @@ export class DocumentViewInternal extends DocComponent !d.hidden); + const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.linkDisplay); return filtered.map((link, i) => (
{ linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale; @computed get linkCountView() { - return (this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton) && - DocumentLinksButton.LinkEditorDocView?.rootDoc !== this.rootDoc ? null : ( + return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton ? null : ( ); } @@ -1654,6 +1653,9 @@ export class DocumentView extends React.Component { startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); + @computed get anchorViewDoc() { + return this.props.LayoutTemplateString?.includes('anchor2') ? DocCast(this.rootDoc['anchor2']) : this.props.LayoutTemplateString?.includes('anchor1') ? DocCast(this.rootDoc['anchor1']) : this.rootDoc; + } docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index be9565452..e89076c1f 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,4 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; @@ -7,20 +6,17 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; -import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; -import { LinkEditor } from '../linking/LinkEditor'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import React = require('react'); -import { OpenWhere } from './DocumentView'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; +import { LinkManager } from '../../util/LinkManager'; +import globalCssVariables = require('../global/globalCssVariables.scss'); +import { SelectionManager } from '../../util/SelectionManager'; @observer export class LinkAnchorBox extends ViewBoxBaseComponent() { @@ -34,15 +30,23 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { _timeout: NodeJS.Timeout | undefined; @observable _x = 0; @observable _y = 0; - @observable _selected = false; - @observable _editing = false; - @observable _forceOpen = false; onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false); + const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); + setupMoveUpEvents( + this, + e, + this.onPointerMove, + emptyFunction, + (e, doubleTap) => { + if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false); + else this.props.select(false); + }, + false + ); }; onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { - const cdiv = this._ref && this._ref.current && this._ref.current.parentElement; + const cdiv = this._ref?.current?.parentElement; if (!this._isOpen && cdiv) { const bounds = cdiv.getBoundingClientRect(); const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); @@ -60,58 +64,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { } return false; }); - @action - onClick = (e: React.MouseEvent) => { - if (e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton) { - this.props.select(false); - } - if (!this._doubleTap && !e.ctrlKey && e.button < 2) { - const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); - this._editing = true; - anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false); - if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) { - this._timeout = setTimeout( - action(() => { - LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false); - this._editing = false; - }), - 300 - (Date.now() - this._lastTap) - ); - e.stopPropagation(); - } - } else { - this._timeout && clearTimeout(this._timeout); - this._timeout = undefined; - this._doubleTap = false; - this.openLinkEditor(e); - e.stopPropagation(); - } - }; - openLinkDocOnRight = (e: React.MouseEvent) => { - this.props.addDocTab(this.rootDoc, OpenWhere.addRight); - }; - openLinkTargetOnRight = (e: React.MouseEvent) => { - const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); - alias._isLinkButton = undefined; - alias.layoutKey = 'layout'; - this.props.addDocTab(alias, OpenWhere.addRight); - }; - @action - openLinkEditor = action((e: React.MouseEvent) => { - SelectionManager.DeselectAll(); - this._editing = this._forceOpen = true; - }); - - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: 'Open Link Target on Right', event: () => this.openLinkTargetOnRight(e), icon: 'eye' }); - funcs.push({ description: 'Open Link on Right', event: () => this.openLinkDocOnRight(e), icon: 'eye' }); - funcs.push({ description: 'Open Link Editor', event: () => this.openLinkEditor(e), icon: 'eye' }); - funcs.push({ description: 'Toggle Always Show Link', event: () => (this.props.Document.linkDisplay = !this.props.Document.linkDisplay), icon: 'eye' }); - - ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); - }; + specificContextMenu = (e: React.MouseEvent): void => {}; render() { TraceMobx(); @@ -122,22 +76,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor'); const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1'; const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; - const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title); - const flyout = ( -
Doc.UnBrushDoc(this.rootDoc)}> - {})} /> - {!this._forceOpen ? null : ( -
(this._isOpen = this._editing = this._forceOpen = false))}> - -
- )} -
- ); + const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor1') ? 'anchor1' : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor2') ? 'anchor2' : ''; return (
LinkDocPreview.SetLinkInfo({ docProps: this.props, @@ -149,24 +94,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { }) } onPointerDown={this.onPointerDown} - onClick={this.onClick} - title={targetTitle} onContextMenu={this.specificContextMenu} - ref={this._ref} style={{ + border: selView && this.rootDoc[selView] === this.rootDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined, background, left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, transform: `scale(${anchorScale})`, - }}> - {!this._editing && !this._forceOpen ? null : ( - (this._isOpen = true)} onClose={action(() => (this._isOpen = this._forceOpen = this._editing = false))}> - - - - - )} -
+ }} + /> ); } } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c10751698..c6cabe269 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -94,6 +94,8 @@ export function DocListCastOrNull(field: FieldResult) { export const WidthSym = Symbol('Width'); export const HeightSym = Symbol('Height'); +export const AnimationSym = Symbol('Animation'); +export const HighlightSym = Symbol('Highlight'); export const DataSym = Symbol('Data'); export const LayoutSym = Symbol('Layout'); export const FieldsSym = Symbol('Fields'); @@ -329,6 +331,8 @@ export class Doc extends RefField { @observable private ___fieldKeys: any = {}; @observable public [AclSym]: { [key: string]: symbol } = {}; @observable public [DirectLinksSym]: Set = new Set(); + @observable public [AnimationSym]: Opt; + @observable public [HighlightSym]: boolean = false; private [UpdatingFromServer]: boolean = false; private [ForceServerWrite]: boolean = false; @@ -1261,53 +1265,55 @@ export namespace Doc { } export function linkFollowUnhighlight() { - Doc.UnhighlightAll(); + UnhighlightWatchers.length = 0; + highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc)); document.removeEventListener('pointerdown', linkFollowUnhighlight); - runInAction(() => (HighlightBrush.linkFollowEffect = undefined)); } - let _lastDate = 0; + let UnhighlightWatchers: (() => void)[] = []; + let UnhighlightTimer: any; + export function AddUnlightWatcher(watcher: () => void) { + if (UnhighlightTimer) { + UnhighlightWatchers.push(watcher); + } else watcher(); + } export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) { - //linkFollowUnhighlight(); - // runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = undefined)); - // setTimeout(() => runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect))); - runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect)); - (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs)); + linkFollowUnhighlight(); + (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect)); document.removeEventListener('pointerdown', linkFollowUnhighlight); document.addEventListener('pointerdown', linkFollowUnhighlight); - const lastDate = (_lastDate = Date.now()); - window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); + if (UnhighlightTimer) clearTimeout(UnhighlightTimer); + UnhighlightTimer = window.setTimeout(() => { + UnhighlightWatchers.forEach(watcher => watcher()); + linkFollowUnhighlight(); + UnhighlightTimer = 0; + }, 5000); } - export class HighlightBrush { - @observable HighlightedDoc: Map = new Map(); - @observable static linkFollowEffect: Doc | undefined; - } - const highlightManager = new HighlightBrush(); + var highlightedDocs = new Set(); export function IsHighlighted(doc: Doc) { if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false; - return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); + return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym]; } - export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { + export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, true); - dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true); + doc[AnimationSym] = presEffect; + highlightedDocs.add(doc); + doc[HighlightSym] = true; + if (dataAndDisplayDocs) { + highlightedDocs.add(Doc.GetProto(doc)); + Doc.GetProto(doc)[HighlightSym] = true; + } }); } export function UnHighlightDoc(doc: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, false); - highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false); + highlightedDocs.delete(doc); + highlightedDocs.delete(Doc.GetProto(doc)); + doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false; + doc[AnimationSym] = undefined; }); } - export function UnhighlightAll() { - const mapEntries = highlightManager.HighlightedDoc.keys(); - let docEntry: IteratorResult; - while (!(docEntry = mapEntries.next()).done) { - const targetDoc = docEntry.value; - targetDoc && Doc.UnHighlightDoc(targetDoc); - } - } export function UnBrushAllDocs() { brushManager.BrushedDoc.clear(); } -- cgit v1.2.3-70-g09d2 From f61092c162378f8884335b0988058420b7982f7d Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Nov 2022 15:31:04 -0500 Subject: fixed docDecoration appearance after following link and clicking before highlight times out. fixed selection of links to allow setting background color of link line and link text box. added background for link description text and highlighting of link when clicked/selected. --- src/client/util/SelectionManager.ts | 6 +- src/client/views/MainView.tsx | 6 +- src/client/views/PropertiesView.tsx | 199 +++++++++++---------- .../CollectionFreeFormLinkView.scss | 9 +- .../CollectionFreeFormLinkView.tsx | 56 ++++-- .../CollectionFreeFormLinksView.tsx | 3 +- .../collections/collectionFreeForm/MarqueeView.tsx | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 7 +- src/fields/Doc.ts | 2 +- 9 files changed, 159 insertions(+), 132 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index a3d6f5227..646942569 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,9 +1,10 @@ import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; -import { Doc, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { DocCast } from '../../fields/Types'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; +import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export namespace SelectionManager { @@ -21,6 +22,9 @@ export namespace SelectionManager { // if doc is not in SelectedDocuments, add it if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) { if (!ctrlPressed) { + if (LinkManager.currentLink && !DocListCast(docView.rootDoc.links).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) { + LinkManager.currentLink = undefined; + } this.DeselectAll(); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 09063901d..165bb69bb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,15 +3,15 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import 'browndash-components/dist/styles/global.min.css'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import 'normalize.css'; import * as React from 'react'; -import * as ReactDOM from 'react-dom/client'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; import { DocCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocUtils } from '../documents/Documents'; @@ -58,14 +58,12 @@ import { LinkDocPreview } from './nodes/LinkDocPreview'; import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { PresBox } from './nodes/trails'; -import { WebBox } from './nodes/WebBox'; import { OverlayView } from './OverlayView'; import { AnchorMenu } from './pdf/AnchorMenu'; import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; -import 'browndash-components/dist/styles/global.min.css'; const _global = (window /* browser */ || global) /* node */ as any; @observer diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e8fd540a8..05dd376f2 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1549,6 +1549,7 @@ export class PropertiesView extends React.Component { const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; const indent = 30; + const hasSelectedAnchor = SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)); if (!this.selectedDoc && !this.isPres) { return (
@@ -1575,7 +1576,7 @@ export class PropertiesView extends React.Component { {this.contextsSubMenu} {this.linksSubMenu} - {!this.selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)) ? null : ( + {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : ( <>
@@ -1617,108 +1618,110 @@ export class PropertiesView extends React.Component {
-
-
-

Follow by

- -
-
-

Animation

- -
- {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} - {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} - {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} - {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} - {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} + {!hasSelectedAnchor ? null : ( +
+
+

Follow by

+
-
- {PresBox.inputter( - '0.1', - '0.1', - '10', - NumCast(this.sourceAnchor?.presTransition) / 1000, - true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.presTransition = timeInMS)), - indent - )}{' '} -
-
Fast
-
Slow
-
{' '} -
-

Play Target Audio

- -
-
-

Zoom %

-
- -
-
this.setZoom(String(zoom), 0.1))}> - -
-
this.setZoom(String(zoom), -0.1))}> - +
+

Animation

+ +
+ {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} +
+
+ {PresBox.inputter( + '0.1', + '0.1', + '10', + NumCast(this.sourceAnchor?.presTransition) / 1000, + true, + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.presTransition = timeInMS)), + indent + )}{' '} +
+
Fast
+
Slow
+
{' '} +
+

Play Target Audio

+ +
+
+

Zoom %

+
+ +
+
this.setZoom(String(zoom), 0.1))}> + +
+
this.setZoom(String(zoom), -0.1))}> + +
+
- + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} +
+
0%
+
100%
+
{' '}
- {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} -
-
0%
-
100%
-
{' '} -
+ )} )} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 858719a08..b44acfce8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -5,15 +5,8 @@ transition: opacity 0.5s ease-in; fill: transparent; } -.collectionfreeformlinkview-linkCircle { - stroke: rgb(0,0,0); - opacity: 0.5; - pointer-events: all; - cursor: pointer; -} .collectionfreeformlinkview-linkText { - stroke: rgb(0,0,0); - opacity: 0.5; + stroke: rgb(0, 0, 0); pointer-events: all; cursor: move; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index b39fcbd48..be20bf207 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import { Doc, Field } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { Cast, NumCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -117,12 +117,16 @@ export class CollectionFreeFormLinkView extends React.Component { + action(() => { + SelectionManager.DeselectAll(); + SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; + this.toggleProperties(); // OverlayView.Instance.addElement( // { })} // />, { x: 300, y: 300 }); - } + }) ); }; @@ -168,8 +172,11 @@ export class CollectionFreeFormLinkView extends React.Component { + SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; this.toggleProperties(); }; @@ -232,44 +239,65 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List; const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + const linkDescription = Field.toString(link.description as any as Field); const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; //access stroke color using index of the relationship in the color list (default black) - const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? 'black' : linkColorList[currRelationshipIndex]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex]; // const hexStroke = this.rgbToHex(stroke) //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px'; - if (this.props.LinkDocs[0].linkDisplayArrow === undefined) { - this.props.LinkDocs[0].linkDisplayArrow = false; + if (link.linkDisplayArrow === undefined) { + link.linkDisplayArrow = false; } - return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || (!this.props.LinkDocs[0].linkDisplay && !aActive && !bActive) ? null : ( + return link.opacity === 0 || !a.width || !b.width || (!link.linkDisplay && !aActive && !bActive) ? null : ( <> - - + + + + + + + + + + + + + + + - {textX === undefined ? null : ( - - {Field.toString(this.props.LinkDocs[0].description as any as Field)} + {textX === undefined || !linkDescription ? null : ( + +   + {linkDescription} +   )} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 9e360f557..420e6a318 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -8,7 +8,7 @@ import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; import React = require('react'); @observer -export class CollectionFreeFormLinksView extends React.Component> { +export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)) .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath))) @@ -19,7 +19,6 @@ export class CollectionFreeFormLinksView extends React.Component {this.uniqueConnections} - {this.props.children}
); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index a020b67cd..3fd6ca803 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -17,6 +17,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; @@ -120,7 +121,7 @@ export class MarqueeView extends React.Component this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), 'add:right')); + cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), OpenWhere.addRight)); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index c71552a6f..fe8c85e5e 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -13,6 +13,7 @@ import { WebField } from '../../../../fields/URLField'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; import { aggregateBounds, StopEvent, Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { LinkManager } from '../../../util/LinkManager'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; @@ -547,11 +548,11 @@ ScriptingGlobals.add(function setView(view: string) { // toggle: Set overlay status of selected document ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement(); + const selected = SelectionManager.Views().lastElement()?.props.Document ?? LinkManager.currentLink; if (checkResult) { - return selected?.props.Document._backgroundColor ?? 'transparent'; + return selected?._backgroundColor ?? 'transparent'; } - if (selected) selected.props.Document._backgroundColor = color; + if (selected) selected._backgroundColor = color; }); // toggle: Set overlay status of selected document diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c6cabe269..40152551e 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1265,6 +1265,7 @@ export namespace Doc { } export function linkFollowUnhighlight() { + UnhighlightWatchers.forEach(watcher => watcher()); UnhighlightWatchers.length = 0; highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc)); document.removeEventListener('pointerdown', linkFollowUnhighlight); @@ -1284,7 +1285,6 @@ export namespace Doc { document.addEventListener('pointerdown', linkFollowUnhighlight); if (UnhighlightTimer) clearTimeout(UnhighlightTimer); UnhighlightTimer = window.setTimeout(() => { - UnhighlightWatchers.forEach(watcher => watcher()); linkFollowUnhighlight(); UnhighlightTimer = 0; }, 5000); -- cgit v1.2.3-70-g09d2 From 9d88adb19c2caf715b56c5ed40a500b9ef1491aa Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 30 Nov 2022 13:38:35 -0500 Subject: fixed link properties for link anchors to set fields affecting target, not source. --- src/client/views/PropertiesView.tsx | 40 +++++++++++-------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 05dd376f2..3e199919e 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -4,7 +4,7 @@ import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@material-ui/core'; import { intersection } from 'lodash'; -import { action, autorun, computed, Lambda, observable, trace } from 'mobx'; +import { action, autorun, computed, Lambda, observable } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, DocListCast, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; @@ -23,6 +23,7 @@ import { SharingManager } from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { EditableView } from './EditableView'; +import { Colors } from './global/globalEnums'; import { InkStrokeProperties } from './InkStrokeProperties'; import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; import { FilterBox } from './nodes/FilterBox'; @@ -33,7 +34,6 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; -import { Colors } from './global/globalEnums'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -1409,44 +1409,28 @@ export class PropertiesView extends React.Component { }); @undoBatch - changeAnimationBehavior = action((behavior: string) => { - const lanch = this.sourceAnchor; - if (lanch) { - lanch.presEffect = behavior; - } - }); + changeFollowBehavior = action((follow: string) => this.selectedDoc && (this.selectedDoc.followLinkLocation = follow)); + @undoBatch - @action - updateEffectDirection = (effect: PresEffectDirection) => { - const lanch = this.sourceAnchor; - if (lanch) { - lanch.presEffectDirection = effect; - } - }; + changeAnimationBehavior = action((behavior: string) => this.destinationAnchor && (this.destinationAnchor.presEffect = behavior); @undoBatch + changeEffectDirection = action((effect: PresEffectDirection) => this.destinationAnchor && (this.destinationAnchor.presEffectDirection = effect); + animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { - const lanch = this.sourceAnchor; + const lanch = this.destinationAnchor; const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : ''; return ( {direction}
}>
this.updateEffectDirection(direction)}> + onClick={() => this.changeEffectDirection(direction)}> {icon ? : null}
); }; - @undoBatch - changeFollowBehavior = action((follow: string) => { - if (LinkManager.currentLink && this.selectedDoc) { - this.selectedDoc.followLinkLocation = follow; - return true; - } - }); - onSelectOutDesc = () => { this.setDescripValue(this.description); document.getElementById('link_description_input')?.blur(); @@ -1637,7 +1621,7 @@ export class PropertiesView extends React.Component {

Animation

- this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.destinationAnchor?.presEffect, 'default')}> {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( @@ -1655,9 +1639,9 @@ export class PropertiesView extends React.Component { '0.1', '0.1', '10', - NumCast(this.sourceAnchor?.presTransition) / 1000, + NumCast(this.destinationAnchor?.presTransition) / 1000, true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.presTransition = timeInMS)), + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.destinationAnchor && (this.destinationAnchor.presTransition = timeInMS)), indent )}{' '}
Date: Thu, 1 Dec 2022 10:13:03 -0500 Subject: refactored process of following links / jumping to docs and added following options for zoomTime, etc instead of setting temporary fields on docs. --- src/client/documents/Documents.ts | 8 +- src/client/util/DocumentManager.ts | 98 +++--- .../util/Import & Export/DirectoryImportBox.tsx | 334 +++++++++++---------- src/client/util/LinkFollower.ts | 84 +++--- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/MainView.tsx | 2 +- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/PropertiesView.tsx | 28 +- .../views/collections/CollectionNoteTakingView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../collectionSchema/CollectionSchemaCells.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 26 +- src/client/views/nodes/ImageBox.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- src/client/views/nodes/trails/PresBox.tsx | 24 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 8 +- src/client/views/search/SearchBox.tsx | 2 +- 27 files changed, 354 insertions(+), 353 deletions(-) (limited to 'src/client/views/PropertiesView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index eed839520..d13d96dd3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -157,7 +157,7 @@ export class DocumentOptions { _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed - _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target + _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs @@ -272,7 +272,7 @@ export class DocumentOptions { clipWidth?: number; // percent transition from before to after in comparisonBox dockingConfig?: string; annotationOn?: Doc; - isPushpin?: boolean; + followLinkToggle?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document noteType?: string; @@ -1703,7 +1703,7 @@ export namespace DocUtils { } export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.isPushpin) return undefined; + if (doc.followLinkToggle) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { @@ -1711,7 +1711,7 @@ export namespace DocUtils { title: 'pushpin', label: '', annotationOn: Cast(doc.annotationOn, Doc, null), - isPushpin: true, + followLinkToggle: true, icon: 'map-pin', x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4f02a8202..1b63b615b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,7 +5,7 @@ import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; @@ -171,19 +171,13 @@ export class DocumentManager { }; public jumpToDocument = ( targetDoc: Doc, // document to display - willZoom: boolean, // whether to zoom doc to take up most of screen + options: DocFocusOptions, // options for how to navigate to target createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist docContext: Doc[], // context to load that should contain the target - linkDoc?: Doc, // link that's being followed - closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there - originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc - finished?: () => void, - originalTarget?: Doc, - noSelect?: boolean, - presZoomScale?: number + finished?: () => void ): void => { - originalTarget = originalTarget ?? targetDoc; - const docView = this.getFirstDocumentView(targetDoc, originatingDoc); + const originalTarget = options.originalTarget ?? targetDoc; + const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; @@ -195,14 +189,14 @@ export class DocumentManager { } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = resolvedTarget; - if (originatingDoc?.isPushpin) { + if (options.toggleTarget) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); - !noSelect && docView?.select(false); + !options.noSelect && docView?.select(false); } finished?.(); }; @@ -216,9 +210,8 @@ export class DocumentManager { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { annoContainerView.iconify(() => annoContainerView.focus(targetDoc, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(true); @@ -232,13 +225,12 @@ export class DocumentManager { } } if (focusView) { - !noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox - if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); + !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox + if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => - focusView.focus(originalTarget ?? targetDoc, { + focusView.focus(originalTarget, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(forceDidFocus || didFocus); @@ -262,13 +254,12 @@ export class DocumentManager { targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden targetDocContext._viewTransition = 'transform 500ms'; targetDocContextView.props.focus(targetDocContextView.rootDoc, { - willZoom, + ...options, + // originalTarget, // needed? afterFocus: async () => { targetDocContext._viewTransition = undefined; if (targetDocContext.layoutKey === 'layout_icon') { - targetDocContextView.iconify(() => - this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished)); } return ViewAdjustment.doNothing; }, @@ -281,56 +272,35 @@ export class DocumentManager { finished?.(); } else { // no timecode means we need to find the context view and focus on our target - const findView = (delay: number) => { - const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { - // we found the target in the context. - Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.focus(targetDoc, { - willZoom, - afterFocus: (didFocus: boolean) => - new Promise(res => { - !noSelect && focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }); // focus on the target in the context - } else if (delay > 1000) { - // we didn't find the target, so it must have moved out of the context. Go back to just creating it. - if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); - if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } else { - setTimeout(() => findView(delay + 200), 200); - } - }; - setTimeout(() => findView(0), 0); + const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { + // we found the target in the context. + Doc.linkFollowHighlight(retryDocView.rootDoc); + retryDocView.focus(targetDoc, { + ...options, + // originalTarget -- needed? + afterFocus: (didFocus: boolean) => + new Promise(res => { + !options.noSelect && focusAndFinish(true); + res(ViewAdjustment.doNothing); + }), + }); // focus on the target in the context + } else if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document + createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } } } else { if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { const docContextView = this.getFirstDocumentView(docContext[0]); if (docContextView) { - return docContextView.iconify(() => - this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished)); } } // there's no context view so we need to create one first and try again when that finishes createViewFunc( targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => - this.jumpToDocument( - targetDoc, - willZoom, - (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), - docContext, - linkDoc, - true /* if target not found, get rid of context just created */, - originatingDoc, - finished, - originalTarget - ) + () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished) ); } } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 37571ae01..916eee4b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,27 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { BatchedArray } from "array-batcher"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import Measure, { ContentRect } from "react-measure"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast } from "../../../fields/Types"; -import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { DocumentManager } from "../DocumentManager"; -import "./DirectoryImportBox.scss"; -import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; -import React = require("react"); +import { BatchedArray } from 'array-batcher'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import Measure, { ContentRect } from 'react-measure'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast } from '../../../fields/Types'; +import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; +import { Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import { DocumentManager } from '../DocumentManager'; +import './DirectoryImportBox.scss'; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; +import React = require('react'); -const unsupported = ["text/html", "text/plain"]; +const unsupported = ['text/html', 'text/plain']; @observer export class DirectoryImportBox extends React.Component { @@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component { @observable private top = 0; @observable private left = 0; private dimensions = 50; - @observable private phase = ""; + @observable private phase = ''; private disposer: Opt; @observable private entries: ImportMetadataEntry[] = []; @@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component { @observable private uploading = false; @observable private removeHover = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DirectoryImportBox, fieldKey); + } constructor(props: FieldViewProps) { super(props); @@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component { handleSelection = async (e: React.ChangeEvent) => { runInAction(() => { this.uploading = true; - this.phase = "Initializing download..."; + this.phase = 'Initializing download...'; }); const docs: Doc[] = []; @@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component { const files = e.target.files; if (!files || files.length === 0) return; - const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; + const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0]; const validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component { const sizes: number[] = []; const modifiedDates: number[] = []; - runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); + runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`)); const batched = BatchedArray.from(validated, { batchSize: 15 }); const uploads = await batched.batchedMapAsync>(async (batch, collector) => { @@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component { modifiedDates.push(file.lastModified); }); collector.push(...(await Networking.UploadFilesToServer(batch))); - runInAction(() => this.completed += batch.length); + runInAction(() => (this.completed += batch.length)); }); - await Promise.all(uploads.map(async response => { - const { source: { type }, result } = response; - if (result instanceof Error) { - return; - } - const { accessPaths, exifData } = result; - const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); - const { data, error } = exifData; - if (document) { - Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); - docs.push(document); - } - })); + await Promise.all( + uploads.map(async response => { + const { + source: { type }, + result, + } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; + const path = Utils.prepend(accessPaths.agnostic.client); + const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const { data, error } = exifData; + if (document) { + Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); + docs.push(document); + } + }) + ); for (let i = 0; i < docs.length; i++) { const doc = docs[i]; @@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component { _height: 500, _chromeHidden: true, x: NumCast(doc.x), - y: NumCast(doc.y) + offset + y: NumCast(doc.y) + offset, }; const parent = this.props.ContainingCollectionView; if (parent) { @@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component { if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; + const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } - runInAction(() => this.phase = 'External: uploading files to Google Photos...'); + runInAction(() => (this.phase = 'External: uploading files to Google Photos...')); await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []); + DocumentManager.Instance.jumpToDocument(importContainer, { willZoom: true }, undefined, []); } runInAction(() => { @@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component { this.quota = 1; this.completed = 0; }); - } + }; componentDidMount() { - this.selector.current!.setAttribute("directory", ""); - this.selector.current!.setAttribute("webkitdirectory", ""); + this.selector.current!.setAttribute('directory', ''); + this.selector.current!.setAttribute('webkitdirectory', ''); this.disposer = reaction( () => this.completed, - completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`) + completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)) ); } @@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component { const offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; - } + }; @action addMetadataEntry = async () => { @@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component { entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; - Doc.AddDocToList(this.props.Document, "data", entryDoc); - } + Doc.AddDocToList(this.props.Document, 'data', entryDoc); + }; @action remove = async (entry: ImportMetadataEntry) => { @@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component { } } } - } + }; render() { const dimensions = 50; @@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component { const uploading = this.uploading; const showRemoveLabel = this.removeHover; const persistent = this.persistent; - let percent = `${completed / quota * 100}`; - percent = percent.split(".")[0]; - percent = percent.startsWith("100") ? "99" : percent; + let percent = `${(completed / quota) * 100}`; + percent = percent.split('.')[0]; + percent = percent.startsWith('100') ? '99' : percent; const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; - const message = {this.phase}; - const centerPiece = this.phase.includes("Google Photos") ? - - :
{this.phase}; + const centerPiece = this.phase.includes('Google Photos') ? ( + + ) : ( +
{percent}%
; + color: 'white', + marginLeft: this.left + marginOffset, + }}> + {percent}% +
+ ); return ( - {({ measureRef }) => -
+ {({ measureRef }) => ( +
{message} + position: 'absolute', + display: 'none', + }} + />