aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx7
-rw-r--r--src/client/views/nodes/DocumentView.tsx5
-rw-r--r--src/client/views/nodes/FieldView.tsx5
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx24
-rw-r--r--src/client/views/nodes/IconTagBox.scss26
-rw-r--r--src/client/views/nodes/IconTagBox.tsx92
-rw-r--r--src/client/views/nodes/ImageBox.tsx17
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx6
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts4
10 files changed, 165 insertions, 23 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 1eae163df..39a2e3a31 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
+import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -303,6 +303,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<DocumentView
// eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
+ fitWidth={undefined}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
ignoreUsePath={layoutString ? true : undefined}
renderDepth={this.props.renderDepth + 1}
LayoutTemplateString={layoutString}
@@ -310,8 +313,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
containerViewPath={this.DocumentView?.().docViewPath}
moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
- NativeWidth={this.layoutWidth}
- NativeHeight={this.layoutHeight}
isContentActive={emptyFunction}
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4c357cf45..758e70508 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
hideCaptions?: boolean;
contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
dontCenter?: 'x' | 'y' | 'xy';
+ showTags?: boolean;
childHideDecorationTitle?: boolean;
childHideResizeHandles?: boolean;
childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
@@ -1126,6 +1127,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
@observable public TagPanelHeight = 0;
+ @computed get showTags() {
+ return this.Document._layout_showTags || this._props.showTags;
+ }
+
@computed private get shouldNotScale() {
return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.();
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index dd71fd946..683edba16 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable react/no-unused-prop-types */
-/* eslint-disable react/require-default-props */
import { Property } from 'csstype';
import { computed } from 'mobx';
import { observer } from 'mobx-react';
@@ -21,6 +19,7 @@ export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>
// eslint-disable-next-line no-use-before-define
export type StyleProviderFuncType = (
doc: Opt<Doc>,
+ // eslint-disable-next-line no-use-before-define
props: Opt<FieldViewProps>,
property: string
) =>
@@ -65,6 +64,7 @@ export interface FieldViewSharedProps {
containerViewPath?: () => DocumentView[];
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
isGroupActive?: () => string | undefined; // is this document part of a group that is active
+ // eslint-disable-next-line no-use-before-define
setContentViewBox?: (view: ViewBoxInterface<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
PanelWidth: () => number;
PanelHeight: () => number;
@@ -82,6 +82,7 @@ export interface FieldViewSharedProps {
// eslint-disable-next-line no-use-before-define
onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
fitWidth?: (doc: Doc) => boolean | undefined;
+ dontCenter?: 'x' | 'y' | 'xy' | undefined;
searchFilterDocs: () => Doc[];
showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 7a09ad9e2..aa40b14aa 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -192,7 +192,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
} else {
text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
// text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- getStyle = (val: string) => ({ fontFamily: val });
+ if (this.Document.title === 'Font') getStyle = (val: string) => ({ fontFamily: val }); // bcz: major hack to style the font dropdown items --- needs to become part of the dropdown's metadata
}
// Get items to place into the list
@@ -265,27 +265,33 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const toggleStatus = script?.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result as boolean;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
+ const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string;
const items = DocListCast(this.dataDoc.data);
- const multiDoc = this.Document;
+ const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType));
return (
<MultiToggle
tooltip={`Toggle ${tooltip}`}
type={Type.PRIM}
color={color}
- onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: multiDoc, value: undefined, _readOnly_: false }))}
+ background={background === SnappingManager.userBackgroundColor ? undefined : background}
+ multiSelect={true}
+ onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
isToggle={script ? true : false}
toggleStatus={toggleStatus}
- //background={SnappingManager.userBackgroundColor}
label={this.label}
- items={DocListCast(this.dataDoc.data).map(item => ({
+ items={items.map(item => ({
icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
tooltip: StrCast(item.toolTip),
val: StrCast(item.toolType),
}))}
- selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType ?? StrCast(multiDoc.toolType))}
- setSelectedVal={(val: string | number) => {
- const itemDoc = items.find(item => item.toolType === val);
- itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false });
+ selectedItems={selectedItems}
+ onSelectionChange={(val: (string | number) | (string | number)[], added: boolean) => {
+ // note: the multitoggle is telling us whether the selection was toggled on or off, but we ignore this since we know the state of all the buttons
+ // and control it through the selectedItems prop. Therefore, the callback script will have to re-determine the toggle information.
+ // it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define
+ // arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript)
+ const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val));
+ itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, itemDoc, _readOnly_: false }));
}}
/>
);
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
new file mode 100644
index 000000000..90cc06092
--- /dev/null
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -0,0 +1,26 @@
+@import '../global/globalCssVariables.module.scss';
+
+.card-button-container {
+ display: flex;
+ position: relative;
+ pointer-events: none;
+ background-color: rgb(218, 218, 218);
+ border-radius: 50px;
+ align-items: center;
+ gap: 5px;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+
+ button {
+ pointer-events: auto;
+ width: 20px;
+ height: 20px;
+ margin: auto;
+ padding: 0;
+ border-radius: 50%;
+ background-color: $dark-gray;
+ background-color: transparent;
+ }
+}
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
new file mode 100644
index 000000000..ddabd61e1
--- /dev/null
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -0,0 +1,92 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
+import { computed, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
+import { undoable } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { TagItem } from '../TagsView';
+import { DocumentView } from './DocumentView';
+import './IconTagBox.scss';
+
+export interface IconTagProps {
+ Views: DocumentView[];
+ IsEditing: boolean;
+}
+
+/**
+ * Renders the icon tags that rest under the document. The icons rendered are determined by the values of
+ * each icon in the userdoc.
+ */
+@observer
+export class IconTagBox extends ObservableReactComponent<IconTagProps> {
+ constructor(props: IconTagProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @computed get View() { return this._props.Views.lastElement(); } // prettier-ignore
+ @computed get currentScale() { return this.View?.screenToLocalScale(); } // prettier-ignore
+
+ /**
+ * Sets or removes the specified tag
+ * @param tag tag name (should begin with '#')
+ * @param state flag to add or remove the metadata
+ */
+ setIconTag = undoable((tag: string, state: boolean) => {
+ this._props.Views.forEach(view => {
+ state && TagItem.addTagToDoc(view.dataDoc, tag);
+ !state && TagItem.removeTagFromDoc(view.dataDoc, tag);
+ });
+ }, 'toggle card tag');
+
+ /**
+ * Returns a renderable version of the button Doc that is colorized to indicate
+ * whether the doc has the associated tag set on it or not.
+ * @param doc doc to test
+ * @param key metadata icon button
+ * @returns an icon for the metdata button
+ */
+ getButtonIcon = (doc: Doc, key: Doc): JSX.Element => {
+ const icon = StrCast(key.icon) as IconProp;
+ const tag = StrCast(key.toolType);
+ const isActive = TagItem.docHasTag(doc, tag);
+ const color = isActive ? '#4476f7' : '#323232'; // TODO should use theme colors
+
+ return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
+ };
+
+ /**
+ * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+ */
+ render() {
+ const buttons = Doc.MyFilterHotKeys
+ .map(key => ({ key, tag: StrCast(key.toolType) }))
+ .filter(({ tag }) => this._props.IsEditing || TagItem.docHasTag(this.View.Document, tag) || (DocumentView.Selected().length === 1 && this.View.IsSelected))
+ .map(({ key, tag }) => (
+ <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove this card from the {tag} group</div>}>
+ <button
+ type="button"
+ onPointerDown={e =>
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => {
+ this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag));
+ clickEv.stopPropagation();
+ })
+ }>
+ {this.getButtonIcon(this.View.Document, key)}
+ </button>
+ </Tooltip>
+ )); // prettier-ignore
+
+ return !buttons.length ? null : (
+ <div className="card-button-container" style={{ fontSize: '50px' }}>
+ {buttons}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d0a7fc6ac..aa4376bb2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -11,7 +11,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
@@ -188,8 +188,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@undoBatch
setNativeSize = action(() => {
+ const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
- const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
+ const nw = nscale / oldnativeWidth;
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
this.dataDoc._freeform_panX = nw * NumCast(this.dataDoc._freeform_panX);
@@ -198,6 +199,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.dataDoc._freeform_panX_min = this.dataDoc._freeform_panX_min ? nw * NumCast(this.dataDoc._freeform_panX_min) : undefined;
this.dataDoc._freeform_panY_max = this.dataDoc._freeform_panY_max ? nw * NumCast(this.dataDoc._freeform_panY_max) : undefined;
this.dataDoc._freeform_panY_min = this.dataDoc._freeform_panY_min ? nw * NumCast(this.dataDoc._freeform_panY_min) : undefined;
+ const newnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
+ DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => {
+ doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth;
+ doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth;
+ if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) {
+ doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth;
+ doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth;
+ }
+ });
});
@undoBatch
rotate = action(() => {
@@ -358,7 +368,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
TraceMobx();
const backColor = DashColor((this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string) ?? Colors.WHITE);
- const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
+ // allow use case where the image is transparent when the alpha value is to smallest possible value from UI (alpha = 1 out of 255)
+ const backAlpha = backColor.alpha() < 0.015 && backColor.alpha() > 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 95e344004..3daacc9bb 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -84,7 +84,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
const onDelegate = rawvalue.startsWith('=');
rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
- rawvalue = type ? rawvalue.substring(2) : rawvalue;
+ rawvalue = type ? rawvalue.substring(2) : rawvalue.replace(/^:/, '');
rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)');
const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(+rawvalue) ? rawvalue : '`' + rawvalue + '`';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a88bd8920..1ccc6e502 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
ele.append(contents);
}
this._selectionHTML = ele?.innerHTML;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
/* empty */
}
@@ -561,7 +562,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const draggedDoc = dragData.droppedDocuments.lastElement();
let added: Opt<boolean>;
const dropAction = dragData.dropAction || dragData.userDropAction;
- if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
+ if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) {
// replace text contents when dragging with Alt
if (de.altKey) {
const fieldKey = Doc.LayoutFieldKey(draggedDoc);
@@ -2035,7 +2036,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
style={{
cursor: this._props.isContentActive() ? 'text' : undefined,
height: this._props.height ? 'max-content' : undefined,
- overflow: this.layout_autoHeight ? 'hidden' : undefined,
pointerEvents: Doc.ActiveTool === InkTool.None && !SnappingManager.ExploreMode ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
@@ -2054,7 +2054,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}}
style={{
width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
- overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
+ overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
onDrop={this.ondrop}>
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e0d6c7c05..f58434906 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -404,9 +404,9 @@ export class RichTextRules {
if (!tags.includes(tag)) {
tags.push(tag);
this.Document[DocData].tags = new List<string>(tags);
- this.Document[DocData].showTags = true;
+ this.Document._layout_showTags = true;
}
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: tag.startsWith('@') ? tag.replace(/^@/, '') : '#' + tag });
return state.tr
.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))
.replaceSelectionWith(fieldView, true)