aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
commitb158b7ffb564db8dc60da9b80b01b10f1ed8b7cf (patch)
treeaf35e320eb876c12c617fda2eb70ce19ef376b67 /src/client/views/collections/collectionFreeForm
parenteecc7ee1d14719d510ec2975826022c565a35e5f (diff)
parent3b90916af8ffcbeaf5b8a3336009b84e19c22fa9 (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx58
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx178
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss39
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx108
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx22
6 files changed, 222 insertions, 187 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 58f6b1593..73dd7fea3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { SettingsManager } from '../../../util/SettingsManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
-// import assets from './assets/link.png';
+import { Doc } from '../../../../fields/Doc';
/**
* An Fsa Arc. The first array element is a test condition function that will be observed.
@@ -15,18 +15,18 @@ import './CollectionFreeFormView.scss';
export type infoArc = [() => any, (res?: any) => infoState];
export const StateMessage = Symbol('StateMessage');
-export const StateEntryFunc = Symbol('StateEntryFunc');
export const StateMessageGIF = Symbol('StateMessageGIF');
+export const StateEntryFunc = Symbol('StateEntryFunc');
export class infoState {
[StateMessage]: string = '';
- [StateEntryFunc]?: () => any;
[StateMessageGIF]?: string = '';
+ [StateEntryFunc]?: () => any;
[key: string]: infoArc;
- constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) {
+ constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) {
this[StateMessage] = message;
Object.assign(this, arcs);
- this[StateEntryFunc] = entryFunc;
this[StateMessageGIF] = messageGif;
+ this[StateEntryFunc] = entryFunc;
}
}
@@ -36,16 +36,17 @@ export class infoState {
* @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and
* field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire),
* and an arc transition action function (that sets the next state)
+ * @param gif the gif displayed when in this state
* @param entryFunc a function to call when entering the state
* @returns an FSA state
*/
export function InfoState(
msg: string, //
arcs: { [key: string]: infoArc },
- entryFunc?: () => any,
- gif?: string
+ gif?: string,
+ entryFunc?: () => any
) {
- return new infoState(msg, arcs, entryFunc, gif);
+ return new infoState(msg, arcs, gif, entryFunc);
}
export interface CollectionFreeFormInfoStateProps {
@@ -57,7 +58,7 @@ export interface CollectionFreeFormInfoStateProps {
@observer
export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> {
_disposers: IReactionDisposer[] = [];
- @observable _hide = false;
+ @observable _expanded = false;
constructor(props: any) {
super(props);
@@ -72,22 +73,16 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
}
clearState = () => this._disposers.map(disposer => disposer());
- initState = () =>
- (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => {
- return reaction(
- //
+ initState = () => (this._disposers =
+ this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
+ arc => reaction(
arc.test,
- res => {
- if (res) {
- const next = arc.act(res);
- this._props.next(next);
- }
- },
+ res => res && this._props.next(arc.act(res)),
{ fireImmediately: true }
- );
- }));
+ )
+ )); // prettier-ignore
- componentDidMount(): void {
+ componentDidMount() {
this.initState();
}
componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) {
@@ -95,17 +90,24 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
this.clearState();
this.initState();
}
- componentWillUnmount(): void {
+ componentWillUnmount() {
this.clearState();
}
+
render() {
+ const gif = this.State?.[StateMessageGIF];
return (
- <div className="collectionFreeform-infoUI" style={{ display: this._hide ? 'none' : undefined }}>
- <div className="msg">{this.State?.[StateMessage]}</div>
- <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}>
- <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img>
+ <div className={'collectionFreeform-infoUI'}>
+ <p className="collectionFreeform-infoUI-msg">
+ {this.State?.[StateMessage]}
+ <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+ {this._expanded ? 'Less...' : 'More...'}
+ </button>
+ </p>
+ <div className={'collectionFreeform-' + (!this._expanded || !gif ? 'hidden' : 'infoUI-gif-container')}>
+ <img src={`/assets/${gif}`} alt="state message gif" />
</div>
- <div style={{ position: 'absolute', top: -10, left: -10 }}>
+ <div className="collectionFreeform-infoUI-close">
<IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
</div>
</div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 8628ca3c3..cc729decc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -8,6 +8,7 @@ import { DocumentManager } from '../../../util/DocumentManager';
import { LinkManager } from '../../../util/LinkManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { TopBar } from '../../topbar/TopBar';
import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
import { CollectionFreeFormView } from './CollectionFreeFormView';
import './CollectionFreeFormView.scss';
@@ -25,16 +26,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
constructor(props: any) {
super(props);
makeObservable(this);
- this.currState = this.setupStates();
+ this._currState = this.setupStates();
}
_originalbackground: string | undefined;
@observable _currState: infoState | undefined = undefined;
- get currState() { return this._currState!; } // prettier-ignore
+ get currState() { return this._currState; } // prettier-ignore
set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
componentWillUnmount(): void {
- this._props.Freeform.layoutDoc.backgroundColor = this._originalbackground;
+ this._props.Freeform.dataDoc.backgroundColor = this._originalbackground;
}
setCurrState = (state: infoState) => {
@@ -45,9 +46,9 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
};
setupStates = () => {
- this._originalbackground = StrCast(this._props.Freeform.layoutDoc.backgroundColor);
+ this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor);
// state entry functions
- const setBackground = (colour: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = colour);
+ const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour);
const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
// arc transition trigger conditions
const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
@@ -60,6 +61,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const docNewY = () => firstDoc()?.y;
const linkStart = () => DocumentLinksButton.StartLink;
+ const linkUnstart = () => !DocumentLinksButton.StartLink;
const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
@@ -74,81 +76,85 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const presentationMode = () => Doc.ActivePresentation?.presentation_status;
// set of states
- const start = InfoState('Click anywhere and begin typing to create your first text document.', {
- docCreated: [() => numDocs(), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return oneDoc;
- }],
- }, setBackground("blue")); // prettier-ignore
-
- const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', {
- // docCreated: [() => numDocs() > 1, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc1;
- }],
- }, setBackground("red")); // prettier-ignore
-
- const movedDoc1 = InfoState('Great moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc2;
- }],
- }, setBackground("yellow")); // prettier-ignore
-
- const movedDoc2 = InfoState('Slick moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc3;
- }],
- }, setBackground("pink")); // prettier-ignore
-
- const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', {
- docCreated: [() => numDocs() == 2, () => multipleDocs],
- docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
- docX = firstDoc()?.x;
- docY = firstDoc()?.y;
- return movedDoc1;
- }],
- }, setBackground("green")); // prettier-ignore
-
- const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', {
- linkStarted: [() => linkStart(), () => startedLink],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- }, setBackground("purple")); // prettier-ignore
-
- const startedLink = InfoState('Now click the highlighted link icon on your other document.', {
- linkCreated: [() => numDocLinks(), () => madeLink],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- }, setBackground("orange")); // prettier-ignore
-
- const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', {
- linkCreated: [() => !numDocLinks(), () => multipleDocs],
- linkViewed: [() => linkMenuOpen(), () => {
- alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
- return viewedLink;
- }],
- }, setBackground("blue")); // prettier-ignore
-
- const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', {
- linkDeleted: [() => !numDocLinks(), () => multipleDocs],
- docRemoved: [() => numDocs() < 2, () => oneDoc],
- docCreated: [() => numDocs() == 3, () => {
- trail = pin().length;
- return presentDocs;
- }],
- activePen: [() => activeTool() === InkTool.Pen, () => penMode],
- }, setBackground("black")); // prettier-ignore
+ const start = InfoState(
+ 'Click anywhere and begin typing to create your first text document.',
+ {
+ docCreated: [() => numDocs(), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return oneDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const oneDoc = InfoState(
+ 'Hello world! You can drag and drop to move your document around.',
+ {
+ // docCreated: [() => numDocs() > 1, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ return movedDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const movedDoc = InfoState(
+ 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")',
+ {
+ docCreated: [() => numDocs() == 2, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ },
+ 'dash-colon-menu.gif',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
+
+ const multipleDocs = InfoState(
+ 'Let\'s create a new link. Click the link icon on one of your documents.',
+ {
+ linkStarted: [() => linkStart(), () => startedLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ },
+ 'dash-create-link-board.gif'
+ ); // prettier-ignore
+
+ const startedLink = InfoState(
+ 'Now click the highlighted link icon on your other document.',
+ {
+ linkUnstart: [() => linkUnstart(), () => multipleDocs],
+ linkCreated: [() => numDocLinks(), () => madeLink],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ },
+ 'dash-create-link-board.gif'
+ ); // prettier-ignore
+
+ const madeLink = InfoState(
+ 'You made your first link! You can view your links by selecting the blue dot.',
+ {
+ linkCreated: [() => !numDocLinks(), () => multipleDocs],
+ linkViewed: [() => linkMenuOpen(), () => {
+ alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+ return viewedLink;
+ }],
+ },
+ 'dash-following-link.gif'
+ ); // prettier-ignore
+
+ const viewedLink = InfoState(
+ 'Great work. You are now ready to create your own hypermedia world. Click the ? icon in the top right corner to learn more.',
+ {
+ linkDeleted: [() => !numDocLinks(), () => multipleDocs],
+ docRemoved: [() => numDocs() < 2, () => oneDoc],
+ docCreated: [() => numDocs() == 3, () => {
+ trail = pin().length;
+ return presentDocs;
+ }],
+ activePen: [() => activeTool() === InkTool.Pen, () => penMode],
+ },
+ 'documentation.png',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
const presentDocs = InfoState(
'Another document! You could make a presentation. Click the pin icon in the top left corner.',
@@ -162,7 +168,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
],
docRemoved: [() => numDocs() < 3, () => viewedLink],
},
- setBackground('black'),
'/assets/dash-pin-with-view.gif'
);
@@ -242,14 +247,17 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
docCreated: [() => numDocs() == 4, () => completed],
});
- const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', {
- docRemoved: [() => numDocs() == 1, () => oneDoc],
- }, setBackground("white")); // prettier-ignore
+ const completed = InfoState(
+ 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.',
+ { docRemoved: [() => numDocs() == 1, () => oneDoc] },
+ 'documentation.png',
+ () => TopBar.Instance.FlipDocumentationIcon()
+ ); // prettier-ignore
return start;
};
render() {
- return <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />;
+ return !this.currState ? null : <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField';
import { listSpec } from '../../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { aggregateBounds } from '../../../../Utils';
-import * as React from 'react';
export interface ViewDefBounds {
type: string;
@@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
+ backgroundColor: StrCast(layoutDoc.backgroundColor),
pair: { layout: doc },
replica: val.replicas[i],
});
@@ -388,7 +388,7 @@ function normalizeResults(
minWidth: number,
extras: ViewDefBounds[]
): ViewDefResult[] {
- const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
const aggBounds = aggregateBounds(
extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 9e7d364ea..2c94446fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -273,33 +273,34 @@
margin: 15px;
padding: 10px;
+ .collectionFreeform-infoUI-close {
+ position: absolute;
+ top: -10;
+ left: -10;
+ }
- .msg {
+ .collectionFreeform-infoUI-msg {
+ position: relative;
+ max-width: 500;
+ margin: 10;
+ }
+ .collectionFreeform-infoUI-button {
+ border-radius: 50px;
+ font-size: 12px;
+ padding: 6;
position: relative;
- // display: block;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
}
- .gif-container {
+ .collectionFreeform-infoUI-gif-container {
position: relative;
margin-top: 5px;
- // display: block;
+ pointer-events: none;
- justify-content: center;
- align-items: center;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-
- .gif {
- background-color: transparent;
+ > img {
height: 300px;
}
}
+ .collectionFreeform-hidden {
+ display: none;
+ }
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5cc921d85..c5102070a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -29,14 +29,14 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { freeformScrollMode, SettingsManager } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
import { CtrlKey } from '../../GlobalKeyHandler';
import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
@@ -80,7 +80,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
- const field = this.layoutDoc[this.fieldKey];
+ const field = this.dataDoc[this.fieldKey];
const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
return !paintFunc
? ''
@@ -170,8 +170,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: aggregateBounds(
this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
- NumCast(this.layoutDoc._xPadding, 10),
- NumCast(this.layoutDoc._yPadding, 10)
+ NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
+ NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
);
}
@computed get nativeWidth() {
@@ -260,7 +260,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () =>
this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
@@ -331,8 +331,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// options.didMove = true;
// }
}
-
- if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
+ if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -353,6 +352,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
+ if (doc === this.Document) return res(this.DocumentView?.());
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -480,7 +480,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
de.moveDocument = this._props.moveDocument;
de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
@@ -582,7 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (doc && this.childDocList?.includes(doc))
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.BackgroundColor:
const cluster = NumCast(doc?.layout_cluster);
if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -607,7 +607,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
trySelectCluster = (addToSel: boolean) => {
- if (this._hitCluster !== -1) {
+ if (addToSel && this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
@@ -665,7 +665,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
x: B.x - inkWidth / 2,
y: B.y - inkWidth / 2,
_width: B.width + inkWidth,
- _height: B.height + inkWidth }, // prettier-ignore
+ _height: B.height + inkWidth,
+ stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
inkWidth
);
if (Doc.ActiveTool === InkTool.Write) {
@@ -778,7 +779,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
- @action
onPointerMove = (e: PointerEvent) => {
if (this.tryDragCluster(e, this._hitCluster)) {
e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
@@ -859,10 +859,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
- segment.length && segments.push(segment);
+ if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment);
}
// start a new segment from the intersection t value
- segment = tVals.length - 1 === index ? [inkSegment.split(t).right] : [];
+ if (tVals.length - 1 === index) {
+ const split = inkSegment.split(t).right;
+ if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment = [split];
+ else segment = [];
+ } else segment = [];
startSegmentT = docCurveTVal;
});
} else {
@@ -1162,23 +1166,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isContentActive = () => this._props.isContentActive();
- @undoBatch
+ /**
+ * Create a new text note of the same style as the one being typed into.
+ * If the text doc is be part of a larger templated doc, the new Doc will be a copy of the templated Doc
+ *
+ * @param fieldProps render props for the text doc being typed into
+ * @param below whether to place the new text Doc below or to the right of the one being typed into.
+ * @returns whether the new text doc was created and added successfully
+ */
+ createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
+ const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+ const newDoc = Doc.MakeCopy(textDoc, true);
+ newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+ newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+ newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+ FormattedTextBox.SetSelectOnLoad(newDoc);
+ FormattedTextBox.DontSelectInitialText = true;
+ return this.addDocument?.(newDoc);
+ }, 'copied text note');
+
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== 'Tab';
- const layout_fieldKey = StrCast(fieldProps.fieldKey);
- const newDoc = Doc.MakeCopy(fieldProps.Document, true);
- const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
- newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
- else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
- if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
- }
- newDoc[DocData].text = undefined;
- FormattedTextBox.SetSelectOnLoad(newDoc);
- return this.addDocument?.(newDoc);
+ return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
}
};
@computed get childPointerEvents() {
@@ -1200,7 +1210,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
return (
- <CollectionFreeFormDocumentViewWrapper
+ <CollectionFreeFormDocumentView
{...OmitKeys(entry, ['replica', 'pair']).omit}
key={childLayout[Id] + (entry.replica || '')}
Document={childLayout}
@@ -1271,7 +1281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lightboxDoc = doc;
return true;
}
- if (this.childDocList?.includes(doc)) {
+ if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
if (doc.hidden) doc.hidden = false;
return true;
}
@@ -1296,15 +1306,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const rotation = Cast(_rotation,'number',
!this.layoutDoc._rotation_jitter ? null
: NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
+ const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1363,7 +1374,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
renderCutoffProvider = computedFn(
function renderCutoffProvider(this: any, doc: Doc) {
- return !this._renderCutoffData.get(doc[Id] + '');
+ return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
}.bind(this)
);
@@ -1414,7 +1425,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection),
+ layout_unrendered: true,
+ presentation_transition: 500,
+ annotationOn: this.Document,
+ });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
if (addAsAnnotation) {
@@ -1427,8 +1443,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return anchor;
};
- @action closeInfo = () => (this.Document._hideInfo = true);
- infoUI = () => (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+ @action closeInfo = () => (Doc.IsInfoUIDisabled = true);
+ infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
componentDidMount() {
this._props.setContentViewBox?.(this);
@@ -1486,7 +1502,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!code.includes('dashDiv')) {
const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
if (script.compiled) script.run({ this: this.DocumentView?.() });
- } else code && !first && eval(code);
+ } else code && !first && eval?.(code);
},
{ fireImmediately: true }
);
@@ -1768,7 +1784,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!this._props.isAnnotationOverlay &&
!Doc.noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
- this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' });
this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
// Want to condense into a Smart Organize button
this._props.renderDepth && optionItems.push({ description: 'Style with AI', event: this.gptStyling, icon: 'paint-brush' });
@@ -1828,7 +1844,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
});
- SnappingManager.addSnapLines(horizLines, vertLines);
+ this.layoutDoc._freeform_snapLines && SnappingManager.addSnapLines(horizLines, vertLines);
};
incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
@@ -1836,7 +1852,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
- const loadIncrement = 5;
+ const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
}
@@ -1978,7 +1994,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
width: `${100 / (this.nativeDimScaling || 1)}%`,
height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {this.paintFunc ? null : this._lightboxDoc ? (
+ {this.paintFunc ? (
+ <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+ ) : this._lightboxDoc ? (
<div style={{ padding: 15, width: '100%', height: '100%' }}>
<DocumentView
{...this._props}
@@ -2087,8 +2105,8 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
for (let i = 0; i < children.length; i++) {
let eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ var cell = children[i][keys[j]]?.toString();
+ if (cell) cell = cell.toString().replace(/\,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a417d777a..6eca91e9d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -101,13 +101,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
@action
- onKeyPress = (e: KeyboardEvent) => {
+ onKeyDown = (e: KeyboardEvent) => {
//make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- 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', data_useCors: true }), OpenWhere.addRight));
+ cm.setDefaultItem('?', (str: string) =>
+ this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
+ );
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this._props.ungroup) {
@@ -178,7 +180,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
- this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
+ this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
e.stopPropagation();
}
};
@@ -309,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
PreviewCursor.Instance.Doc = doc;
- PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
+ PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
}
this.clearSelection();
}
@@ -494,11 +496,10 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
followLinkToggle: true,
_width: 200,
_height: 200,
- _layout_fitContentsToBox: true,
_layout_showSidebar: true,
title: 'overview',
});
- const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+ const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
@@ -635,8 +636,13 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
MarqueeRef: HTMLDivElement | null = null;
+ /**
+ * This is called for every drag movement when a document is dragged over this collection.
+ * If the document is dragged within 25 pixels of the edge of the collection and paused, this will
+ * auto scroll the collection so that it can be dragged farther (unless auto panning has been disabled)
+ */
@action
- onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ onDragMovePause = (e: CustomEvent<React.DragEvent>) => {
if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
(e as any).handlePan = true;
@@ -659,7 +665,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
<div
className="marqueeView"
ref={r => {
- r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ r?.addEventListener('dashDragMovePause', this.onDragMovePause as any);
this.MarqueeRef = r;
}}
style={{