aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DocComponent.tsx14
-rw-r--r--src/client/views/DocumentDecorations.tsx8
-rw-r--r--src/client/views/InkStroke.scss25
-rw-r--r--src/client/views/InkStrokeProperties.ts1
-rw-r--r--src/client/views/InkingStroke.tsx20
-rw-r--r--src/client/views/MainView.tsx7
-rw-r--r--src/client/views/StyleProvider.tsx9
-rw-r--r--src/client/views/_nodeModuleOverrides.scss3
-rw-r--r--src/client/views/collections/CollectionMenu.tsx44
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx220
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx53
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx41
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx17
-rw-r--r--src/client/views/collections/TabDocView.tsx39
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx55
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx64
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx17
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx1
-rw-r--r--src/client/views/nodes/FieldView.tsx11
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx272
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx119
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx45
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx4
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx358
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx13
-rw-r--r--src/client/views/topbar/TopBar.tsx6
29 files changed, 633 insertions, 846 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 280ca8a8c..886dd974b 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -132,20 +132,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
- styleFromLayoutString = (scale: number) => {
- const style: { [key: string]: any } = {};
- const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
- const replacer = (match: any, expr: string, offset: any, string: any) => {
- // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? '';
- };
- divKeys.map((prop: string) => {
- const p = (this.props as any)[prop];
- typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
- });
- return style;
- };
-
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@computed public get annotationKey() {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6d1397395..3589e014a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -203,8 +203,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (this._deleteAfterIconify) {
views.forEach(iconView => {
Doc.setNativeView(iconView.props.Document);
- if (iconView.props.Document.isInkMask && iconView.props.Document.activeFrame !== undefined) {
- iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMaks to be "turned off" without removing them from the collection which allows them to function properly in a presenation.
+ if (iconView.props.Document.activeFrame) {
+ iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
} else {
iconView.props.removeDocument?.(iconView.props.Document);
}
@@ -358,8 +358,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@action
onPointerDown = (e: React.PointerEvent): void => {
- DragManager.docsBeingDragged.push(...SelectionManager.Views().map(dv => dv.rootDoc));
- this._inkDragDocs = DragManager.docsBeingDragged
+ const views = SelectionManager.Views().map(dv => dv.rootDoc);
+ this._inkDragDocs = views
.filter(doc => doc.type === DocumentType.INK)
.map(doc => {
if (InkStrokeProperties.Instance._lock) {
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index 664f2448b..bed7caf7f 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -1,22 +1,23 @@
.inkstroke-UI {
- // transform-origin: top left;
- position: absolute;
- overflow: visible;
- pointer-events: none;
- z-index: 2001; // 1 higher than documentdecorations
-
- svg:not(:root) {
- overflow: visible !important;
+ // transform-origin: top left;
position: absolute;
- left:0;
- top:0;
- }
+ overflow: visible;
+ pointer-events: none;
+ z-index: 2001; // 1 higher than documentdecorations
+
+ svg:not(:root) {
+ overflow: visible !important;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
}
.inkStroke-wrapper {
display: flex;
align-items: center;
height: 100%;
+ transition: inherit;
.inkStroke {
mix-blend-mode: multiply;
stroke-linejoin: round;
@@ -26,8 +27,10 @@
width: 100%;
height: 100%;
pointer-events: none;
+ transition: inherit;
svg:not(:root) {
overflow: visible !important;
+ transition: inherit;
}
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 821e2f739..d19a916f9 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -347,7 +347,6 @@ export class InkStrokeProperties {
const deltaX = snapData.nearestPt.X - ink[controlIndex].X;
const deltaY = snapData.nearestPt.Y - ink[controlIndex].Y;
const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex, ink.slice());
- console.log('X = ' + snapData.nearestPt.X + ' ' + snapData.nearestPt.Y);
return res;
}
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 2671aea56..dae1c10bb 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -25,7 +25,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, FieldValue, NumCast, RTFCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
import { CognitiveServices } from '../cognitive_services/CognitiveServices';
@@ -45,6 +45,10 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
import './InkStroke.scss';
import Color = require('color');
+import { ComputedField } from '../../fields/ScriptField';
+import { listSpec } from '../../fields/Schema';
+import { List } from '../../fields/List';
+import { StyleProp } from './StyleProvider';
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -56,7 +60,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
}
private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
- private _selDisposer?: IReactionDisposer;
+ private _disposers: { [key: string]: IReactionDisposer } = {};
@observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
@observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
@@ -64,13 +68,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
componentDidMount() {
this.props.setContentView?.(this);
- this._selDisposer = reaction(
+ this._disposers.selfDisper = reaction(
() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
);
}
componentWillUnmount() {
- this._selDisposer?.();
+ Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
// transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
@@ -356,7 +360,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const closed = InkingStroke.IsClosed(inkData);
const isInkMask = BoolCast(this.layoutDoc.isInkMask);
const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent');
- const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
+ const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
// bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
@@ -401,13 +405,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
inkTop,
highlightColor,
inkStrokeWidth,
- fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0),
+ Math.max(5, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0)),
StrCast(this.layoutDoc.strokeLineJoin),
StrCast(this.layoutDoc.strokeLineCap),
StrCast(this.layoutDoc.strokeBezier),
!closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor,
- startMarker,
- endMarker,
+ '',
+ '',
markerScale,
undefined,
inkScaleX,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c166594e5..06be4d194 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -20,6 +20,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
+import { ReportManager } from '../util/ReportManager';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
import { ColorScheme, SettingsManager } from '../util/SettingsManager';
@@ -171,6 +172,7 @@ export class MainView extends React.Component {
'curPage',
'viewType',
'chromeHidden',
+ 'currentFrame',
'width',
'nativeWidth',
]); // can play with these fields on someone else's
@@ -975,10 +977,11 @@ export class MainView extends React.Component {
<DictationOverlay />
<SharingManager />
<SettingsManager />
+ <ReportManager />
<CaptureManager />
<GroupManager />
<GoogleAuthenticationManager />
- <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+ <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
{this._hideUI ? null : <TopBar />}
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
@@ -991,7 +994,7 @@ export class MainView extends React.Component {
default:
return (
<>
- <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+ <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
<CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
</div>
{this.mainDashboardArea}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3bd4f5152..c0ba170c6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -40,7 +40,8 @@ export enum StyleProp {
JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
BorderPath = 'customBorder', // border path for document view
FontSize = 'fontSize', // size of text font
- FontFamily = 'fontFamily', // size of text font
+ FontFamily = 'fontFamily', // font family of text
+ FontWeight = 'fontWeight', // font weight of text
}
function darkScheme() {
@@ -117,9 +118,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.HideLinkButton:
return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
case StyleProp.FontSize:
- return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
+ return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._fontSize, StrCast(Doc.UserDoc().fontSize)));
case StyleProp.FontFamily:
- return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+ return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+ case StyleProp.FontWeight:
+ return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._fontWeight, StrCast(Doc.UserDoc().fontWeight)));
case StyleProp.ShowTitle:
return (
(doc &&
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 17eff022f..b1cce8705 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -44,7 +44,8 @@ div .lm_header {
position: absolute;
width: calc(100% - 60px);
overflow: scroll;
- background: $dark-gray;
+ background: #6b6b6b6b; //$dark-gray;
+ border-radius: 5px;
}
.lm_tab {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index eb55650e4..0dc30e0fd 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -587,49 +587,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
@undoBatch
- @action
- pinWithView = (targetDoc: Opt<Doc>) => {
- if (targetDoc) {
- TabDocView.PinDoc(targetDoc);
- const presArray: Doc[] = PresBox.Instance?.sortArray();
- const size: number = PresBox.Instance?._selectedArray.size;
- const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
- const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1];
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) {
- const scroll = targetDoc._scrollTop;
- activeDoc.presPinView = true;
- activeDoc.presPinViewScroll = scroll;
- } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.MAP) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeDoc.presPinView = true;
- activeDoc.presPinViewX = x;
- activeDoc.presPinViewY = y;
- activeDoc.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinView = true;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeDoc.presPinClipWidth = width;
- activeDoc.presPinView = true;
- }
- }
- };
-
- @computed
- get pinWithViewButton() {
- const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: 'auto', width: 19 }} />;
- return !this.selectedDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{'Pin with current view'}</div>} placement="top">
- <button className="antimodeMenu-button" style={{ justifyContent: 'center' }} onClick={() => this.pinWithView(this.selectedDoc)}>
- {presPinWithViewIcon}
- </button>
- </Tooltip>
- );
- }
-
- @undoBatch
onAlias = () => {
if (this.selectedDoc && this.selectedDocumentView) {
// const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
@@ -722,7 +679,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
{this.aliasButton}
{/* {this.pinButton} */}
{this.toggleOverlayButton}
- {this.pinWithViewButton}
<div className="collectionMenu-divider" key="divider2"></div>
{this.subChrome}
<div className="collectionMenu-divider" key="divider3"></div>
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 5c8b10ae1..b359ef420 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,6 @@
import React = require('react');
import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
@@ -37,84 +37,82 @@ export type collectionNoteTakingViewProps = {
NativeHeight?: () => number;
};
-//TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like
-// in Hypermedia?
-
+/**
+ * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1)
+ * add and remove columns (2) change column sizes and (3) move documents within and between columns. This
+ * view is reminiscent of Kanban-style web apps like Trello, or the 'Board' view in Notion. Each column is
+ * headed by a SchemaHeaderField followed by the column's documents. SchemaHeaderFields are NOT present in
+ * the rest of Dash, so it may be worthwhile to transition the headers to simple documents.
+ */
@observer
export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() {
_disposers: { [key: string]: IReactionDisposer } = {};
_masonryGridRef: HTMLDivElement | null = null;
- _draggerRef = React.createRef<HTMLDivElement>(); // change to relative widths for deleting. change storage from columnStartXCoords to columnHeaders (schemaHeaderFields has a widgth alrady)
- @observable columnStartXCoords: number[] = []; // columnHeaders -- SchemaHeaderField -- widht
+ _draggerRef = React.createRef<HTMLDivElement>();
@observable docsDraggedRowCol: number[] = [];
@observable _cursor: CursorProperty = 'grab';
- @observable _scroll = 0; // used to force the document decoration to update when scrolling
+ @observable _scroll = 0;
@computed get chromeHidden() {
return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
}
+ // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
@computed get columnHeaders() {
- const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null));
+ const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null);
const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset'));
-
- // @#Oberable draggedColIndex = ...
- //@observable cloneDivXYcoords
- // @observable clonedDiv...
-
- // render() {
- // { this.clonedDiv ? <div clone styule={{transform: clonedDivXYCoords}} : null}
- // }
-
- // in NoteatakinView Column code, add a poinerDown handler that calls setupMoveUpEvents() which will clone the column div
- // and re-render it under the cursor during move events.
- // that move move event will update 2 observales -- the draggedColIndex up above, and the location of the clonedDiv so that the render in this view will know where to render the cloned div
- // add observable variable that tells drag column to rnder in a different location than where the schemaHeaderFiel sa y ot.
- // if (col 1 is where col 3) {
- // return 3 2 1 4 56
- // }
if (needsUnsetCategory) {
- columnHeaders.push(new SchemaHeaderField('unset'));
+ setTimeout(() => columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)));
}
return columnHeaders;
}
+ // notetakingCategoryField returns the key to accessing a document's column value
@computed get notetakingCategoryField() {
return 'NotetakingCategory';
}
- @computed get filteredChildren() {
- return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
- }
@computed get headerMargin() {
return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
}
@computed get xMargin() {
return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth()));
}
+ // dividerWidth returns the width of a CollectionNoteTakingViewDivider
+ @computed get dividerWidth() {
+ return 32;
+ }
@computed get yMargin() {
return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5);
- } // 2 * this.gridGap)); }
+ }
@computed get gridGap() {
return NumCast(this.layoutDoc._gridGap, 10);
}
+ // numGroupColumns returns the number of columns
@computed get numGroupColumns() {
return this.columnHeaders.length;
}
+ // PanelWidth returns the size of the total available space the view occupies
@computed get PanelWidth() {
return this.props.PanelWidth();
}
- @computed get maxColWdith() {
+ // maxColWidth returns the maximum column width, which is slightly less than the total available space.
+ @computed get maxColWidth() {
return this.props.PanelWidth() - 2 * this.xMargin;
}
+ // availableWidth is the total amount of non-divider width. Since widths are stored relatively,
+ // we use availableWidth to convert from a percentage to a pixel count.
+ @computed get availableWidth() {
+ const numDividers = this.columnHeaders.length - 1;
+ return this.maxColWidth - numDividers * this.dividerWidth;
+ }
- // If the user has not yet created any docs (in another view), this will create a single column. Otherwise,
- // it will adjust according to the
+ // Documents should NOT have column category fields until entering this view, so the contructor creates the 'New Column'
+ // category for the user to then edit later.
constructor(props: any) {
super(props);
if (this.columnHeaders === undefined) {
- this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
- // add all of the docs that have not been added to a column to this new column
+ this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column', undefined, undefined, 1)]);
}
}
- // passed as a prop to the NoteTakingField, which uses this function
+ // children is passed as a prop to the NoteTakingField, which uses this function
// to render the docs you see within an individual column.
children = (docs: Doc[]) => {
TraceMobx();
@@ -130,39 +128,30 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
});
};
+ // Sections is one of the more important functions in this file, rendering the the documents
+ // for the UI. It properly renders documents being dragged between columns.
// [CAVEATS] (1) keep track of the offsetting
// (2) documentView gets unmounted as you remove it from the list
@computed get Sections() {
TraceMobx();
const columnHeaders = this.columnHeaders;
- let docs = this.childDocs;
+ // filter out the currently dragged docs from the child docs, since we will insert them later
+ const docs = this.childDocs.filter(d => !DragManager.docsBeingDragged.includes(d));
const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
const rowCol = this.docsDraggedRowCol;
-
- // filter out the currently dragged docs from the child docs, since we will insert them later
- if (rowCol.length && DragManager.docsBeingDragged.length) {
- const docIdsToRemove = new Set();
- DragManager.docsBeingDragged.forEach(d => docIdsToRemove.add(d[Id]));
- docs = docs.filter(d => !docIdsToRemove.has(d[Id]));
- }
-
// this will sort the docs into the correct columns (minus the ones you're currently dragging)
docs.map(d => {
const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`;
-
// look for if header exists already
const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
if (existingHeader) {
sections.get(existingHeader)!.push(d);
}
});
-
// now we add back in the docs that we're dragging
- if (rowCol.length && DragManager.docsBeingDragged.length) {
- const colHeader = columnHeaders[rowCol[1]];
- // TODO: get the actual offset that occurs if the docs were in that column
+ if (rowCol.length) {
const offset = 0;
- sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged);
+ sections.get(columnHeaders[rowCol[1]])?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged);
}
return sections;
}
@@ -173,6 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
100
);
};
+
componentDidMount() {
super.componentDidMount?.();
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
@@ -180,11 +170,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
() => this.layoutDoc._autoHeight,
autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
);
- this._disposers.headers = reaction(
- () => this.columnHeaders.slice(),
- headers => this.resizeColumns(headers.length),
- { fireImmediately: true }
- );
}
componentWillUnmount() {
@@ -206,6 +191,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
@computed get onChildClickHandler() {
return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
}
+
@computed get onChildDoubleClickHandler() {
return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
}
@@ -225,7 +211,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
// let's dive in and get the actual document we want to drag/move around
focusDocument = (doc: Doc, options?: DocFocusOptions) => {
Doc.BrushDoc(doc);
-
let focusSpeed = 0;
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
@@ -260,7 +245,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- // rules for displaying the documents
+ // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
getDisplayDoc(doc: Doc, width: () => number) {
const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
@@ -316,7 +301,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
);
}
- // This is used to get the coordinates of a document when we go from a view like freeform to columns
+ // getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns
getDocTransform(doc: Doc, dref?: DocumentView) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
@@ -329,9 +314,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
getDocWidth(d: Doc) {
const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
const existingHeader = this.columnHeaders.find(sh => sh.heading === heading);
- const index = existingHeader ? this.columnHeaders.indexOf(existingHeader) : 0;
- const endColValue = index === this.columnHeaders.length - 1 || index > this.columnStartXCoords.length - 1 ? this.PanelWidth : this.columnStartXCoords[index + 1];
- const maxWidth = index > this.columnStartXCoords.length - 1 ? this.PanelWidth : endColValue - this.columnStartXCoords[index] - 3 * this.xMargin;
+ const existingWidth = existingHeader?.width ? existingHeader.width : 0;
+ const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth - 2 * this.xMargin : this.maxColWidth - 2 * this.xMargin;
if (d.type === DocumentType.RTF) {
return maxWidth;
}
@@ -358,28 +342,36 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
return Math.min(childHeight, maxHeight, panelHeight);
}
- // called when a column is either added or deleted. This function creates n evenly spaced columns
+ // resizeColumns is called whenever a user adds or removes a column. When removing,
+ // this function renormalizes the column widths to fill the newly available space
+ // in the panel. When adding, this function renormalizes the existing columns to take up
+ // (n - 1)/n space, since the new column will be allocated 1/n of the total space.
+ // Column widths are relative (portion of available space) and stored in the 'width'
+ // field of SchemaHeaderFields.
+ //
+ // Removing example: column widths are [0.5, 0.30, 0.20] --> user deletes the final column --> column widths are [0.625, 0.375].
+ // Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33]
@action
- resizeColumns = (n: number) => {
- const totalWidth = this.PanelWidth;
- const dividerWidth = 32;
- const totaldividerWidth = (n - 1) * dividerWidth;
- const colWidth = (totalWidth - totaldividerWidth) / n;
- const newColXCoords: number[] = [];
- let colStart = 0;
- for (let i = 0; i < n; i++) {
- newColXCoords.push(colStart);
- colStart += colWidth + dividerWidth;
+ resizeColumns = (isAdd: boolean, colWidth: number, colIndex: number) => {
+ const n = this.columnHeaders.length;
+ if (n == 1) {
+ this.columnHeaders[0].setWidth(1);
+ return true;
}
- this.columnStartXCoords = newColXCoords;
+ const scaleFactor = isAdd ? 1 - colWidth : 1 / (1 - colWidth);
+ this.columnHeaders.forEach((h, i) => {
+ if (!(isAdd && i == colIndex)) {
+ h.width < 0 ? h.setWidth(1 / n) : h.setWidth(h.width * scaleFactor);
+ }
+ });
+ return true;
};
- // This function is used to preview where a document will drop in a column once a drag is complete.
+ // onPointerMove is used to preview where a document will drop in a column once a drag is complete.
@action
onPointerMove = (force: boolean, ex: number, ey: number) => {
if (this.childDocList && (this.childDocList.includes(DragManager.DocDragData?.draggedDocuments.lastElement()!) || force || this.isContentActive())) {
// get the current docs for the column based on the mouse's x coordinate
- // will use again later, which is why we're saving as local
const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
const colDocs = this.getDocsFromXCoord(xCoord);
// get the index for where you need to insert the doc you are currently dragging
@@ -408,10 +400,16 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
};
- // returns the column index for a given x-coordinate
+ // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate).
+ // This function is used to know which document a column SHOULD be in while it is being dragged.
getColumnFromXCoord = (xCoord: number): number => {
const numColumns = this.columnHeaders.length;
- const coords = this.columnStartXCoords.slice();
+ const coords = [];
+ let colStartXCoord = 0;
+ for (let i = 0; i < numColumns; i++) {
+ coords.push(colStartXCoord);
+ colStartXCoord += this.columnHeaders[i].width * this.availableWidth + this.dividerWidth;
+ }
coords.push(this.PanelWidth);
let colIndex = 0;
for (let i = 0; i < numColumns; i++) {
@@ -423,7 +421,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
return colIndex;
};
- // returns the docs of a column based on the x-coordinate provided.
+ // getDocsFromXCoord returns the docs of a column based on the x-coordinate provided.
getDocsFromXCoord = (xCoord: number): Doc[] => {
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = StrCast(this.columnHeaders[colIndex].heading);
@@ -455,6 +453,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
};
+ // onInternalDrop is used when dragging and dropping a document within the view, such as dragging
+ // a document to a new column or changing its order within the column.
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -464,14 +464,11 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
const rowCol = this.docsDraggedRowCol;
const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
-
- // const docs = this.childDocs
const docs = this.childDocList;
if (docs && newDocs.length) {
// remove the dragged documents from the childDocList
newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1));
// if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly
- //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify
if (rowCol[0] <= 0) {
docs.splice(0, 0, ...newDocs);
} else {
@@ -482,8 +479,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
}
}
- } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging
- else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+ } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
this.props.addDocument?.(source);
de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
@@ -502,7 +498,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
return true;
}
- // when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...)
+ // onExternalDrop is used when dragging a document out from a CollectionNoteTakingView
+ // to another tab/view/collection
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const targInd = this.docsDraggedRowCol?.[0] || 0;
const colInd = this.docsDraggedRowCol?.[1] || 0;
@@ -530,12 +527,14 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
headings = () => Array.from(this.Sections);
refList: any[] = [];
+
editableViewProps = () => ({
GetValue: () => '',
SetValue: this.addGroup,
contents: '+ New Column',
});
+ // sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column)
sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const type = 'number';
return (
@@ -558,8 +557,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
}}
addDocument={this.addDocument}
- // docsByColumnHeader={this._docsByColumnHeader}
- // setDocsForColHeader={this.setDocsForColHeader}
chromeHidden={this.chromeHidden}
columnHeaders={this.columnHeaders}
Document={this.props.Document}
@@ -569,8 +566,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
numGroupColumns={this.numGroupColumns}
gridGap={this.gridGap}
pivotField={this.notetakingCategoryField}
- columnStartXCoords={this.columnStartXCoords}
- maxColWidth={this.maxColWdith}
+ dividerWidth={this.dividerWidth}
+ maxColWidth={this.maxColWidth}
+ availableWidth={this.availableWidth}
PanelWidth={this.PanelWidth}
key={heading?.heading ?? ''}
headings={this.headings}
@@ -586,19 +584,20 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
);
};
- // called when adding a new columnHeader
+ // addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of
+ // columnHeaders and resizing the existing columns to make room for our new one.
@undoBatch
@action
addGroup = (value: string) => {
+ for (const header of this.columnHeaders) {
+ if (header.heading == value) {
+ alert('You cannot use an existing column name. Please try a new column name');
+ return value;
+ }
+ }
const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
- return value && columnHeaders?.push(new SchemaHeaderField(value)) ? true : false;
- };
-
- sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
- const descending = StrCast(this.layoutDoc._columnsSort) === 'descending';
- const firstEntry = descending ? b : a;
- const secondEntry = descending ? a : b;
- return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
+ const newColWidth = 1 / (this.numGroupColumns + 1);
+ return value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1) ? true : false;
};
onContextMenu = (e: React.MouseEvent): void => {
@@ -612,29 +611,29 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
};
- // used to reset column sizes when using the drag handlers
+ // setColumnStartXCoords is used to update column widths when using the drag handlers between columns
@action
- setColumnStartXCoords = (movementX: number, colIndex: number) => {
- const coords = [...this.columnStartXCoords];
- coords[colIndex] += movementX;
- this.columnStartXCoords = coords;
+ setColumnStartXCoords = (movementXScreen: number, colIndex: number) => {
+ const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0];
+ const leftHeader = this.columnHeaders[colIndex];
+ const rightHeader = this.columnHeaders[colIndex + 1];
+ leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
+ rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth);
};
+ // renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view
+ // has more than one column, those columns will be separated by a CollectionNoteTakingViewDivider that
+ // allows the user to adjust the column widths.
@computed get renderedSections() {
TraceMobx();
- // let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
- // if (this.pivotField) {
- // const entries = Array.from(this.Sections.entries());
- // sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries;
- // }
const entries = Array.from(this.Sections.entries());
- const sections = entries; //.sort(this.sortFunc);
+ const sections = entries;
const eles: JSX.Element[] = [];
for (let i = 0; i < sections.length; i++) {
const col = this.sectionNoteTaking(sections[i][0], sections[i][1]);
eles.push(col);
if (i < sections.length - 1) {
- eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i + 1} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
+ eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
}
}
return eles;
@@ -642,7 +641,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
@computed get buttonMenu() {
const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
- // TODO:glr Allow support for multiple buttons
if (menuDoc) {
const width: number = NumCast(menuDoc._width, 30);
const height: number = NumCast(menuDoc._height, 30);
@@ -693,7 +691,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
@computed get backgroundEvents() {
return SnappingManager.GetIsDragging();
}
+
observer: any;
+
render() {
TraceMobx();
const buttonMenu = this.rootDoc.buttonMenu;
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 624beca96..4610da4e3 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -5,11 +5,12 @@ import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { ScriptField } from '../../../fields/ScriptField';
+import { Cast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { returnEmptyString } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
@@ -21,13 +22,10 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionNoteTakingView.scss';
-import { listSpec } from '../../../fields/Schema';
-import { Cast } from '../../../fields/Types';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-// So this is how we are storing a column
interface CSVFieldColumnProps {
Document: Doc;
DataDoc: Opt<Doc>;
@@ -38,7 +36,6 @@ interface CSVFieldColumnProps {
columnHeaders: SchemaHeaderField[] | undefined;
headingObject: SchemaHeaderField | undefined;
yMargin: number;
- // columnWidth: number;
numGroupColumns: number;
gridGap: number;
type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
@@ -49,32 +46,32 @@ interface CSVFieldColumnProps {
screenToLocalTransform: () => Transform;
observeHeight: (myref: any) => void;
unobserveHeight: (myref: any) => void;
- //setDraggedCol:(clonedDiv:any, header:SchemaHeaderField, xycoors: )
editableViewProps: () => any;
- resizeColumns: (n: number) => void;
- columnStartXCoords: number[];
+ resizeColumns: (isAdd: boolean, colWidth: number, colIndex: number) => boolean;
PanelWidth: number;
maxColWidth: number;
- // docsByColumnHeader: Map<string, Doc[]>
- // setDocsForColHeader: (key: string, docs: Doc[]) => void
+ dividerWidth: number;
+ availableWidth: number;
}
+/**
+ * CollectionNoteTakingViewColumn represents an individual column rendered in CollectionNoteTakingView. The
+ * majority of functions here are for rendering styles.
+ */
@observer
export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColumnProps> {
@observable private _background = 'inherit';
+ // columnWidth returns the width of a column in absolute pixels
@computed get columnWidth() {
- // base cases
- if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
+ if (!this.props.columnHeaders || !this.props.headingObject) {
return this.props.maxColWidth;
}
- const i = this.props.columnHeaders.indexOf(this.props.headingObject);
- if (i < 0 || i > this.props.columnStartXCoords.length - 1) {
+ if (this.props.columnHeaders.length == 1) {
return this.props.maxColWidth;
}
- const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i + 1];
- // TODO make the math work here. 35 is half of 70, which is the current width of the divider
- return endColValue - this.props.columnStartXCoords[i] - 30;
+ const i = this.props.columnHeaders.indexOf(this.props.headingObject);
+ return this.props.columnHeaders[i].width * this.props.availableWidth;
}
private dropDisposer?: DragManager.DragDropDisposer;
@@ -84,8 +81,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
@observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
_ele: HTMLElement | null = null;
- // This is likely similar to what we will be doing. Why do we need to make these refs?
- // is that the only way to have drop targets?
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
if (ele) {
@@ -134,6 +129,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
@action pointerLeave = () => (this._background = 'inherit');
textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ // addNewTextDoc is called when a user starts typing in a column to create a new node
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
@@ -146,14 +142,22 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
return this.props.addDocument?.(newDoc) || false;
};
+ // deleteColumn is called when a user deletes a column using the 'trash' icon in the button area.
+ // If the user deletes the first column, the documents get moved to the second column. Otherwise,
+ // all docs are added to the column directly to the left.
@undoBatch
@action
deleteColumn = () => {
const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
if (columnHeaders && this.props.headingObject) {
const index = columnHeaders.indexOf(this.props.headingObject);
- this.props.docList.forEach(d => (d[this.props.pivotField] = 'unset'));
+ const newColIndex = index > 0 ? index - 1 : 1;
+ const newColHeader = this.props.columnHeaders ? this.props.columnHeaders[newColIndex] : undefined;
+ const newHeading = newColHeader ? newColHeader.heading : 'unset';
+ this.props.docList.forEach(d => (d[this.props.pivotField] = newHeading));
+ const colWidth = this.props.columnHeaders ? this.props.columnHeaders[index].width : 0;
columnHeaders.splice(index, 1);
+ this.props.resizeColumns(false, colWidth, index);
}
};
@@ -255,7 +259,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
</div>
</div>
) : null;
- // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
const templatecols = `${this.columnWidth}px `;
const type = this.props.Document.type;
return (
@@ -280,10 +283,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
</div>
{!this.props.chromeHidden && type !== DocumentType.PRES ? (
- <div
- className="collectionNoteTakingView-DocumentButtons"
- // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
- style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
+ <div className="collectionNoteTakingView-DocumentButtons" style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
<div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
<EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
</div>
@@ -311,7 +311,6 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
key={heading}
style={{
- //TODO: change this so that it's based on the column width
width: this.columnWidth,
background: this._background,
}}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 7d31b3193..8d659f790 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,5 +1,7 @@
import { action, observable } from 'mobx';
import * as React from 'react';
+import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { UndoManager } from '../../util/UndoManager';
interface DividerProps {
index: number;
@@ -7,34 +9,35 @@ interface DividerProps {
setColumnStartXCoords: (movementX: number, colIndex: number) => void;
}
+/**
+ * CollectionNoteTakingViewDivider are dividers between CollectionNoteTakingViewColumns,
+ * which only appear when there is more than 1 column in CollectionNoteTakingView. Dividers
+ * are two simple vertical lines that allow the user to alter the widths of CollectionNoteTakingViewColumns.
+ */
export class CollectionNoteTakingViewDivider extends React.Component<DividerProps> {
@observable private isHoverActive = false;
@observable private isResizingActive = false;
@action
private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
- e.stopPropagation();
- e.preventDefault();
- window.removeEventListener('pointermove', this.onPointerMove);
- window.removeEventListener('pointerup', this.onPointerUp);
- window.addEventListener('pointermove', this.onPointerMove);
- window.addEventListener('pointerup', this.onPointerUp);
+ const batch = UndoManager.StartBatch('resizing');
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ this.props.setColumnStartXCoords(delta[0], this.props.index);
+ return false;
+ },
+ action(() => {
+ this.isResizingActive = false;
+ this.isHoverActive = false;
+ batch.end();
+ }),
+ emptyFunction
+ );
this.isResizingActive = true;
};
- @action
- private onPointerUp = () => {
- this.isResizingActive = false;
- this.isHoverActive = false;
- window.removeEventListener('pointermove', this.onPointerMove);
- window.removeEventListener('pointerup', this.onPointerUp);
- };
-
- @action
- onPointerMove = ({ movementX }: PointerEvent) => {
- this.props.setColumnStartXCoords(movementX, this.props.index);
- };
-
render() {
return (
<div
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d4efef47a..71834607c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -455,8 +455,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1));
docs.splice(insertInd - offset, 0, ...newDocs);
}
- // reset drag manager docs, because we just dropped
- DragManager.docsBeingDragged.length = 0;
}
} else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
@@ -493,13 +491,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
targInd = i;
}
});
- super.onExternalDrop(e, {}, () => {
- if (targInd !== -1) {
- const newDoc = this.childDocs[this.childDocs.length - 1];
- const docs = this.childDocList;
- if (docs) {
- docs.splice(docs.length - 1, 1);
- docs.splice(targInd, 0, newDoc);
+ super.onExternalDrop(e, {}, (docs: Doc[]) => {
+ if (targInd === -1) {
+ this.addDocument(docs);
+ }
+ else {
+ const childDocs = this.childDocList;
+ if (childDocs) {
+ childDocs.splice(targInd, 0, ...docs);
}
}
});
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e147f34d2..73574bdb3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -242,43 +242,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
- // If pinWithView option set then update scale and x / y props of slide
- if (pinProps?.pinWithView) {
- const viewProps = pinProps.pinWithView;
- pinDoc.presPinView = true;
- pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2;
- pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2;
- pinDoc.presPinViewScale = viewProps.scale;
- pinDoc.contentBounds = new List<number>([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left + viewProps.bounds.width, viewProps.bounds.top + viewProps.bounds.height]);
- }
- if (pinProps?.pinDocView) {
- const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking;
- const pannable: boolean = (pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG;
- if (scrollable) {
- const scroll = doc._scrollTop;
- pinDoc.presPinView = true;
- pinDoc.presPinViewScroll = scroll;
- } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) {
- pinDoc.presPinView = true;
- pinDoc.presStartTime = doc._currentTimecode;
- pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1;
- } else if (pannable) {
- pinDoc.presPinView = true;
- pinDoc.presPinViewX = pinDoc._panX;
- pinDoc.presPinViewY = pinDoc._panY;
- pinDoc.presPinViewScale = pinDoc._viewScale;
- const pw = NumCast(pinProps.panelWidth);
- const ph = NumCast(pinProps.panelHeight);
- const ps = NumCast(pinDoc._viewScale);
- if (pw && ph && ps) {
- pinDoc.contentBounds = new List<number>([NumCast(pinDoc.panX) - pw / 2 / ps, NumCast(pinDoc.panY) - ph / 2 / ps, NumCast(pinDoc.panX) + pw / 2 / ps, NumCast(pinDoc.panY) + ph / 2 / ps]);
- }
- } else if (doc.type === DocumentType.COMPARISON) {
- const width = doc._clipWidth;
- pinDoc.presPinClipWidth = width;
- pinDoc.presPinView = true;
- }
- }
+
+ PresBox.pinDocView(pinDoc, pinProps);
pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
if (!pinProps?.audioRange && duration !== undefined) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index ee01c341b..89cc22d07 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -34,6 +34,7 @@ export interface PoolData {
zIndex?: number;
width?: number;
height?: number;
+ backgroundColor?: string;
color?: string;
opacity?: number;
transition?: string;
@@ -45,7 +46,7 @@ export interface PoolData {
export interface ViewDefResult {
ele: JSX.Element;
bounds?: ViewDefBounds;
- inkMask?: boolean;
+ inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transprent (hidden), >0 = mask layer and not hidden
}
function toLabel(target: FieldResult<Field>) {
if (typeof target === 'number' || Number(target)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 010132aa5..d80fcdfc3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -20,15 +20,20 @@
pointer-events: none;
}
+.collectionfreeformview-mask-empty,
.collectionfreeformview-mask {
- mix-blend-mode: multiply;
z-index: 5000;
width: $INK_MASK_SIZE;
height: $INK_MASK_SIZE;
transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF);
- background-color: rgba(0, 0, 0, 0.7);
pointer-events: none;
position: absolute;
+ background-color: transparent;
+ transition: background-color 1s ease 0s;
+}
+.collectionfreeformview-mask {
+ mix-blend-mode: multiply;
+ background-color: rgba(0, 0, 0, 0.7);
}
.collectionfreeformview-viewdef {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0fd326091..052cbd3bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -115,13 +115,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
- @observable _keyframeEditing = false;
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get views() {
- const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele);
- const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele);
- if (viewsMask.length) renderableEles.push(<div className="collectionfreeformview-mask">{viewsMask}</div>);
+ const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1).map(ele => ele.ele);
+ const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask === -1).map(ele => ele.ele);
+ if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>);
return renderableEles;
}
@computed get fitToContentVals() {
@@ -185,8 +184,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
}
};
- @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
- getKeyFrameEditing = () => this._keyframeEditing;
onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
@@ -251,7 +248,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
isCurrent(doc: Doc) {
- if (doc.isInkMask && doc.opacity === 0) return false; // bcz: hacky --- allows inkMasks to be "turned off" in a presentation without removing them from the collection. otherwise, they still render a gray background.. need to come back to this and fix.
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
const curTime = NumCast(this.Document._currentTimecode, -1);
@@ -273,15 +269,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
- const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+ const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
if (this.Document._currentFrame !== undefined) {
CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
- vals.x = x + (vals.x || 0) - dropPos[0];
- vals.y = y + (vals.y || 0) - dropPos[1];
+ vals.x = x + NumCast(vals.x) - dropPos[0];
+ vals.y = y + NumCast(vals.y) - dropPos[1];
vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined;
CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
} else {
@@ -1318,22 +1314,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
- const layoutDoc = Doc.Layout(params.pair.layout);
- const { z, color, zIndex } = params.pair.layout;
- const { x, y, opacity } =
- this.Document._currentFrame === undefined
- ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.childOpacity ? this.props.childOpacity() : this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
- : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
+ const curFrame = Cast(this.Document._currentFrame, 'number');
+ const childDoc = params.pair.layout;
+ const childDocLayout = Doc.Layout(childDoc);
+ const { z, zIndex } = childDoc;
+ const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
+ const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
return {
x: NumCast(x),
y: NumCast(y),
z: Cast(z, 'number'),
- color: StrCast(color),
+ color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
+ opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
- transition: StrCast(layoutDoc.dataTransition),
- opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
- width: Cast(layoutDoc._width, 'number'),
- height: Cast(layoutDoc._height, 'number'),
+ width: Cast(childDocLayout._width, 'number'),
+ height: Cast(childDocLayout._height, 'number'),
+ transition: StrCast(childDocLayout.dataTransition),
pair: params.pair,
replica: '',
};
@@ -1448,7 +1445,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
const newPos = entry[1];
- if (!lastPos || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+ if (
+ !lastPos ||
+ newPos.color !== lastPos.color ||
+ newPos.backgroundColor !== lastPos.backgroundColor ||
+ newPos.opacity !== lastPos.opacity ||
+ newPos.x !== lastPos.x ||
+ newPos.y !== lastPos.y ||
+ newPos.z !== lastPos.z ||
+ newPos.zIndex !== lastPos.zIndex
+ ) {
this._layoutPoolData.set(entry[0], newPos);
}
if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
@@ -1465,7 +1471,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
elements.push({
ele: this.getChildDocView(entry[1]),
bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
- inkMask: BoolCast(entry[1].pair.layout.isInkMask),
+ inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity) : -1,
})
);
@@ -2207,3 +2213,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
!readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
});
+ScriptingGlobals.add(function pinWithView(readOnly: boolean) {
+ !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() }));
+});
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e19e2d525..86566ac6a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -16,9 +16,14 @@ import { StyleProp } from '../StyleProvider';
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps } from './DocumentView';
import React = require('react');
+import { InkField } from '../../../fields/InkField';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Field } from '../../util/ProsemirrorCopy/prompt';
+import { RefField } from '../../../fields/RefField';
+import { ObjectField } from '../../../fields/ObjectField';
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+ dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
renderCutoffProvider: (doc: Doc) => boolean;
zIndex?: number;
@@ -32,32 +37,39 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
+ public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames
+ public static animDataFields = ['data', 'text']; // fields that are configured to be animatable using animation frames
@observable _animPos: number[] | undefined = undefined;
@observable _contentView: DocumentView | undefined | null;
get displayName() {
+ // this makes mobx trace() statements more descriptive
return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
- } // this makes mobx trace() statements more descriptive
+ }
+
get transform() {
return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
}
get X() {
- return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
+ return this.dataProvider?.x ?? NumCast(this.Document.x);
}
get Y() {
- return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y);
+ return this.dataProvider?.y ?? NumCast(this.Document.y);
}
get ZInd() {
- return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex);
+ return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
}
get Opacity() {
- return this.dataProvider ? this.dataProvider.opacity : undefined;
+ return this.dataProvider?.opacity;
+ }
+ get BackgroundColor() {
+ return this.dataProvider?.backgroundColor;
+ }
+ get Color() {
+ return this.dataProvider?.color;
}
get Highlight() {
return this.dataProvider?.highlight;
}
- @computed get ShowTitle() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
- }
@computed get dataProvider() {
return this.props.dataProvider?.(this.props.Document, this.props.replica);
}
@@ -66,7 +78,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
- if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
+ if (doc === this.layoutDoc)
+ // prettier-ignore
+ switch (property) {
+ 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;
+ }
return this.props.styleProvider?.(doc, props, property);
};
@@ -77,6 +95,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}, {} as { [val: string]: Opt<number> });
}
+ public static getStringValues(doc: Doc, time: number) {
+ return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
+ p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+ return p;
+ }, {} as { [val: string]: Opt<string> });
+ }
+
public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
@@ -95,6 +120,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null);
findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
});
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+ });
+ CollectionFreeFormDocumentView.animDataFields.forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+ });
})
);
setTimeout(
@@ -141,9 +174,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
// opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
}
- CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode)));
- doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
- doc.dataTransition = 'inherit';
+ CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedNumber(val, 'activeFrame', doc, currTimecode)));
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
+ CollectionFreeFormDocumentView.animDataFields.forEach(val => (Doc.GetProto(doc)[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', Doc.GetProto(doc), currTimecode)));
+ const targetDoc = doc.type === DocumentType.RTF ? Doc.GetProto(doc) : doc; // data fields, like rtf 'text' exist on the data doc, so
+ doc !== targetDoc && (targetDoc.context = doc.context); // the computed fields don't see the layout doc -- need to copy the context to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
+ targetDoc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
+ targetDoc.dataTransition = 'inherit';
});
}
@@ -189,7 +226,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
};
- const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
return (
<div
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 381436a56..d065c62fb 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -25,7 +25,7 @@ import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
import { FilterBox } from './FilterBox';
-import { FormattedTextBox, FormattedTextBoxProps } from './formattedText/FormattedTextBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
import { KeyValueBox } from './KeyValueBox';
@@ -114,14 +114,13 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
@observer
export class DocumentContentsView extends React.Component<
- DocumentViewProps &
- FormattedTextBoxProps & {
- isSelected: (outsideReaction: boolean) => boolean;
- select: (ctrl: boolean) => void;
- NativeDimScaling?: () => number;
- setHeight?: (height: number) => void;
- layoutKey: string;
- }
+ DocumentViewProps & {
+ isSelected: (outsideReaction: boolean) => boolean;
+ select: (ctrl: boolean) => void;
+ NativeDimScaling?: () => number;
+ setHeight?: (height: number) => void;
+ layoutKey: string;
+ }
> {
@computed get layout(): string {
TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 6ea697a2f..9aaaf1e68 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -99,6 +99,7 @@
border-radius: inherit;
width: 100%;
height: 100%;
+ transition: inherit;
.sharingIndicator {
height: 30px;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 172adcafe..74143a731 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1656,7 +1656,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ref={this.ContentRef}
style={{
transition: this.props.dataTransition,
- //position: this.props.Document.isInkMask ? 'absolute' : undefined,
transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
height:
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index dd2c13391..77aaa4441 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -26,16 +26,21 @@ export interface FieldViewProps extends DocumentViewSharedProps {
NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
onBrowseClick?: () => ScriptField | undefined;
onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+ pointerEvents?: () => Opt<string>;
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
- pointerEvents?: () => Opt<string>;
+ // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
+ // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
+ backgroundColor?: string;
+ color?: string;
fontSize?: number;
height?: number;
width?: number;
- background?: string;
- color?: string;
xPadding?: number;
yPadding?: number;
+ noSidebar?: boolean;
+ dontScale?: boolean;
+ dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
}
@observer
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 4b1fbaf7d..d9f46509e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,34 +1,37 @@
-
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field, FieldResult } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { RichTextField } from "../../../fields/RichTextField";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { ImageField } from "../../../fields/URLField";
-import { Docs } from "../../documents/Documents";
-import { SetupDrag } from "../../util/DragManager";
-import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting";
-import { undoBatch } from "../../util/UndoManager";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Field, FieldResult } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
+import { Docs } from '../../documents/Documents';
+import { SetupDrag } from '../../util/DragManager';
+import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
+import { undoBatch } from '../../util/UndoManager';
import { FieldView, FieldViewProps } from './FieldView';
-import "./KeyValueBox.scss";
-import { KeyValuePair } from "./KeyValuePair";
-import React = require("react");
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import e = require("express");
+import './KeyValueBox.scss';
+import { KeyValuePair } from './KeyValuePair';
+import React = require('react');
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import e = require('express');
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImageBox } from './ImageBox';
export type KVPScript = {
script: CompiledScript;
- type: "computed" | "script" | false;
+ type: 'computed' | 'script' | false;
onDelegate: boolean;
};
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(KeyValueBox, fieldStr);
+ }
private _mainCont = React.createRef<HTMLDivElement>();
private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
@@ -37,8 +40,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@observable private rows: KeyValuePair[] = [];
- @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
- get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
+ @computed get splitPercentage() {
+ return NumCast(this.props.Document.schemaSplitPercentage, 50);
+ }
+ get fieldDocToLayout() {
+ return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document;
+ }
@action
onEnterKey = (e: React.KeyboardEvent): void => {
@@ -46,19 +53,19 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
e.stopPropagation();
if (this._keyInput.current?.value && this._valInput.current?.value && this.fieldDocToLayout) {
if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput.current.value, this._valInput.current.value)) {
- this._keyInput.current.value = "";
- this._valInput.current.value = "";
+ this._keyInput.current.value = '';
+ this._valInput.current.value = '';
document.body.focus();
}
}
}
- }
+ };
public static CompileKVPScript(value: string): KVPScript | undefined {
- const eq = value.startsWith("=");
+ const eq = value.startsWith('=');
value = eq ? value.substr(1) : value;
- const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
+ const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false;
value = dubEq ? value.substr(2) : value;
- const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: "any", _readOnly_: "boolean" }, editable: false };
+ const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false };
if (dubEq) options.typecheck = false;
const script = CompileScript(value, options);
return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
@@ -67,11 +74,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
const { script, type, onDelegate } = kvpScript;
//const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
- const target = forceOnDelegate || onDelegate || key.startsWith("_") ? doc : doc.proto || doc;
+ const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : doc.proto || doc;
let field: Field;
- if (type === "computed") {
+ if (type === 'computed') {
field = new ComputedField(script);
- } else if (type === "script") {
+ } else if (type === 'script') {
field = new ScriptField(script);
} else {
const res = script.run({ this: target }, console.log);
@@ -96,7 +103,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
if (e.buttons === 1 && this.props.isSelected(true)) {
e.stopPropagation();
}
- }
+ };
onPointerWheel = (e: React.WheelEvent): void => e.stopPropagation();
rowHeight = () => 30;
@@ -104,7 +111,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@computed get createTable() {
const doc = this.fieldDocToLayout;
if (!doc) {
- return <tr><td>Loading...</td></tr>;
+ return (
+ <tr>
+ <td>Loading...</td>
+ </tr>
+ );
}
const realDoc = doc;
@@ -122,83 +133,102 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let i = 0;
const self = this;
for (const key of Object.keys(ids).slice().sort()) {
- rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} PanelWidth={this.props.PanelWidth} PanelHeight={this.rowHeight}
- ref={(function () {
- let oldEl: KeyValuePair | undefined;
- return (el: KeyValuePair) => {
- if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1);
- oldEl = el;
- if (el) self.rows.push(el);
- };
- })()} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
+ rows.push(
+ <KeyValuePair
+ doc={realDoc}
+ addDocTab={this.props.addDocTab}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.rowHeight}
+ ref={(function () {
+ let oldEl: KeyValuePair | undefined;
+ return (el: KeyValuePair) => {
+ if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1);
+ oldEl = el;
+ if (el) self.rows.push(el);
+ };
+ })()}
+ keyWidth={100 - this.splitPercentage}
+ rowStyle={'keyValueBox-' + (i++ % 2 ? 'oddRow' : 'evenRow')}
+ key={key}
+ keyName={key}
+ />
+ );
}
return rows;
}
@computed get newKeyValue() {
- return <tr className="keyValueBox-valueRow">
- <td className="keyValueBox-td-key" onClick={(e) => { this._keyInput.current!.select(); e.stopPropagation(); }} style={{ width: `${100 - this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} ref={this._keyInput} type="text" placeholder="Key" />
- </td>
- <td className="keyValueBox-td-value" onClick={(e) => { this._valInput.current!.select(); e.stopPropagation(); }} style={{ width: `${this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
- </td>
- </tr>;
+ return (
+ <tr className="keyValueBox-valueRow">
+ <td
+ className="keyValueBox-td-key"
+ onClick={e => {
+ this._keyInput.current!.select();
+ e.stopPropagation();
+ }}
+ style={{ width: `${100 - this.splitPercentage}%` }}>
+ <input style={{ width: '100%' }} ref={this._keyInput} type="text" placeholder="Key" />
+ </td>
+ <td
+ className="keyValueBox-td-value"
+ onClick={e => {
+ this._valInput.current!.select();
+ e.stopPropagation();
+ }}
+ style={{ width: `${this.splitPercentage}%` }}>
+ <input style={{ width: '100%' }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
+ </td>
+ </tr>
+ );
}
@action
onDividerMove = (e: PointerEvent): void => {
const nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
- }
+ this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
+ };
@action
onDividerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointermove', this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- }
+ };
onDividerDown = (e: React.PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointermove', this.onDividerMove);
document.addEventListener('pointerup', this.onDividerUp);
- }
+ };
- getTemplate = async () => {
- const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeHidden: true });
- parent._columnWidth = 100;
- for (const row of this.rows.filter(row => row.isChecked)) {
- await this.createTemplateField(parent, row);
- row.uncheck();
+ getFieldView = async () => {
+ const rows = this.rows.filter(row => row.isChecked);
+ if (rows.length > 1) {
+ const parent = Docs.Create.StackingDocument([], { _autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document.data).title}`, _chromeHidden: true });
+ for (const row of rows) {
+ const field = this.createFieldView(DocCast(this.props.Document.data), row);
+ field && Doc.AddDocToList(parent, 'data', field);
+ row.uncheck();
+ }
+ return parent;
}
- return parent;
- }
+ return this.createFieldView(DocCast(this.props.Document.data), rows.lastElement());
+ };
- createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => {
+ createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
const metaKey = row.props.keyName;
- const sourceDoc = await Cast(this.props.Document.data, Doc);
- if (!sourceDoc) {
- return;
- }
+ const fieldTemplate = Doc.MakeAlias(templateDoc);
+ fieldTemplate.title = metaKey;
+ fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
+ return fieldTemplate;
+ };
- const fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
- if (!fieldTemplate) {
- return;
- }
- const previousViewType = fieldTemplate._viewType;
- Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
- previousViewType && (fieldTemplate._viewType = previousViewType);
-
- Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
- }
-
- inferType = async (data: FieldResult, metaKey: string) => {
+ inferType = (data: FieldResult, metaKey: string) => {
const options = { _width: 300, _height: 300, title: metaKey };
- if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
- return Docs.Create.TextDocument("", options);
+ if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
+ return FormattedTextBox.LayoutString(metaKey);
} else if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
- const first = await Cast(data[0], Doc);
+ const first = DocCast(data[0]);
if (!first || !first.data) {
return Docs.Create.StackingDocument([], options);
}
@@ -212,44 +242,52 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return undefined;
}
} else if (data instanceof ImageField) {
- return Docs.Create.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options);
+ return ImageBox.LayoutString(metaKey);
}
- return new Doc;
- }
+ return new Doc();
+ };
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const open = cm.findByDescription("Change Perspective...");
- const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+ const open = cm.findByDescription('Change Perspective...');
+ const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : [];
openItems.push({
- description: "Default Perspective", event: () => {
- this.props.addDocTab(this.props.Document, "close");
- this.props.addDocTab(this.fieldDocToLayout, "add:right");
- }, icon: "image"
+ description: 'Default Perspective',
+ event: () => {
+ this.props.addDocTab(this.props.Document, 'close');
+ this.props.addDocTab(this.fieldDocToLayout, 'add:right');
+ },
+ icon: 'image',
});
- !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
- }
+ !open && cm.addItem({ description: 'Change Perspective...', subitems: openItems, icon: 'external-link-alt' });
+ };
render() {
- const dividerDragger = this.splitPercentage === 0 ? (null) :
- <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
- <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
- </div>;
-
- return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
- <table className="keyValueBox-table">
- <tbody className="keyValueBox-tbody">
- <tr className="keyValueBox-header">
- <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }} ref={this._keyHeader}
- onPointerDown={SetupDrag(this._keyHeader, this.getTemplate)}
- >Key</th>
- <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th>
- </tr>
- {this.createTable}
- {this.newKeyValue}
- </tbody>
- </table>
- {dividerDragger}
- </div>);
+ const dividerDragger =
+ this.splitPercentage === 0 ? null : (
+ <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
+ <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
+ </div>
+ );
+
+ return (
+ <div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
+ <table className="keyValueBox-table">
+ <tbody className="keyValueBox-tbody">
+ <tr className="keyValueBox-header">
+ <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }} ref={this._keyHeader} onPointerDown={SetupDrag(this._keyHeader, this.getFieldView)}>
+ Key
+ </th>
+ <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>
+ Fields
+ </th>
+ </tr>
+ {this.createTable}
+ {this.newKeyValue}
+ </tbody>
+ </table>
+ {dividerDragger}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index cb68c1ac3..c72b5ca9b 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -90,9 +90,11 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
@computed get label() {
return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
}
- @computed get icon() {
- return StrCast(this.dataDoc.icon, 'user') as any;
- }
+ Icon = (color: string) => {
+ const icon = StrCast(this.dataDoc.icon, 'user') as any;
+ const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
+ return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
+ };
@computed get dropdown() {
return BoolCast(this.rootDoc.dropDownOpen);
}
@@ -228,7 +230,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
className={`menuButton ${this.type} ${active}`}
style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {this.Icon(color)}
{!this.label || !FontIconBox.GetShowLabels() ? null : (
<div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
{' '}
@@ -252,16 +254,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const script = ScriptCast(this.rootDoc.script);
- if (!script) {
- return null;
- }
let noviceList: string[] = [];
let text: string | undefined;
let dropdown = true;
let icon: IconProp = 'caret-down';
try {
- if (script.script.originalScript.startsWith('setView')) {
+ if (script?.script.originalScript.startsWith('setView')) {
const selected = SelectionManager.Docs().lastElement();
if (selected) {
if (StrCast(selected.type) === DocumentType.COL) {
@@ -277,7 +276,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
text = 'User Default';
}
noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
- } else if (script.script.originalScript.startsWith('setFont')) {
+ } else if (script?.script.originalScript.startsWith('setFont')) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text'];
@@ -287,11 +286,9 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
// Get items to place into the list
- const list = this.buttonList.map(value => {
- if (Doc.noviceMode && !noviceList.includes(value)) {
- return;
- }
- return (
+ const list = this.buttonList
+ .filter(value => !Doc.noviceMode || noviceList.includes(value))
+ .map(value => (
<div
className="list-item"
key={`${value}`}
@@ -302,8 +299,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
onClick={() => script.script.run({ value }).result}>
{value[0].toUpperCase() + value.slice(1)}
</div>
- );
- });
+ ));
const label =
!this.label || !FontIconBox.GetShowLabels() ? null : (
@@ -384,7 +380,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
onClick={action(() => (this.colorPickerClosed = !this.colorPickerClosed))}
onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {this.Icon(color)}
<div className="colorButton-color" style={{ backgroundColor: curColor }} />
{label}
{/* {dropdownCaret} */}
@@ -436,7 +432,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
} else {
return (
<div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {this.Icon(color)}
{label}
</div>
);
@@ -449,11 +445,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
@computed get defaultButton() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const active: string = StrCast(this.rootDoc.dropDownOpen);
return (
<div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
<div className="menuButton-wrap">
- <FontAwesomeIcon className={`menuButton-icon-${this.type}`} icon={this.icon} color={'black'} size={'sm'} />
+ {this.Icon(color)}
{!this.label || !FontIconBox.GetShowLabels() ? null : (
<div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
{' '}
@@ -485,90 +480,50 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
render() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const label =
- !this.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color, backgroundColor }}>
- {this.label}
- </div>
- );
-
- const menuLabel =
+ const label = (noBackground: boolean = false) =>
!this.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color, backgroundColor: 'transparent' }}>
+ <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}>
{this.label}
</div>
);
-
// TODO:glr Add label of button type
- let button: JSX.Element | null = this.defaultButton;
+ let button: JSX.Element = this.defaultButton;
+ // prettier-ignore
switch (this.type) {
- case ButtonType.TextButton:
- button = (
+ case ButtonType.DropdownList: return this.dropdownListButton;
+ case ButtonType.ColorButton: return this.colorButton;
+ case ButtonType.NumberButton: return this.numberButton;
+ case ButtonType.EditableText: return this.editableText;
+ case ButtonType.DropdownButton: button = this.dropdownButton; break;
+ case ButtonType.ToggleButton: button = this.toggleButton; break;
+ case ButtonType.TextButton: button = (
<div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {this.Icon(color)}
{StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
- {label}
+ {label()}
</div>
);
- // button = <TextButton {...buttonProps}></TextButton>
- break;
- case ButtonType.EditableText:
- button = this.editableText;
- break;
- case ButtonType.NumberButton:
- button = this.numberButton;
- break;
- case ButtonType.DropdownButton:
- button = this.dropdownButton;
- break;
- case ButtonType.DropdownList:
- button = this.dropdownListButton;
- break;
- case ButtonType.ColorButton:
- button = this.colorButton;
- break;
- case ButtonType.ToolButton:
- button = (
- <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {label}
- </div>
- );
- break;
- case ButtonType.ToggleButton:
- button = this.toggleButton;
- // button = <ToggleButton {...buttonProps}></ToggleButton>
break;
case ButtonType.ClickButton:
- button = (
- <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ color, backgroundColor, opacity: 1 }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {label}
+ case ButtonType.ToolButton: button = (
+ <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
+ {this.Icon(color)}
+ {label()}
</div>
);
break;
- case ButtonType.MenuButton:
- const trailsIcon = <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
- button = (
+ case ButtonType.MenuButton: button = (
<div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
- {this.icon === 'pres-trail' ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
- {menuLabel}
+ {this.Icon(color)}
+ {label(true)}
<FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
</div>
);
break;
- default:
- break;
}
- const retval =
- !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
- button
- ) : button !== null ? (
- <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
- ) : null;
- return retval;
+ return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>;
}
}
@@ -697,7 +652,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return RichTextMenu.Instance.fontSize.replace('px', '');
+ return RichTextMenu.Instance?.fontSize.replace('px', '');
}
if (typeof size === 'number') size = size.toString();
if (size && Number(size).toString() === size) size += 'px';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f61533619..81ac45521 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -44,6 +45,7 @@ import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
+import { DocumentViewInternal } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
import { DashDocCommentView } from './DashDocCommentView';
@@ -61,25 +63,14 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
-import { text } from 'body-parser';
-import { CollectionTreeView } from '../../collections/CollectionTreeView';
-import { DocumentViewInternal } from '../DocumentView';
const translateGoogleApi = require('translate-google-api');
-export interface FormattedTextBoxProps {
- makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
- yPadding?: number;
- noSidebar?: boolean;
- dontScale?: boolean;
- dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
-}
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -1843,18 +1834,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
- const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
- return styleFromString?.height === '0px' ? null : (
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox-cont"
onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
- transform: this.props.dontScale ? undefined : `scale(${scale})`,
- transformOrigin: this.props.dontScale ? undefined : 'top left',
- width: this.props.dontScale ? undefined : `${100 / scale}%`,
- height: this.props.dontScale ? undefined : `${100 / scale}%`,
+ ...(this.props.dontScale
+ ? {}
+ : {
+ transform: `scale(${scale})`,
+ transformOrigin: 'top left',
+ width: `${100 / scale}%`,
+ height: `${100 / scale}%`,
+ }),
+ transition: 'inherit',
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
- ...styleFromString,
+ color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+ fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
+ fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
+ fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+ ...styleFromLayoutString,
}}>
<div
className={`formattedTextBox-cont`}
@@ -1862,11 +1862,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
style={{
overflow: this.autoHeight ? 'hidden' : undefined,
height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
- background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
- color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
- fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
- fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
pointerEvents: interactive ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 2a77210ae..0cbe60c0c 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -16,7 +16,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
@@ -29,7 +29,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
private _linkToRef = React.createRef<HTMLInputElement>();
@observable public view?: EditorView;
- public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
+ public editorProps: FieldViewProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 05e09361b..eb40089ec 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -358,20 +358,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
};
+ static pinDataTypes(target: Doc) {
+ const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
+ const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
+ const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
+ const clippable = [DocumentType.COMPARISON].includes(target.type as any);
+ return { scrollable, pannable, temporal, clippable };
+ }
// navigates to the bestTarget document by making sure it is on screen,
// then it applies the view specs stored in activeItem to
@action
static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
- if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
- bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
- bestTarget._scrollTop = activeItem.presPinViewScroll;
- } else if (bestTarget.type === DocumentType.COMPARISON) {
- bestTarget._clipWidth = activeItem.presPinClipWidth;
- } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
- bestTarget._currentTimecode = activeItem.presStartTime;
- } else {
+ bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
+ const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget);
+ if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
+ if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
+ if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+ if (pannable) {
const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
- bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
if (contentBounds) {
bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2;
bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2;
@@ -388,6 +392,43 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
}
+ /// copies values from the targetDoc (which is the prototype of the pinDoc) to
+ /// reserved fields on the pinDoc so that those values can be restored to the
+ /// target doc when navigating to it.
+ @action
+ static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) {
+ if (pinProps?.pinWithView) {
+ // If pinWithView option set then update scale and x / y props of slide
+ const bounds = pinProps.pinWithView.bounds;
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = bounds.left + bounds.width / 2;
+ pinDoc.presPinViewY = bounds.top + bounds.height / 2;
+ pinDoc.presPinViewScale = pinProps.pinWithView.scale;
+ pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+ }
+ if (pinProps?.pinDocView) {
+ const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc);
+ pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable;
+
+ if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
+ else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
+ else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
+ else if (pannable) {
+ const panX = NumCast(pinDoc._panX);
+ const panY = NumCast(pinDoc._panY);
+ const pw = NumCast(pinProps.panelWidth);
+ const ph = NumCast(pinProps.panelHeight);
+ const ps = NumCast(pinDoc._viewScale);
+ if (pw && ph && ps) {
+ pinDoc.contentBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
+ }
+ pinDoc.presPinViewX = panX;
+ pinDoc.presPinViewY = panY;
+ pinDoc.presPinViewScale = ps;
+ }
+ }
+ }
+
/**
* This method makes sure that cursor navigates to the element that
* has the option open and last in the group.
@@ -1540,26 +1581,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
- @computed get effectDirection(): string {
- let effect = '';
+ @computed get effectDirection() {
+ // prettier-ignore
switch (this.activeItem.presEffectDirection) {
- case 'left':
- effect = 'Enter from left';
- break;
- case 'right':
- effect = 'Enter from right';
- break;
- case 'top':
- effect = 'Enter from top';
- break;
- case 'bottom':
- effect = 'Enter from bottom';
- break;
- default:
- effect = 'Enter from center';
- break;
+ case 'left': return 'Enter from left';
+ case 'right': return 'Enter from right';
+ case 'top': return'Enter from top';
+ case 'bottom': return 'Enter from bottom';
}
- return effect;
+ return 'Enter from center';
}
@undoBatch
@@ -1582,179 +1612,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
};
- @computed get presPinViewOptionsDropdown() {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />;
- return (
- <>
- {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
- <div className="ribbon-doubleButton">
- <Tooltip
- title={
- <>
- <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div>
- </>
- }>
- <div
- className="ribbon-toggle"
- style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }}
- onClick={() => {
- activeItem.presPinView = !activeItem.presPinView;
- targetDoc.presPinView = activeItem.presPinView;
- if (activeItem.presPinView) {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinView = true;
- activeItem.presPinViewScroll = scroll;
- } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
- activeItem.presStartTime = targetDoc._currentTimecode;
- activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
- } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinView = true;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeItem.presPinClipWidth = width;
- activeItem.presPinView = true;
- }
- }
- }}>
- {presPinWithViewIcon}
- </div>
- </Tooltip>
- {activeItem.presPinView ? (
- <Tooltip
- title={
- <>
- <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div>
- </>
- }>
- <div
- className="ribbon-button"
- onClick={() => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
- activeItem.presStartTime = targetDoc._currentTimecode;
- activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
- }
- }}>
- Update
- </div>
- </Tooltip>
- ) : null}
- </div>
- </>
- );
- }
-
- @computed get panOptionsDropdown() {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- return (
- <>
- {this.panable ? (
- <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Pan X</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input
- className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number"
- value={NumCast(activeItem.presPinViewX)}
- onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- const val = e.target.value;
- activeItem.presPinViewX = Number(val);
- })}
- />
- </div>
- </div>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Pan Y</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input
- className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number"
- value={NumCast(activeItem.presPinViewY)}
- onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- const val = e.target.value;
- activeItem.presPinViewY = Number(val);
- })}
- />
- </div>
- </div>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Scale</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input
- className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number"
- value={NumCast(activeItem.presPinViewScale)}
- onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- const val = e.target.value;
- activeItem.presPinViewScale = Number(val);
- })}
- />
- </div>
- </div>
- </div>
- ) : null}
- </>
- );
- }
-
- @computed get scrollOptionsDropdown() {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- return (
- <>
- {this.scrollable ? (
- <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Scroll</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input
- className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number"
- value={NumCast(activeItem.presPinViewScroll)}
- onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- const val = e.target.value;
- activeItem.presPinViewScroll = Number(val);
- })}
- />
- </div>
- </div>
- </div>
- ) : null}
- </>
- );
- }
-
@computed get mediaStopSlides() {
const activeItem: Doc = this.activeItem;
const list = this.childDocs.map((doc, i) => {
@@ -2102,42 +1959,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
createTemplate = (layout: string, input?: string) => {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- let x = 0;
- let y = 0;
- if (activeItem && targetDoc) {
- x = NumCast(targetDoc.x);
- y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
- }
- let doc = undefined;
- const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
- const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
- const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
- const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
- const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
- const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
- const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+ const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0;
+ const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0;
+ const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
+ const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
+ const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
+ const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
+ const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
+ const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
+ const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+ // prettier-ignore
switch (layout) {
- case 'blank':
- doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y });
- break;
- case 'title':
- doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
- break;
- case 'header':
- doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
- break;
- case 'content':
- doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
- break;
- case 'twoColumns':
- doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
- break;
- default:
- break;
+ 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, _fitContentsToBox: true, x, y });
+ case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+ case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+ case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x, y })
}
- return doc;
};
// Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2201,42 +2039,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/**
* Returns the collection type as a string for headers
*/
- @computed get stringType(): string {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- let type: string = '';
- if (activeItem) {
- switch (targetDoc.type) {
- case DocumentType.PDF:
- type = 'PDF';
- break;
- case DocumentType.RTF:
- type = 'Text node';
- break;
- case DocumentType.COL:
- type = 'Collection';
- break;
- case DocumentType.AUDIO:
- type = 'Audio';
- break;
- case DocumentType.VID:
- type = 'Video';
- break;
- case DocumentType.IMG:
- type = 'Image';
- break;
- case DocumentType.WEB:
- type = 'Web page';
- break;
- case DocumentType.MAP:
- type = 'Map';
- break;
- default:
- type = 'Other node';
- break;
+ @computed get stringType() {
+ if (this.activeItem) {
+ // prettier-ignore
+ switch (this.targetDoc.type) {
+ case DocumentType.PDF: return 'PDF';
+ case DocumentType.RTF: return 'Text node';
+ case DocumentType.COL: return 'Collection';
+ case DocumentType.AUDIO: return 'Audio';
+ case DocumentType.VID: return 'Video';
+ case DocumentType.IMG: return 'Image';
+ case DocumentType.WEB: return 'Web page';
+ case DocumentType.MAP: return 'Map';
+ default: return 'Other node';
}
}
- return type;
+ return '';
}
@observable private openActiveColorPicker: boolean = false;
@@ -2845,10 +2663,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
this.updateList(activeItem.frameList);
break;
- case DocumentType.COL:
- break;
- default:
- break;
}
};
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 3af8cad9a..91196ca21 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -256,10 +256,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerMove = (e: PointerEvent) => {
const slide = this._itemRef.current!;
- let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
- for (const doc of DragManager.docsBeingDragged) {
- if (!doc.presentationTargetDoc) dragIsPresItem = false;
- }
+ const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentationTargetDoc);
if (slide && dragIsPresItem) {
const rect = slide.getBoundingClientRect();
const y = e.clientY - rect.top; //y position within the element.
@@ -501,7 +498,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isSelected ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
}}>
- <div className="presItem-name" style={{ maxWidth: showMore ? toolbarWidth - 195 : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <div
+ className="presItem-name"
+ style={{
+ pointerEvents: isSelected ? undefined : 'none',
+ maxWidth: showMore ? toolbarWidth - 195 : toolbarWidth - 105,
+ cursor: isSelected ? 'text' : 'grab',
+ }}>
<div>{`${this.indexInPres + 1}. `}</div>
<EditableView ref={this._titleRef} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
</div>
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 3fc0a237e..cbcfed06f 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,5 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
+import { MdBugReport } from 'react-icons/md';
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -18,6 +19,7 @@ import { DashboardView } from '../DashboardView';
import { Borders, Colors } from '../global/globalEnums';
import { MainView } from '../MainView';
import './TopBar.scss';
+import { ReportManager } from '../../util/ReportManager';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
@@ -31,6 +33,7 @@ export class TopBar extends React.Component {
DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
});
};
+
render() {
const activeDashboard = Doc.ActiveDashboard;
return (
@@ -92,6 +95,9 @@ export class TopBar extends React.Component {
{GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'view original'}
</div>
) : null}
+ <div className="topbar-button-icon" onClick={() => ReportManager.Instance.open()}>
+ <MdBugReport />
+ </div>
<div className="topbar-button-icon" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')}>
<FontAwesomeIcon icon="question-circle" />
</div>