aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx175
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx86
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss2
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx22
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx84
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx15
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx38
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts4
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx88
-rw-r--r--src/client/views/nodes/EquationBox.tsx5
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx31
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/LabelBox.tsx10
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx4
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx4
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx4
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx4
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx32
-rw-r--r--src/client/views/nodes/VideoBox.tsx3
-rw-r--r--src/client/views/nodes/WebBox.tsx6
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx27
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx59
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx13
26 files changed, 405 insertions, 321 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a0a64ab59..2800ea200 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { OmitKeys, numberRange } from '../../../Utils';
@@ -12,14 +12,16 @@ import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { DocComponent } from '../DocComponent';
-import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
-export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
+/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
+/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
+const freeFormPropsKeys = ['x', 'y', 'z', 'zIndex', 'rotation', 'opacity', 'backgroundColor', 'color', 'highlight', 'width', 'height', 'autoDim', 'transition'];
+interface freeFormProps {
x: number;
y: number;
z: number;
@@ -33,102 +35,17 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
opacity?: number;
highlight?: boolean;
transition?: string;
- RenderCutoffProvider: (doc: Doc) => boolean;
- CollectionFreeFormView: CollectionFreeFormView;
-}
-@observer
-export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> {
- constructor(props: CollectionFreeFormDocumentViewWrapperProps) {
- super(props);
- makeObservable(this);
- }
- @observable X = this.props.x;
- @observable Y = this.props.y;
- @observable Z = this.props.z;
- @observable ZIndex = this.props.zIndex;
- @observable Rotation = this.props.rotation;
- @observable Opacity = this.props.opacity;
- @observable BackgroundColor = this.props.backgroundColor;
- @observable Color = this.props.color;
- @observable Highlight = this.props.highlight;
- @observable Width = this.props.width;
- @observable Height = this.props.height;
- @observable AutoDim = this.props.autoDim;
- @observable Transition = this.props.transition;
- CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
- RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
-
- get Document() {
- return this._props.Document;
- }
- @computed get WrapperKeys() {
- return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
- .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
- }
-
- // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing.
- // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
- w_X = () => this.X; // prettier-ignore
- w_Y = () => this.Y; // prettier-ignore
- w_Z = () => this.Z; // prettier-ignore
- w_ZIndex = () => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
- w_Rotation = () => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
- w_Opacity = () => this.Opacity; // prettier-ignore
- w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
- w_Color = () => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
- w_Highlight = () => this.Highlight; // prettier-ignore
- w_Width = () => this.Width; // prettier-ignore
- w_Height = () => this.Height; // prettier-ignore
- w_AutoDim = () => this.AutoDim; // prettier-ignore
- w_Transition = () => this.Transition; // prettier-ignore
-
- PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
- PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
-
- componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
- super.componentDidUpdate(prevProps);
- this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
- }
- render() {
- const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
- return (
- <CollectionFreeFormDocumentView
- {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
- {...layoutProps}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- />
- );
- }
}
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- w_X: () => number;
- w_Y: () => number;
- w_Z: () => number;
- w_ZIndex?: () => number;
- w_Rotation?: () => number;
- w_Color: () => string;
- w_BackgroundColor: () => string;
- w_Opacity: () => number | undefined;
- w_Highlight: () => boolean | undefined;
- w_Transition: () => string | undefined;
- w_Width: () => number;
- w_Height: () => number;
- PanelWidth: () => number;
- PanelHeight: () => number;
RenderCutoffProvider: (doc: Doc) => boolean;
CollectionFreeFormView: CollectionFreeFormView;
}
-
@observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
- constructor(props: CollectionFreeFormDocumentViewProps) {
- super(props);
- makeObservable(this);
- }
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
get displayName() { // this makes mobx trace() statements more descriptive
return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
} // prettier-ignore
+ public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
public static animFields: { key: string; val?: number }[] = [
{ key: 'x' },
{ key: 'y' },
@@ -145,16 +62,53 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
- get CollectionFreeFormView() {
- return this._props.CollectionFreeFormView;
+ constructor(props: CollectionFreeFormDocumentViewProps & freeFormProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ get WrapperKeys() {
+ // each of these keys is set by the CollectionView and passed via props. however, if any one of these props changes
+ // (or any other prop), then it's as if they all change.
+ // Anything that accesses these props will then invalidate unncessarily.
+ // To avoid this, we copy these prop values into local observables. Now when 'props' changes, nothing invalidates.
+ // Instead, we copy each values into its observable which ohnly triggers invalidations if the new value is different
+ // than the old value, and then only things that access that observable will invalidate.
+ return freeFormPropsKeys
+ .map(key => ({upper:key[0].toUpperCase() + key.substring(1), lower:key})); // prettier-ignore
+ }
+ @observable X = this.props.x;
+ @observable Y = this.props.y;
+ @observable Z = this.props.z;
+ @observable ZIndex = this.props.zIndex;
+ @observable Rotation = this.props.rotation;
+ @observable Opacity = this.props.opacity;
+ @observable BackgroundColor = this.props.backgroundColor;
+ @observable Color = this.props.color;
+ @observable Highlight = this.props.highlight;
+ @observable Width = this.props.width;
+ @observable Height = this.props.height;
+ @observable AutoDim = this.props.autoDim;
+ @observable Transition = this.props.transition;
+
+ componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
+ super.componentDidUpdate(prevProps);
+ this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
}
+ CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
+ // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
+ DataTransition = () => this._props.transition; // prettier-ignore
+ RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
+ PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+ PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
if (doc === this.layoutDoc) {
switch (property) {
- case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
- case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
- case StyleProp.Color: return this._props.w_Color();
+ case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
+ case StyleProp.BackgroundColor: return this.BackgroundColor;
+ case StyleProp.Color: return this.Color;
} // prettier-ignore
}
return this._props.styleProvider?.(doc, props, property);
@@ -253,14 +207,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
nudge = (x: number, y: number) => {
const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
- this.Document.x = this._props.w_X() + locX;
- this.Document.y = this._props.w_Y() + locY;
+ this.Document.x = this.X + locX;
+ this.Document.y = this.Y + locY;
};
screenToLocalTransform = () =>
this._props
.ScreenToLocalTransform()
- .translate(-this._props.w_X(), -this._props.w_Y())
- .rotateDeg(-(this._props.w_Rotation?.() || 0));
+ .translate(-this.X, -this.Y)
+ .rotateDeg(-(this.Rotation || 0));
returnThis = () => this;
/// this indicates whether the doc view is activated because of its relationshop to a group
@@ -273,27 +227,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
- public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
render() {
TraceMobx();
- const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+
return (
<div
className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
style={{
- width: this._props.PanelWidth(),
- height: this._props.PanelHeight(),
- transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
- transition: this._props.w_Transition?.() || StrCast(this.Document.dataTransition),
- zIndex: this._props.w_ZIndex?.(),
- display: this._props.w_Width?.() ? undefined : 'none',
+ width: this.PanelWidth(),
+ height: this.PanelHeight(),
+ transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
+ transition: this.Transition || StrCast(this.Document.dataTransition),
+ zIndex: this.ZIndex,
+ display: this.Width ? undefined : 'none',
}}>
- {this._props.RenderCutoffProvider(this.Document) ? (
- <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
+ {this.RenderCutoffProvider(this.Document) ? (
+ <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView
- {...passOnProps}
- DataTransition={this._props.w_Transition}
+ {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
+ DataTransition={this.DataTransition}
CollectionFreeFormDocumentView={this.returnThis}
styleProvider={this.styleProvider}
ScreenToLocalTransform={this.screenToLocalTransform}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 116dc48a6..ef8c045cc 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -6,7 +6,7 @@ import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents }
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
@@ -135,7 +135,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this,
e,
e => {
- const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], 'move');
+ const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
this.clearDoc(which);
return addDocument(doc);
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index a3132dc6e..6b5738790 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -29,6 +29,10 @@
}
}
+ .liveSchema-checkBox {
+ margin-bottom: -35px;
+ }
+
.dataviz-sidebar {
position: absolute;
right: 0;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index e453bcee0..66a08f13e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -11,7 +11,7 @@ import { listSpec } from '../../../../fields/Schema';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
-import { Docs } from '../../../documents/Documents';
+import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
@@ -27,6 +27,7 @@ import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
+import { Checkbox } from '@mui/material';
export enum DataVizView {
TABLE = 'table',
@@ -85,6 +86,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
// all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
static dataset = new ObservableMap<string, { [key: string]: string }[]>();
+ // when a dataset comes from schema view, this stores the original dataset to refer back to
+ // href : dataset
+ static datasetSchemaOG = new ObservableMap<string, { [key: string]: string }[]>();
+
private _vizRenderer: LineChart | Histogram | PieChart | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -103,10 +108,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
return Cast(this.dataDoc[this.fieldKey], CsvField);
}
@computed.struct get axes() {
- return StrListCast(this.layoutDoc.dataViz_axes);
+ return StrListCast(this.layoutDoc._dataViz_axes);
}
-
- selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+ selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+ @computed.struct get titleCol() {
+ return StrCast(this.layoutDoc._dataViz_titleCol);
+ }
+ selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
@@ -258,6 +266,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
this._disposers.datavis = reaction(
() => {
+ if (this.layoutDoc.dataViz_schemaLive==undefined) this.layoutDoc.dataViz_schemaLive = true;
const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
if (!keys) return;
@@ -274,10 +283,43 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
});
current.push(row);
});
+ if (!this.layoutDoc._dataViz_schemaOG){ // makes a copy of the original table for the "live" toggle
+ let csvRows = [];
+ csvRows.push(keys.join(','));
+ for (let i = 0; i < children.length-1; 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, '');
+ eachRow.push(cell);
+ }
+ csvRows.push(eachRow);
+ }
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
+ const file = new File([blob], 'schemaTable for static dataviz', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ DocUtils.uploadFileToDoc(file, {}, loading);
+ this.layoutDoc._dataViz_schemaOG = loading;
+ }
+ const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc
+ const ogHref = CsvCast(ogDoc[this.fieldKey])? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
+ const href = CsvCast(this.Document[this.fieldKey]).url.href
+ if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){ // sets original dataset to the var
+ const lastRow = current.pop();
+ DataVizBox.datasetSchemaOG.set(href, current);
+ current.push(lastRow!);
+ fetch('/csvData?uri=' + ogHref)
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ }
return current;
},
current => {
- current && DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current);
+ if (current) {
+ const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
+ else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
+ }
},
{ fireImmediately: true }
);
@@ -299,6 +341,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
layoutDoc: this.layoutDoc,
records: this.records,
axes: this.axes,
+ titleCol: this.titleCol,
//width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
@@ -306,7 +349,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
@@ -354,6 +397,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
};
+ @action
+ changeLiveSchemaCheckbox = () => {
+ this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
+ }
+
render() {
const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
@@ -379,27 +427,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
<Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
</div>
- {/* <CollectionFreeFormView
- ref={this._ffref}
- {...this._props}
- setContentView={emptyFunction}
- renderDepth={this._props.renderDepth - 1}
- fieldKey={this.annotationKey}
- styleProvider={this._props.styleProvider}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={false}
- PanelWidth={this._props.PanelWidth}
- PanelHeight={this._props.PanelHeight}
- select={emptyFunction}
- isAnyChildContentActive={returnFalse}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}>
- {this.renderVizView}
- </CollectionFreeFormView> */}
+ {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
+ <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
+ ) : null}
{this.renderVizView}
+
<div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} onPointerDown={this.onPointerDown}>
<SidebarAnnos
ref={this._sidebarRef}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 116a45623..41ce637ac 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -19,8 +19,6 @@
margin-bottom: -20px;
}
.asHistogram-checkBox {
- // display: flex;
- // flex-direction: row;
align-items: left;
align-self: left;
align-content: left;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index a7f292104..6672603f3 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -20,6 +20,7 @@ export interface HistogramProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -63,17 +64,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
var ax0 = this._props.axes[0];
- if (/\d/.test(this._props.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
this.numericalXData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
var ax0 = this._props.axes[0];
var ax1 = this._props.axes[1];
- if (/\d/.test(this._props.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
- if (/\d/.test(this._props.records[0][ax1])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax1])) {
this.numericalYData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
@@ -89,9 +90,6 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
- // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -454,6 +452,18 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
: ''
);
selected = selected.substring(0, selected.length - 2) + ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
}
var selectedBarColor;
var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index bea1b8222..e093ec648 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -28,6 +28,7 @@ export interface LineChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -46,7 +47,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- @observable _currSelected: SelectedDataPoint | undefined = undefined;
+ @observable _currSelected: any | undefined = undefined;
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
constructor(props: any) {
super(props);
@@ -235,21 +236,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
}
- // TODO: nda - can use d3.create() to create html element instead of appending
drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
// clearing tooltip and the current chart
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
- const { xMin, xMax, yMin, yMax } = rangeVals;
+ var { xMin, xMax, yMin, yMax } = rangeVals;
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
return;
}
- // creating the x and y scales
- const xScale = scaleCreatorNumerical(xMin, xMax, 0, width);
- const yScale = scaleCreatorNumerical(0, yMax, height, 0);
-
// adding svg
const margin = this._props.margin;
const svg = (this._lineChartSvg = d3
@@ -261,24 +257,71 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`));
+ var validSecondData;
+ if (this._props.axes.length>2){ // for when there are 2 lines on the chart
+ var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+ validSecondData = next.filter(d => {
+ if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+ return true;
+ });
+ var secondDataRange = minMaxRange([validSecondData]);
+ if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
+ if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
+ if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
+ if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+ }
+
+ // creating the x and y scales
+ const xScale = scaleCreatorNumerical(xMin!, xMax!, 0, width);
+ const yScale = scaleCreatorNumerical(0, yMax!, height, 0);
+ const lineGen = createLineGenerator(xScale, yScale);
+
// create x and y grids
xGrid(svg.append('g'), height, xScale);
yGrid(svg.append('g'), width, yScale);
xAxisCreator(svg.append('g'), height, xScale);
yAxisCreator(svg.append('g'), width, yScale);
+ if (validSecondData) {
+ drawLine(svg.append('path'), validSecondData, lineGen, true);
+ this.drawDataPoints(validSecondData, 0, xScale, yScale);
+ svg.append('path').attr("stroke", "red");
+
+ // legend
+ var color = d3.scaleOrdinal()
+ .range(["black", "blue"])
+ .domain([this._props.axes[1], this._props.axes[2]])
+ svg.selectAll("mydots")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("circle")
+ .attr("cx", 5)
+ .attr("cy", function(d,i){ return -30 + i*15})
+ .attr("r", 7)
+ .style("fill", function(d){ return color(d)})
+ svg.selectAll("mylabels")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("text")
+ .attr("x", 25)
+ .attr("y", function(d,i){ return -30 + i*15})
+ .style("fill", function(d){ return color(d)})
+ .text(function(d){ return d})
+ .attr("text-anchor", "left")
+ .style("alignment-baseline", "middle")
+ }
+
// get valid data points
const data = dataSet[0];
- const lineGen = createLineGenerator(xScale, yScale);
var validData = data.filter(d => {
- var valid = true;
Object.keys(data[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
+ if (!d[key] || Number.isNaN(d[key])) return false;
});
- return valid;
+ return true;
});
+
// draw the plot line
- drawLine(svg.append('path'), validData, lineGen);
+ drawLine(svg.append('path'), validData, lineGen, false);
// draw the datapoint circle
this.drawDataPoints(validData, 0, xScale, yScale);
@@ -291,7 +334,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const xPos = d3.pointer(e)[0];
const x0 = Math.min(data.length - 1, bisect(data, xScale.invert(xPos - 5))); // shift x by -5 so that you can reach points on the left-side axis
const d0 = data[x0];
- if (!d0) return;
+ if (d0) this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -327,7 +370,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
svg.append('text')
.attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
- .attr('y', -20)
+ .attr('y', -30)
.attr('height', 20)
.attr('width', 20)
.style('text-anchor', 'middle')
@@ -356,6 +399,17 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
+ var selectedTitle = "";
+ if (this._currSelected && this._props.titleCol){
+ selectedTitle+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ var mapThisEntry = false;
+ if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
+ else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
+ if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
+ })
+ selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+ }
if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
@@ -375,9 +429,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
{selectedPt != 'none' ? (
<div className={'selected-data'}>
{`Selected: ${selectedPt}`}
+ {`${selectedTitle}`}
<Button
onClick={e => {
- console.log('test plzz');
this._props.vizBox.sidebarBtnDown;
this._props.vizBox.sidebarAddDocument;
}}></Button>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index a922a200b..fc23f47de 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -19,6 +19,7 @@ export interface PieChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -339,14 +340,26 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
var selected: string;
var curSelectedSliceName = '';
if (this._currSelected) {
+ selected = '{ ';
const sliceTitle = this._currSelected[this._props.axes[0]];
curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- selected = '{ ';
Object.keys(this._currSelected).map(key => {
key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
});
selected = selected.substring(0, selected.length - 2);
selected += ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
} else selected = 'none';
var selectedSliceColor;
var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index ed44d9269..1b239b5e5 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -18,7 +18,9 @@ interface TableBoxProps {
layoutDoc: Doc;
records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
+ selectTitleCol: (titleCol: string) => void;
axes: string[];
+ titleCol: string;
width: number;
height: number;
margin: {
@@ -83,14 +85,12 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return this._props.docView?.()?.screenToViewTransform().Scale || 1;
}
@computed get rowHeight() {
- console.log('scale = ' + this.viewScale + ' table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
}
@computed get startID() {
return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
}
@computed get endID() {
- console.log('start = ' + this.startID + ' container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
}
@action handleScroll = () => {
@@ -155,11 +155,18 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
},
emptyFunction,
action(e => {
- const newAxes = this._props.axes;
- if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
- else if (newAxes.length > 1) newAxes[1] = col;
- else newAxes.push(col);
- this._props.selectAxes(newAxes);
+ if (e.shiftKey){
+ if (this._props.titleCol == col) this._props.titleCol = "";
+ else this._props.titleCol = col;
+ this._props.selectTitleCol(this._props.titleCol);
+ }
+ else{
+ const newAxes = this._props.axes;
+ if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
+ else if (newAxes.length > 2) newAxes[newAxes.length-1] = col;
+ else newAxes.push(col);
+ this._props.selectAxes(newAxes);
+ }
})
);
};
@@ -213,8 +220,15 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
<th
key={this.columns.indexOf(col)}
style={{
- color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this._props.axes.lastElement() === col ? 'darkred' : undefined,
- background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this._props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+ color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? 'darkred'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? 'darkblue' : undefined,
+ background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? '#Fbdbdb'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? '#c6ebf7' : undefined,
+ // blue: #ADD8E6
+ // green: #E3fbdb
+ // red: #Fbdbdb
fontWeight: 'bolder',
border: '3px solid black',
}}
@@ -236,7 +250,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- const colSelected = this._props.axes.length > 1 ? this._props.axes[0] == col || this._props.axes[1] == col : this._props.axes.length > 0 ? this._props.axes[0] == col : false;
+ var colSelected = false;
+ if (this._props.axes.length>2) colSelected = this._props.axes[0]==col || this._props.axes[1]==col || this._props.axes[2]==col;
+ else if (this._props.axes.length>1) colSelected = this._props.axes[0]==col || this._props.axes[1]==col;
+ else if (this._props.axes.length>0) colSelected = this._props.axes[0]==col;
+ if (this._props.titleCol==col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 10bfb0c64..336935d23 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -61,6 +61,6 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
);
};
-export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>) => {
- p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen);
+export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
+ p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
};
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c4dab16fb..5421c1b50 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -180,7 +180,6 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
- overflow: hidden;
color: $black;
transform-origin: top left;
top: 0;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 73c13b5dd..29266bd8e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,10 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { Dropdown, DropdownType, Type } from 'browndash-components';
import { Howl } from 'howler';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
+import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -48,6 +47,7 @@ import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
+import { FieldsDropdown } from '../FieldsDropdown';
interface Window {
MediaRecorder: MediaRecorder;
}
@@ -413,7 +413,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
}
};
@@ -518,7 +518,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
+ if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
//!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
@@ -702,7 +702,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
- setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
+ setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height));
setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
@@ -784,28 +784,25 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
- fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
- const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields));
+ fieldsDropdown = (placeholder: string) => {
return (
- <div style={{ width: dropdownWidth }}>
- <div
- ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
- onPointerDown={action(e => (this._changingTitleField = true))}
- style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
- <Dropdown
- activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
- selectedVal={placeholder}
- setSelectedVal={onChange}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
- type={Type.TERT}
- closeOnSelect={true}
- dropdownType={DropdownType.SELECT}
- items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))}
- width={100}
- fillWidth
- />
- </div>
+ <div
+ ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))}
+ onPointerDown={action(e => (this._changingTitleField = true))}
+ style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+ <FieldsDropdown
+ Document={this.Document}
+ placeholder={placeholder}
+ selectFunc={action((field: string | number) => {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
+ Doc.UserDoc().layout_showTitle = field;
+ }
+ this._changingTitleField = false;
+ })}
+ menuClose={action(() => (this._changingTitleField = false))}
+ />
</div>
);
};
@@ -839,22 +836,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
- {!dropdownWidth
- ? null
- : this.fieldsDropdown(
- [StrCast(this.layoutDoc.layout_showTitle)],
- dropdownWidth,
- StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
- action((field: string | number) => {
- if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = field;
- } else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = field;
- }
- this._changingTitleField = false;
- }),
- action(() => (this._changingTitleField = false))
- )}
+ {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>}
<div
style={{
width: `calc(100% - ${dropdownWidth}px)`,
@@ -864,10 +846,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}}>
<EditableView
ref={this._titleRef}
- contents={showTitle
- .split(';')
- .map(field => targetDoc[field.trim()]?.toString())
- .join(' \\ ')}
+ contents={
+ showTitle
+ .split(';')
+ .map(field => Field.toString(targetDoc[field.trim()] as Field))
+ .join(' \\ ') || '-unset-'
+ }
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
@@ -1500,8 +1484,8 @@ ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
let created = false;
const matchedDocs = matchedTags
.filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc))
- .map(tagDoc => {
- let embedding = collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc));
+ .reduce((aset, tagDoc) => {
+ let embedding = Array.from(aset).find(doc => Doc.AreProtosEqual(tagDoc, doc)) ?? collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc));
if (!embedding) {
embedding = Doc.MakeEmbedding(tagDoc);
embedding.x = wid;
@@ -1510,9 +1494,11 @@ ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
wid += NumCast(tagDoc._width);
created = true;
}
- return embedding;
- });
+ Doc.SetContainer(embedding, collection);
+ aset.add(embedding);
+ return aset;
+ }, new Set<Doc>());
- created && (collection[DocData].data = new List<Doc>(matchedDocs));
+ created && (collection[DocData].data = new List<Doc>(Array.from(matchedDocs)));
return true;
});
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 2e03a766a..50d4c7c78 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -11,6 +11,7 @@ import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import EquationEditor from './formattedText/EquationEditor';
+import { DivHeight, DivWidth } from '../../../Utils';
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -57,8 +58,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
keyPressed = (e: KeyboardEvent) => {
- const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', ''));
- const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', ''));
+ const _height = DivHeight(this._ref.current!.element.current);
+ const _width = DivWidth(this._ref.current!.element.current);
if (e.key === 'Enter') {
const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', {
title: '# math',
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 3577cc8d9..f02ad7300 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,13 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -81,9 +81,13 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
}
};
- // Determining UI Specs
+ /**
+ * this chooses the appropriate title for the label
+ * if the Document is a template, then we use the title of the data doc that it renders
+ * otherwise, we use the Document's title itself.
+ */
@computed get label() {
- return StrCast(this.dataDoc.icon_label, StrCast(this.Document.title));
+ return StrCast(this.Document.isTemplateDoc ? this.dataDoc.title : this.Document.title);
}
Icon = (color: string, iconFalse?: boolean) => {
let icon;
@@ -308,13 +312,16 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
);
}
+ @observable _hackToRecompute = 0; // bcz: ugh ... <Toggle>'s toggleStatus initializes but doesn't track its value after a click. so a click that does nothing to the toggle state will toggle the button anyway. this forces the Toggle to re-read the ToggleStatus value.
+
@computed get toggleButton() {
// Determine the type of toggle button
const buttonText = StrCast(this.dataDoc.buttonText);
const tooltip = StrCast(this.Document.toolTip);
const script = ScriptCast(this.Document.onClick);
- const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
+ const double = ScriptCast(this.Document.onDoubleClick);
+ const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
@@ -330,7 +337,19 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
//background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
- onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })}
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnTrue,
+ emptyFunction,
+ action((e, doubleTap) => {
+ (!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ this._hackToRecompute = this._hackToRecompute + 1;
+ })
+ )
+ }
/>
);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 923aead64..251235b93 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -141,6 +141,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const targetField = Doc.LayoutFieldKey(layoutDoc);
const targetDoc = layoutDoc[DocData];
if (targetDoc[targetField] instanceof ImageField) {
+ added = true;
this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey);
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index fd3074a88..be20b5934 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -17,12 +17,8 @@ import './LabelBox.scss';
import { PinProps, PresBox } from './trails';
import { Docs } from '../../documents/Documents';
-export interface LabelBoxProps extends FieldViewProps {
- label?: string;
-}
-
@observer
-export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
+export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LabelBox, fieldKey);
}
@@ -32,7 +28,7 @@ export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
- constructor(props: LabelBoxProps) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
@@ -45,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
}
@computed get Title() {
- return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : Field.toString(this.dataDoc[this.fieldKey] as Field);
+ return Field.toString(this.dataDoc[this.fieldKey] as Field);
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 00e1f04c5..0a4325d8c 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -5,7 +5,7 @@ import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
import { SelectionManager } from '../../util/SelectionManager';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -54,7 +54,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
if (separation > 100) {
const dragData = new DragManager.DocumentDragData([this.Document]);
- dragData.dropAction = 'embed';
+ dragData.dropAction = dropActionType.embed;
dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
return true;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index c185c66fc..927e6fad4 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -379,7 +379,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -592,7 +592,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+ text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 8a5bd7ce6..3eb051dbf 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -232,7 +232,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -466,7 +466,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
+ text: (StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location') as any,
config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index f6d94ce05..e38a42b29 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -11,7 +11,7 @@ import { Upload } from '../../../../server/SharedMediaTypes';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { Presentation } from '../../../util/TrackMovements';
import { undoBatch } from '../../../util/UndoManager';
@@ -251,7 +251,7 @@ ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: b
ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
if (DocCast(value.doc)) {
- DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], 'embed'), value.e.clientX, value.e.clientY);
+ DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], dropActionType.embed), value.e.clientX, value.e.clientY);
value.e.preventDefault();
return true;
}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 89650889d..d9d0dbe3e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -130,7 +130,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
})
);
- observer.observe(document.getElementsByClassName('scriptingBox')[0]);
+ observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
}
@action
@@ -811,22 +811,20 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
render() {
TraceMobx();
return (
- <div className={`scriptingBox`} onContextMenu={this.specificContextMenu} onPointerUp={!this._function ? this.suggestionPos : undefined}>
- <div className="scriptingBox-outerDiv" onWheel={e => this._props.isSelected() && e.stopPropagation()}>
- {this._paramSuggestion ? (
- <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: 'inline' }}>
- {' '}
- {this._scriptSuggestedParams}{' '}
- </div>
- ) : null}
- {!this._applied && !this._function ? this.renderScriptingInputs : null}
- {this._applied && !this._function ? this.renderParamsInputs() : null}
- {!this._applied && this._function ? this.renderFunctionInputs() : null}
-
- {!this._applied && !this._function ? this.renderScriptingTools() : null}
- {this._applied && !this._function ? this.renderTools('Run', () => this.onRun()) : null}
- {!this._applied && this._function ? this.renderTools('Create Function', () => this.onCreate()) : null}
- </div>
+ <div className="scriptingBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerUp={!this._function ? this.suggestionPos : undefined} onWheel={e => this._props.isSelected() && e.stopPropagation()}>
+ {this._paramSuggestion ? (
+ <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: 'inline' }}>
+ {' '}
+ {this._scriptSuggestedParams}{' '}
+ </div>
+ ) : null}
+ {!this._applied && !this._function ? this.renderScriptingInputs : null}
+ {this._applied && !this._function ? this.renderParamsInputs() : null}
+ {!this._applied && this._function ? this.renderFunctionInputs() : null}
+
+ {!this._applied && !this._function ? this.renderScriptingTools() : null}
+ {this._applied && !this._function ? this.renderTools('Run', () => this.onRun()) : null}
+ {!this._applied && this._function ? this.renderTools('Create Function', () => this.onCreate()) : null}
</div>
);
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 40647feff..b2ae7201c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -31,6 +31,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
+import { dropActionType } from '../../util/DragManager';
/**
* VideoBox
@@ -335,7 +336,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
- setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2c5398e40..c9340edc0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -83,7 +83,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return this.webField?.toString() || '';
}
@computed get _urlHash() {
- return ""+ (stringHash(this._url)??'');
+ return '' + (stringHash(this._url) ?? '');
}
@computed get scrollHeight() {
return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
@@ -782,7 +782,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
className="webBox-htmlSpan"
ref={action((r: any) => {
if (r) {
- this._scrollHeight = Number(getComputedStyle(r).height.replace('px', ''));
+ this._scrollHeight = DivHeight(r);
this.lighttext = Array.from(r.children).some((c: any) => c instanceof HTMLElement && lightOrDark(getComputedStyle(c).color) !== Colors.WHITE);
}
})}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index ec0b76aa8..5c4d850ad 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -4,24 +4,23 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import Select from 'react-select';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Cast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
+import { FilterPanel } from '../../FilterPanel';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { FilterPanel } from '../../FilterPanel';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -29,7 +28,7 @@ export class DashFieldView {
node: any;
tbox: FormattedTextBox;
- unclickable = () => !this.tbox._props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this.node = node;
this.tbox = tbox;
@@ -108,12 +107,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
super(props);
makeObservable(this);
this._fieldKey = this._props.fieldKey;
- this._textBoxDoc = this._props.tbox.Document;
+ this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
- this._dashDoc = this._props.tbox.Document;
+ this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
}
}
@@ -202,7 +201,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
style={{
width: this._props.width,
height: this._props.height,
- pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+ pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
{this._props.hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
@@ -210,11 +209,13 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
</span>
)}
{this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
- <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
- {this.values.map(val => (
- <option value={val.value}>{val.label}</option>
- ))}
- </select>
+ {!this.values.length ? null : (
+ <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+ {this.values.map(val => (
+ <option value={val.value}>{val.label}</option>
+ ))}
+ </select>
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f2c4c6c8f..1ff7274f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -23,14 +23,14 @@ import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -67,7 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
-import Select from 'react-select';
// import * as applyDevTools from 'prosemirror-dev-tools';
@observer
export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -100,7 +99,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _ignoreScroll = false;
- private _lastText = '';
private _hadDownFocus = false;
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
@@ -306,7 +304,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e.preventDefault();
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -374,9 +372,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
- dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
- dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
+ dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined;
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
+ this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
+ dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
unchanged = false;
}
} else {
@@ -623,7 +622,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
docId: draggedDoc[Id],
float: 'unset',
});
- if (!['embed', 'copy'].includes((dropAction ?? '') as any)) {
+ if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
added = dragData.removeDocument?.(draggedDoc) ? true : false;
} else {
added = true;
@@ -758,7 +757,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
- const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
+ const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
if (preview) this._showSidebar = true;
else {
this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
@@ -939,9 +938,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
description: 'Make Default Layout',
event: () => {
if (!this.layoutDoc.isTemplateDoc) {
- const title = StrCast(this.Document.title);
- this.Document.title = 'text';
- MakeTemplate(this.Document, true, title);
+ MakeTemplate(this.Document);
}
Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
@@ -1458,39 +1455,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
- if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
- const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+ const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+ if (selectOnLoad) {
FormattedTextBox.SelectOnLoad = undefined;
+ FormattedTextBox.SelectOnLoadChar = '';
+ }
+ if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
this._props.select(false);
if (selLoadChar) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
- const tr = this._editorView.state.tr
- .setStoredMarks(storedMarks)
- .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
- .setStoredMarks(storedMarks);
+ const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
+ const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1);
+ const tr = tr2.setStoredMarks(storedMarks);
+
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
}
}
- selectOnLoad && this._editorView!.focus();
+ if (selectOnLoad) {
+ FormattedTextBox.DontSelectInitialText = false;
+ this._editorView!.focus();
+ }
if (this._props.isContentActive()) this.prepareForTyping();
- if (this._editorView) {
- const tr = this._editorView.state.tr;
- const { from, to } = tr.selection;
- // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
- if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
-
- if (FormattedTextBox.PasteOnLoad) {
- const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
- FormattedTextBox.PasteOnLoad = undefined;
- pdfAnchorId && this.addPdfReference(pdfAnchorId);
- }
+ if (this._editorView && FormattedTextBox.PasteOnLoad) {
+ const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+ FormattedTextBox.PasteOnLoad = undefined;
+ pdfAnchorId && this.addPdfReference(pdfAnchorId);
}
- FormattedTextBox.DontSelectInitialText = false;
}
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -2019,7 +2014,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
render() {
TraceMobx();
- const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = this._props.NativeDimScaling?.() || 1; // * NumCast(this.layoutDoc._freeform_scale, 1);
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b8f6575dd..0d4f9ec78 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,6 +36,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { dropActionType } from '../../../util/DragManager';
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -1074,7 +1075,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
+ removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
panelHeight = () => this._props.PanelHeight() - 40;
/**
@@ -2222,10 +2223,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// prettier-ignore
switch (layout) {
case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
- case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
- case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
- case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
- case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y })
+ case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
}
};
@@ -2607,7 +2608,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childIgnoreNativeSize={true}
moveDocument={returnFalse}
ignoreUnrendered={true}
- childDragAction="move"
+ childDragAction={dropActionType.move}
setContentViewBox={emptyFunction}
//childLayoutFitWidth={returnTrue}
childOpacity={returnOne}