aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-10-19 00:58:50 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-10-19 00:58:50 -0400
commit1efba5a6a80cba09633426fc8cb42be4bf9b2e74 (patch)
treeabd78b4818115b300fb934e343f41417ac13e19f /src
parent612f3d05927113c7b010861c17765fcead4752e5 (diff)
parentabf40af6dd617de6486a97e8b5f276db232119ed (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/views/FilterPanel.tsx4
-rw-r--r--src/client/views/StyleProvider.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx56
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx9
-rw-r--r--src/client/views/global/globalCssVariables.scss3
-rw-r--r--src/client/views/global/globalCssVariables.scss.d.ts1
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx79
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss50
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.scss22
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx215
-rw-r--r--src/client/views/nodes/DocumentView.tsx48
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx4
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx10
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.tsx20
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1
-rw-r--r--src/client/views/pdf/Annotation.tsx10
-rw-r--r--src/client/views/pdf/PDFViewer.tsx3
-rw-r--r--src/fields/Doc.ts34
24 files changed, 284 insertions, 305 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b1632dadf..11b5f9f08 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { action, reaction, runInAction } from 'mobx';
import { basename } from 'path';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt, updateCachedAcls } from '../../fields/Doc';
import { Initializing } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { HtmlField } from '../../fields/HtmlField';
@@ -1302,7 +1302,7 @@ export namespace DocUtils {
if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
- const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== '-linkedTo' ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
+ const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
const satisfiesMatchFacets = !matches.length
? true
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 69ceb0f65..0df88f970 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -5,7 +5,7 @@ import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
import Select from 'react-select';
-import { Doc, DocListCast, Field, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
import { RichTextField } from '../../fields/RichTextField';
import { DocumentOptions, FInfo } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
@@ -57,7 +57,7 @@ export class FilterPanel extends React.Component<filterProps> {
@computed get _allFacets() {
// trace();
- const noviceReqFields = ['author', 'tags', 'text', 'type', '-linkedTo'];
+ const noviceReqFields = ['author', 'tags', 'text', 'type', LinkedTo];
const noviceLayoutFields: string[] = []; //["_layout_curPage"];
const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index f2e1ee0a2..6ee96de5b 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { Dropdown, DropdownType, IconButton, IListItemProps, ListBox, ListItem, Popup, Shadows, Size, Type } from 'browndash-components';
-import { action, runInAction } from 'mobx';
+import { action, runInAction, untracked } from 'mobx';
import { extname } from 'path';
import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
@@ -277,7 +277,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
};
const filter = () => {
- const dashView = DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
+ const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
const showFilterIcon =
StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
? 'green' // #18c718bd' //'hasFilter'
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 158f9d8ee..011bc1de5 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -67,7 +67,7 @@ export function CollectionSubView<X>(moreProps?: X) {
return this.layoutDoc[this.props.fieldKey];
}
- get childLayoutPairs(): { layout: Doc; data: Doc }[] {
+ @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
const { Document, DataDoc } = this.props;
const validPairs = this.childDocs
.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc))
@@ -77,7 +77,7 @@ export function CollectionSubView<X>(moreProps?: X) {
});
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
- get childDocList() {
+ @computed get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 15b6e1d37..c1c01eacb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -6,7 +6,6 @@ import { RefField } from '../../../../fields/RefField';
import { listSpec } from '../../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { aggregateBounds } from '../../../../Utils';
-import { ColorScheme } from '../../../util/SettingsManager';
import React = require('react');
export interface ViewDefBounds {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 676e96714..bfc61f601 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { DateField } from '../../../../fields/DateField';
@@ -59,7 +59,7 @@ export type collectionFreeformViewProps = {
originTopLeft?: boolean;
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
- childPointerEvents?: string;
+ childPointerEvents?: () => string | undefined;
viewField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
@@ -109,6 +109,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private get panYFieldKey() {
return (this.props.viewField ?? '') + '_freeform_panY';
}
+ private get autoResetFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_autoReset';
+ }
private get borderWidth() {
return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
}
@@ -1082,7 +1085,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case freeformScrollMode.Pan:
// if ctrl is selected then zoom
if (!e.ctrlKey && this.props.isContentActive(true)) {
- this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 : -Math.max(-1, Math.min(1, e.deltaY)) });
+ this.scrollPan({ deltaX: -e.deltaX * this.getTransform().Scale, deltaY: e.shiftKey ? 0 : -e.deltaY * this.getTransform().Scale });
break;
}
default:
@@ -1292,7 +1295,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
const pointerEvents = DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
+ : this.props.childPointerEvents?.() ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
return pointerEvents;
}
pointerEvents = () => this._pointerEvents;
@@ -1508,9 +1511,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@observable _numLoaded = 1;
+ _lastPoolSize = 0;
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
const array = Array.from(newPool.entries());
+ let somethingChanged = array.length !== this._lastPoolSize;
+ this._lastPoolSize = array.length;
runInAction(() => {
for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
@@ -1528,12 +1534,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
newPos.transition !== lastPos.transition
) {
this._layoutPoolData.set(entry[0], newPos);
+ somethingChanged = true;
}
if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
+ somethingChanged = true;
}
}
});
+ if (!somethingChanged) return undefined;
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
@@ -1609,10 +1618,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.layoutComputation = reaction(
() => this.doLayoutComputation,
- elements => (this._layoutElements = elements || []),
+ elements => {
+ if (elements !== undefined) this._layoutElements = elements || [];
+ },
{ fireImmediately: true, name: 'doLayout' }
);
+ this._disposers.active = reaction(
+ () => this.isContentActive(),
+ active => this.rootDoc[this.autoResetFieldKey] && !active && this.resetView()
+ );
+
this._disposers.fitContent = reaction(
() => this.rootDoc.fitContentOnce,
fitContentOnce => {
@@ -1768,9 +1784,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
///
@undoBatch
resetView = () => {
- if (!this.props.Document._isGroup) {
- this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
- this.props.Document[this.scaleFieldKey] = 1;
+ this.rootDoc[this.panXFieldKey] = NumCast(this.rootDoc[this.panXFieldKey + '_reset']);
+ this.props.Document[this.panYFieldKey] = NumCast(this.rootDoc[this.panYFieldKey + '_reset']);
+ this.rootDoc[this.scaleFieldKey] = NumCast(this.rootDoc[this.scaleFieldKey + '_reset'], 1);
+ };
+ ///
+ /// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS
+ /// the view is a group, in which case this does nothing (since Groups calculate their own scale and center)
+ ///
+ @undoBatch
+ toggleResetView = () => {
+ this.rootDoc[this.autoResetFieldKey] = !this.rootDoc[this.autoResetFieldKey];
+ if (this.rootDoc[this.autoResetFieldKey]) {
+ this.rootDoc[this.panXFieldKey + '_reset'] = this.rootDoc[this.panXFieldKey];
+ this.rootDoc[this.panYFieldKey + '_reset'] = this.rootDoc[this.panYFieldKey];
+ this.rootDoc[this.scaleFieldKey + '_reset'] = this.rootDoc[this.scaleFieldKey];
}
};
@@ -1780,6 +1808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
!this.props.Document._isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+ !this.props.Document._isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Toggle auto arrange',
@@ -1816,14 +1845,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!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: 'Fit Content Once',
- event: () => {
- this.fitContentOnce();
- },
- icon: 'object-group',
- });
+ this.props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
@@ -1917,7 +1939,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<MarqueeView
{...this.props}
ref={this._marqueeViewRef}
- ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
+ ungroup={this.rootDoc._isGroup ? this.promoteCollection : undefined}
nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
slowLoadDocuments={this.slowLoadDocuments}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4c502021d..5c053fefc 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -6,10 +6,9 @@ import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
-import { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util';
+import { GetEffectiveAcl } from '../../../../fields/util';
import { intersectRect, lightOrDark, returnFalse, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
@@ -24,10 +23,10 @@ import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { PreviewCursor } from '../../PreviewCursor';
import { SubCollectionViewProps } from '../CollectionSubView';
import { TabDocView } from '../TabDocView';
-import { TreeView } from '../TreeView';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
import React = require('react');
+import { freeformScrollMode } from '../../../util/SettingsManager';
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -223,7 +222,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
// allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode
- if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ if (e.button === 2 || (e.button === 0 && (e.altKey || (this.props.isContentActive?.(true) && Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan)))) {
// if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
// (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index ddd99c836..20ccd9ebd 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -74,6 +74,8 @@ $CAROUSEL3D_CENTER_SCALE: 1.3;
$CAROUSEL3D_SIDE_SCALE: 0.6;
$CAROUSEL3D_TOP: 15;
+$DATA_VIZ_TABLE_ROW_HEIGHT: 30;
+
:export {
contextMenuZindex: $contextMenu-zindex;
SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
@@ -91,4 +93,5 @@ $CAROUSEL3D_TOP: 15;
CAROUSEL3D_CENTER_SCALE: $CAROUSEL3D_CENTER_SCALE;
CAROUSEL3D_SIDE_SCALE: $CAROUSEL3D_SIDE_SCALE;
CAROUSEL3D_TOP: $CAROUSEL3D_TOP;
+ DATA_VIZ_TABLE_ROW_HEIGHT: $DATA_VIZ_TABLE_ROW_HEIGHT;
}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index efb702564..3db498e77 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -15,6 +15,7 @@ interface IGlobalScss {
CAROUSEL3D_CENTER_SCALE: string;
CAROUSEL3D_SIDE_SCALE: string;
CAROUSEL3D_TOP: string;
+ DATA_VIZ_TABLE_ROW_HEIGHT: string;
}
declare const globalCssVariables: IGlobalScss;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index 385ef5a1b..430446c06 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -1,9 +1,9 @@
.dataviz {
- overflow: scroll;
+ overflow: auto;
height: 100%;
width: 100%;
- .datatype-button{
+ .datatype-button {
display: flex;
flex-direction: row;
}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 8f32e2ba4..299494c83 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -36,7 +36,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
var records = DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
- return records?.filter(record => Object.keys(record).some(key => record[key]));
+ return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
}
// currently chosen visualization type: line, pie, histogram, table
@@ -110,72 +110,37 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// toggles for user to decide which chart type to view the data in
renderVizView = () => {
- const width = this.props.PanelWidth() * 0.9;
- const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
- const margin = { top: 10, right: 25, bottom: 75, left: 45 };
- if (this.records) {
- switch (this.dataVizView) {
- case DataVizView.TABLE:
- return <TableBox layoutDoc={this.layoutDoc} records={this.records} axes={this.axes} height={height} width={width} margin={margin} rootDoc={this.rootDoc} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
- case DataVizView.LINECHART:
- return (
- <LineChart
- layoutDoc={this.layoutDoc}
- ref={r => (this._vizRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- records={this.records}
- dataDoc={this.dataDoc}
- />
- );
- case DataVizView.HISTOGRAM:
- return (
- <Histogram
- layoutDoc={this.layoutDoc}
- ref={r => (this._vizRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- records={this.records}
- dataDoc={this.dataDoc}
- />
- );
- case DataVizView.PIECHART:
- return (
- <PieChart
- layoutDoc={this.layoutDoc}
- ref={r => (this._vizRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- records={this.records}
- dataDoc={this.dataDoc}
- />
- );
- }
+ const sharedProps = {
+ rootDoc: this.rootDoc,
+ layoutDoc: this.layoutDoc,
+ records: this.records,
+ axes: this.axes,
+ height: (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9,
+ width: this.props.PanelWidth() * 0.9,
+ margin: { top: 10, right: 25, bottom: 75, left: 45 },
+ };
+ if (!this.records.length) return 'no data/visualization';
+ switch (this.dataVizView) {
+ case DataVizView.TABLE:
+ return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.LINECHART:
+ return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
+ 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)} />;
}
- return 'no data/visualization';
};
render() {
- return !this.records?.length ? (
+ return !this.records.length ? (
// displays how to get data into the DataVizBox if its empty
<div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
) : (
<div
className="dataViz"
style={{
- pointerEvents: this.props.isContentActive() === true ? "all" : "none"
+ pointerEvents: this.props.isContentActive() === true ? 'all' : 'none',
}}
onWheel={e => e.stopPropagation()}
ref={r =>
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 50dfe7f05..c788a64c2 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,3 +1,4 @@
+@import '../../../global/globalCssVariables';
.chart-container {
display: flex;
flex-direction: column;
@@ -6,10 +7,10 @@
margin-top: 10px;
overflow-y: visible;
- .graph{
+ .graph {
overflow: visible;
}
- .graph-title{
+ .graph-title {
align-items: center;
font-size: larger;
display: flex;
@@ -29,7 +30,7 @@
position: relative;
margin-bottom: -35px;
}
- .selected-data{
+ .selected-data {
align-items: center;
text-align: center;
display: flex;
@@ -44,10 +45,10 @@
stroke-width: 2px;
}
}
-
- .histogram-bar{
+
+ .histogram-bar {
outline: thin solid black;
- &.hover{
+ &.hover {
outline: 3px solid black;
outline-offset: -3px;
}
@@ -91,13 +92,34 @@
.tableBox {
display: flex;
flex-direction: column;
-}
-.table-container{
- overflow: scroll;
- margin: 5px;
- margin-left: 25px;
- margin-right: 10px;
- margin-bottom: 0;
+ cursor: default;
+ height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
+ .tableBox-container {
+ overflow: scroll;
+ margin: 5px;
+ margin-left: 25px;
+ margin-right: 10px;
+ margin-bottom: 0;
+ .tableBox-table {
+ height: 100%;
+ width: 100%;
+ .tableBox-row {
+ cursor: pointer;
+ .tableBox-cell {
+ text-overflow: ellipsis;
+ width: 100%;
+ white-space: pre;
+ max-width: 150;
+ overflow: hidden;
+ }
+ }
+ }
+ tr td {
+ height: $DATA_VIZ_TABLE_ROW_HEIGHT !important; // bcz: hack. you can't set a <tr> height directly, but you can set the height of all of it's <td>s. So this is the height of a tableBox row.
+ padding: 0 !important;
+ vertical-align: middle !important;
+ }
+ }
}
.selectAll-buttons {
display: flex;
@@ -106,4 +128,4 @@
margin-top: 5px;
margin-right: 10px;
float: right;
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss
deleted file mode 100644
index 1264d6a46..000000000
--- a/src/client/views/nodes/DataVizBox/components/TableBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.table {
- margin-top: 10px;
- margin-bottom: 10px;
- margin-left: 10px;
- margin-right: 10px;
-}
-
-.table-row {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 5px;
- border-bottom: 1px solid #ccc;
-}
-
-.table-container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 4d6027ca4..b88389de6 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,5 +1,5 @@
import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
@@ -10,6 +10,7 @@ import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
+import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
import './Chart.scss';
interface TableBoxProps {
@@ -32,10 +33,17 @@ interface TableBoxProps {
@observer
export class TableBox extends React.Component<TableBoxProps> {
_inputChangedDisposer?: IReactionDisposer;
+ _containerRef: HTMLDivElement | null = null;
+
+ @observable _scrollTop = -1;
+ @observable _tableHeight = 0;
+ @observable _tableContainerHeight = 0;
+
componentDidMount() {
// if the tableData changes (ie., when records are selected by the parent (input) visulization),
// then we need to remove any selected rows that are no longer part of the visualized dataset.
this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true });
+ this.handleScroll();
}
componentWillUnmount() {
this._inputChangedDisposer?.();
@@ -67,6 +75,89 @@ export class TableBox extends React.Component<TableBoxProps> {
this.props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
};
+ @computed get viewScale() {
+ return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
+ }
+ @computed get rowHeight() {
+ return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
+ }
+ @computed get startID() {
+ return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0;
+ }
+ @computed get endID() {
+ return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
+ }
+ @action handleScroll = () => {
+ if (!this.props.docView?.()?.ContentDiv?.hidden) {
+ this._scrollTop = this._containerRef?.scrollTop ?? 0;
+ }
+ };
+ @action
+ tableRowClick = (e: React.MouseEvent, rowId: number) => {
+ const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+ const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ if (e.metaKey) {
+ // highlighting a row
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ else highlited?.push(rowId);
+ if (!selected?.includes(rowId)) selected?.push(rowId);
+ } else {
+ // selecting a row
+ if (selected?.includes(rowId)) {
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ selected.splice(selected.indexOf(rowId), 1);
+ } else selected?.push(rowId);
+ }
+ e.stopPropagation();
+ };
+
+ columnPointerDown = (e: React.PointerEvent, col: string) => {
+ const downX = e.clientX;
+ const downY = e.clientY;
+ setupMoveUpEvents(
+ {},
+ e,
+ e => {
+ // dragging off a column to create a brushed DataVizBox
+ const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
+ embedding._dataViz = DataVizView.TABLE;
+ embedding._dataViz_axes = new List<string>([col, col]);
+ embedding._dataViz_parentViz = this.props.rootDoc;
+ embedding.annotationOn = annotationOn;
+ embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
+ embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+ return embedding;
+ };
+ if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+ e.linkDocument.link_displayLine = true;
+ e.linkDocument.link_matchEmbeddings = true;
+ e.linkDocument.link_displayArrow = true;
+ // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ });
+ return true;
+ }
+ return false;
+ },
+ 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);
+ })
+ );
+ };
+
render() {
if (this._tableData.length > 0) {
return (
@@ -84,21 +175,33 @@ export class TableBox extends React.Component<TableBoxProps> {
<Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
</div>
<div
- className="table-container"
- style={{ height: this.props.height }}
- ref={r =>
- r?.addEventListener(
- 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
- (e: WheelEvent) => {
- if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
- e.stopPropagation();
- },
- { passive: false }
- )
- }>
- <table className="table">
+ className={`tableBox-container ${this.columns[0]}`}
+ style={{ height: '100%', overflow: 'auto' }}
+ onScroll={this.handleScroll}
+ ref={action((r: HTMLDivElement | null) => {
+ this._containerRef = r;
+ if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ this._tableContainerHeight = r.getBoundingClientRect().height ?? 0;
+ r.addEventListener(
+ 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+ (e: WheelEvent) => {
+ if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ },
+ { passive: false }
+ );
+ }
+ })}>
+ <table
+ className="tableBox-table"
+ ref={action((r: HTMLTableElement | null) => {
+ if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ this._tableHeight = r?.getBoundingClientRect().height ?? 0;
+ }
+ })}>
+ <div style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
<thead>
- <tr className="table-row">
+ <tr>
{this.columns.map(col => (
<th
key={this.columns.indexOf(col)}
@@ -108,58 +211,7 @@ export class TableBox extends React.Component<TableBoxProps> {
fontWeight: 'bolder',
border: '3px solid black',
}}
- onPointerDown={e => {
- const downX = e.clientX;
- const downY = e.clientY;
- setupMoveUpEvents(
- {},
- e,
- e => {
- // dragging off a column to create a brushed DataVizBox
- const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
- const targetCreator = (annotationOn: Doc | undefined) => {
- const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
- embedding._dataViz = DataVizView.TABLE;
- embedding._dataViz_axes = new List<string>([col, col]);
- embedding._dataViz_parentViz = this.props.rootDoc;
- embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
- embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
- embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
- embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
- return embedding;
- };
- if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag(
- e.target instanceof HTMLElement ? [e.target] : [],
- new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator),
- downX,
- downY,
- {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.linkDocument.link_displayLine = true;
- e.linkDocument.link_matchEmbeddings = true;
- e.linkDocument.link_displayArrow = true;
- // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
- // e.annoDragData.linkSourceDoc.followLinkZoom = false;
- }
- },
- }
- );
- return true;
- }
- return false;
- },
- 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);
- })
- );
- }}>
+ onPointerDown={e => this.columnPointerDown(e, col)}>
{col}
</th>
))}
@@ -167,44 +219,27 @@ export class TableBox extends React.Component<TableBoxProps> {
</thead>
<tbody>
{this._tableDataIds
- ?.map(rowId => ({ record: this.props.records[rowId], rowId }))
- .map(({ record, rowId }) => (
+ .filter(rowId => this.startID <= rowId && rowId <= this.endID)
+ ?.map(rowId => (
<tr
key={rowId}
- className="table-row"
- onClick={action(e => {
- const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
- const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
- if (e.metaKey) {
- // highlighting a row
- if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
- else highlited?.push(rowId);
- if (!selected?.includes(rowId)) selected?.push(rowId);
- } else {
- // selecting a row
- if (selected?.includes(rowId)) {
- if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
- selected.splice(selected.indexOf(rowId), 1);
- } else selected?.push(rowId);
- }
- e.stopPropagation();
- })}
+ className={`tableBox-row ${this.columns[0]}`}
+ onClick={e => this.tableRowClick(e, rowId)}
style={{
background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
- width: '110%',
}}>
{this.columns.map(col => {
- // each cell
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;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
- {record[col]}
+ <div className="tableBox-cell">{this.props.records[rowId][col]}</div>
</td>
);
})}
</tr>
))}
</tbody>
+ <div style={{ height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
</table>
</div>
</div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e4fc6c4a2..da665a502 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -52,6 +52,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
import { PresEffect, PresEffectDirection } from './trails';
import { PinProps, PresBox } from './trails/PresBox';
import React = require('react');
+import { KeyValueBox } from './KeyValueBox';
const { Howl } = require('howler');
interface Window {
@@ -114,7 +115,7 @@ export interface DocComponentView {
getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
restoreView?: (viewSpec: Doc) => boolean;
scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
- brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void;
+ brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void; // highlight a region of a view (used by freeforms)
getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
@@ -156,7 +157,6 @@ export interface DocumentViewSharedProps {
contentBounds?: () => undefined | { x: number; y: number; r: number; b: number };
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
suppressSetHeight?: boolean;
- thumbShown?: () => boolean;
setContentView?: (view: DocComponentView) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
@@ -299,10 +299,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
}
@computed get widgetDecorations() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : ''));
+ TraceMobx();
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations);
}
@computed get backgroundBoxColor() {
- return this.thumbShown() ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
}
@computed get docContents() {
return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
@@ -317,7 +318,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
}
@computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
- return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : ''));
+ return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents);
}
@computed get finalLayoutKey() {
return StrCast(this.Document.layout_fieldKey, 'layout');
@@ -884,23 +885,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
: undefined;
}
isContentActive = (): boolean | undefined => this._isContentActive;
- @observable _retryThumb = 1;
- @computed get _thumbShown() {
- const childHighlighted = () => false;
- // Array.from(Doc.highlightedDocs.keys())
- // .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
- // .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
- const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
- return !this.props.LayoutTemplateString &&
- !this.isContentActive() &&
- LightboxView.LightboxDoc !== this.rootDoc &&
- this.thumb &&
- !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
- ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking)
- ? true
- : false;
- }
- thumbShown = () => this._thumbShown;
childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
/// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
@@ -918,25 +902,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
pointerEvents: (isInk ? 'none' : this.contentPointerEvents()) ?? 'all',
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
- {!this._retryThumb || !this.thumbShown() ? null : (
- <img
- style={{ background: 'white', top: 0, position: 'relative' }}
- src={this.thumb} // + '?d=' + (new Date()).getTime()}
- width={this.props.PanelWidth()}
- height={this.props.PanelHeight()}
- onError={(e: any) => {
- setTimeout(action(() => (this._retryThumb = 0)));
- // prettier-ignore
- setTimeout(action(() => (this._retryThumb = 1)), 150 );
- }}
- />
- )}
<DocumentContentsView
key={1}
{...this.props}
pointerEvents={this.contentPointerEvents}
docViewPath={this.props.viewPath}
- thumbShown={this.thumbShown}
setContentView={this.setContentView}
childFilters={this.childFilters}
NativeDimScaling={this.props.NativeDimScaling}
@@ -994,7 +964,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
TraceMobx();
- if (this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
+ if (this._componentView instanceof KeyValueBox || this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.map(link => (
<div className="documentView-anchorCont" key={link[Id]}>
@@ -1232,10 +1202,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
@computed get highlighting() {
- return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
}
@computed get borderPath() {
- return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BorderPath);
}
render() {
TraceMobx();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1ab120af5..2f4f788d4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -526,7 +526,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
})}
style={{
- display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
width: this.props.PanelWidth() ? undefined : `100%`,
height: this.props.PanelWidth() ? undefined : `100%`,
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 9bcd04cf5..fd7d13655 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -28,6 +28,10 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _x = 0;
@observable _y = 0;
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ }
+
@computed get linkSource() {
return this.props.docViewPath()[this.props.docViewPath().length - 2].rootDoc; // this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 65c138975..50b070e7f 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -4,7 +4,7 @@ import { Button, EditableText, IconButton, Type } from 'browndash-components';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -251,7 +251,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
createNoteAnnotation = () => {
const createFunc = undoable(
action(() => {
- const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', '-linkedTo']);
+ const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
if (note && this.selectedPin) {
note.latitude = this.selectedPin.latitude;
note.longitude = this.selectedPin.longitude;
@@ -383,7 +383,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Removes filter
Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
const temp = this.selectedPin;
if (!this._unmounting) {
@@ -415,7 +415,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
// Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+ Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
this.recolorPin(this.selectedPin, 'green');
@@ -550,7 +550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// Removes filter
Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
this.removePushpin(this.selectedPin);
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cf44649a2..537da5055 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -566,7 +566,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
className="pdfBox"
onContextMenu={this.specificContextMenu}
style={{
- display: this.props.thumbShown?.() ? 'none' : undefined,
height: this.props.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
}}>
<div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
@@ -609,7 +608,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
render() {
TraceMobx();
- if (this.props.thumbShown?.()) return null;
const pdfView = this.renderPdfView;
const href = this.pdfUrl?.url.href;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index af20ff061..58a765d61 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -94,19 +94,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get webField() {
return Cast(this.rootDoc[this.props.fieldKey], WebField)?.url;
}
- @computed get webThumb() {
- return (
- this.props.thumbShown?.() &&
- ImageCast(
- this.layoutDoc['thumb-frozen'],
- ImageCast(
- this.layoutDoc.thumbScrollTop === this.layoutDoc._layout_scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight)
- ? this.layoutDoc.thumb
- : undefined
- )
- )?.url
- );
- }
constructor(props: any) {
super(props);
@@ -372,7 +359,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
webClipDown = (e: React.PointerEvent) => {
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
- const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
@@ -940,7 +926,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
width: !this.layoutDoc.layout_forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%',
transform: `scale(${this.zoomScaling()}) translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`,
}}>
- {this._hackHide || (this.webThumb && !this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc) ? null : this.urlContent}
+ {this._hackHide ? null : this.urlContent}
</div>
);
}
@@ -968,6 +954,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
}
+ childPointerEvents = () => (this.props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
@@ -999,7 +986,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocumentWrapper}
- childPointerEvents={this.props.isContentActive() ? 'all' : undefined}
+ childPointerEvents={this.childPointerEvents}
pointerEvents={this.annotationPointerEvents}
/>
);
@@ -1090,7 +1077,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
style={{
pointerEvents: this.pointerEvents(), //
position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
- display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
}}>
<div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
<div
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index eab62c20b..46cd6db8f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2115,7 +2115,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
width: `${100 / scale}%`,
height: `${100 / scale}%`,
}),
- // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
transition: 'inherit',
// overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
color: this.fontColor,
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index caa72c9dc..52904b852 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -11,6 +11,7 @@ import { OpenWhere } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { AnchorMenu } from './AnchorMenu';
import './Annotation.scss';
+import { LinkManager } from '../../util/LinkManager';
interface IAnnotationProps extends FieldViewProps {
anno: Doc;
@@ -86,6 +87,13 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
};
+ @computed get linkHighlighted() {
+ for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) {
+ const a1 = LinkManager.getOppositeAnchor(link, this.props.document);
+ if (a1 && Doc.IsBrushedDegreeUnmemoized(DocCast(a1.annotationOn, this.props.document))) return true;
+ }
+ }
+
render() {
const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
return (
@@ -108,7 +116,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
height: NumCast(this.props.document._height),
opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
pointerEvents: this.props.pointerEvents?.() as any,
- outline: brushed === Doc.DocBrushStatus.linkHighlighted ? 'solid 1px lightBlue' : undefined,
+ outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this.props.document.backgroundColor),
}}
/>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 23dc084ad..58a54764d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -525,6 +525,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return this.props.styleProvider?.(doc, props, property);
};
+ childPointerEvents = () => (this.props.isContentActive() !== false ? 'all' : 'none');
renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
<div
className="pdfViewerDash-overlay"
@@ -539,7 +540,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
NativeHeight={returnZero}
setContentView={emptyFunction} // override setContentView to do nothing
pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
- childPointerEvents={this.props.isContentActive() !== false ? 'all' : 'none'} // but freeform children need to get events to allow text editing, etc
+ childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
renderDepth={this.props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.props.fieldKey + '_annotations'}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7170be6cc..9e3eb28f9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -50,8 +50,9 @@ import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions } from './util';
+import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, TraceMobx } from './util';
import JSZip = require('jszip');
+export const LinkedTo = '-linkedTo';
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
const onDelegate = Object.keys(doc).includes(key.replace(/^_/, ''));
@@ -853,22 +854,22 @@ export namespace Doc {
export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
const { clone, map, linkMap } = await Doc.MakeClone(doc);
const proms = new Set<string>();
- function replacer(key: any, value: any) {
+ function replacer(key: any, value: any) {
if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
- if (value?.__type === 'image') {
+ if (value?.__type === 'image') {
const extension = value.url.replace(/.*\./, '');
proms.add(value.url.replace('.' + extension, '_o.' + extension));
return SerializationHelper.Serialize(new ImageField(value.url));
}
- if (value?.__type === 'pdf') {
+ if (value?.__type === 'pdf') {
proms.add(value.url);
return SerializationHelper.Serialize(new PdfField(value.url));
}
- if (value?.__type === 'audio') {
+ if (value?.__type === 'audio') {
proms.add(value.url);
return SerializationHelper.Serialize(new AudioField(value.url));
}
- if (value?.__type === 'video') {
+ if (value?.__type === 'video') {
proms.add(value.url);
return SerializationHelper.Serialize(new VideoField(value.url));
}
@@ -897,7 +898,9 @@ export namespace Doc {
const zip = new JSZip();
var count = 0;
- const promArr = Array.from(proms).filter(url => url?.startsWith("/files")).map(url => url.replace("/",""))// window.location.origin));
+ const promArr = Array.from(proms)
+ .filter(url => url?.startsWith('/files'))
+ .map(url => url.replace('/', '')); // window.location.origin));
console.log(promArr.length);
if (!promArr.length) {
zip.file('docs.json', jsonDocs);
@@ -905,7 +908,7 @@ export namespace Doc {
} else
promArr.forEach((url, i) => {
// loading a file and add it in a zip file
- JSZipUtils.getBinaryContent(window.location.origin+"/"+url, (err: any, data: any) => {
+ JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
if (err) throw err; // or handle the error
// // Generate a directory within the Zip file structure
// const assets = zip.folder("assets");
@@ -1343,24 +1346,11 @@ export namespace Doc {
protoBrushed = 1,
selfBrushed = 2,
highlighted = 3,
- linkHighlighted = 4,
}
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
- if (status === DocBrushStatus.unbrushed) {
- const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement();
- if (lastBrushed) {
- for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
- const a1 = Cast(link.link_anchor_1, Doc, null);
- const a2 = Cast(link.link_anchor_2, Doc, null);
- if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) {
- return DocBrushStatus.linkHighlighted;
- }
- }
- }
- }
return status;
}
export function IsBrushedDegree(doc: Doc) {
@@ -1472,7 +1462,7 @@ export namespace Doc {
const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1;
return isTransparent(StrCast(doc[key]));
}
- if (key === '-linkedTo') {
+ if (key === LinkedTo) {
// links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("..."))
const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
const matchLink = (value: string, anchor: Doc) => {