aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts6
-rw-r--r--src/client/documents/DocUtils.ts14
-rw-r--r--src/client/documents/DocumentTypes.ts19
-rw-r--r--src/client/documents/Documents.ts71
-rw-r--r--src/client/util/CalendarManager.tsx2
-rw-r--r--src/client/util/CurrentUserUtils.ts104
-rw-r--r--src/client/util/DictationManager.ts39
-rw-r--r--src/client/util/DropConverter.ts4
-rw-r--r--src/client/util/GroupManager.tsx14
-rw-r--r--src/client/util/Scripting.ts1
-rw-r--r--src/client/util/SettingsManager.tsx28
-rw-r--r--src/client/util/SharingManager.tsx4
-rw-r--r--src/client/util/SnappingManager.ts54
-rw-r--r--src/client/views/AntimodeMenu.tsx2
-rw-r--r--src/client/views/DocComponent.tsx52
-rw-r--r--src/client/views/DocViewUtils.ts3
-rw-r--r--src/client/views/DocumentButtonBar.tsx2
-rw-r--r--src/client/views/DocumentDecorations.scss117
-rw-r--r--src/client/views/DocumentDecorations.tsx58
-rw-r--r--src/client/views/EditableView.tsx22
-rw-r--r--src/client/views/GlobalKeyHandler.ts9
-rw-r--r--src/client/views/InkTranscription.tsx2
-rw-r--r--src/client/views/MainView.tsx22
-rw-r--r--src/client/views/PropertiesButtons.tsx4
-rw-r--r--src/client/views/PropertiesView.tsx12
-rw-r--r--src/client/views/SidebarAnnos.scss10
-rw-r--r--src/client/views/SidebarAnnos.tsx7
-rw-r--r--src/client/views/StyleProvider.scss19
-rw-r--r--src/client/views/StyleProvider.tsx67
-rw-r--r--src/client/views/TagsView.scss12
-rw-r--r--src/client/views/TagsView.tsx40
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx3
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss6
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx15
-rw-r--r--src/client/views/collections/CollectionMenu.tsx11
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx6
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx15
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx12
-rw-r--r--src/client/views/collections/CollectionSubView.tsx32
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx16
-rw-r--r--src/client/views/collections/CollectionView.tsx3
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.tsx58
-rw-r--r--src/client/views/collections/TabDocView.scss12
-rw-r--r--src/client/views/collections/TabDocView.tsx27
-rw-r--r--src/client/views/collections/TreeView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx29
-rw-r--r--src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx20
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx8
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx8
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx112
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx103
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaCellField.tsx3
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx14
-rw-r--r--src/client/views/global/globalCssVariables.module.scss2
-rw-r--r--src/client/views/global/globalScripts.ts13
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx5
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx131
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx103
-rw-r--r--src/client/views/nodes/DocumentView.scss10
-rw-r--r--src/client/views/nodes/DocumentView.tsx177
-rw-r--r--src/client/views/nodes/FieldView.tsx13
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx38
-rw-r--r--src/client/views/nodes/IconTagBox.scss1
-rw-r--r--src/client/views/nodes/IconTagBox.tsx69
-rw-r--r--src/client/views/nodes/ImageBox.scss8
-rw-r--r--src/client/views/nodes/ImageBox.tsx134
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx28
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx46
-rw-r--r--src/client/views/nodes/PDFBox.scss6
-rw-r--r--src/client/views/nodes/PDFBox.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx142
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts3
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx9
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx2
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx2
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss4
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx46
-rw-r--r--src/client/views/pdf/PDFViewer.tsx3
-rw-r--r--src/client/views/search/FaceRecognitionHandler.tsx4
-rw-r--r--src/client/views/smartdraw/DrawingFillHandler.tsx99
-rw-r--r--src/client/views/topbar/TopBar.tsx23
-rw-r--r--src/fields/Doc.ts75
-rw-r--r--src/fields/Types.ts8
-rw-r--r--src/fields/util.ts25
-rw-r--r--src/server/ApiManagers/FireflyManager.ts1
-rw-r--r--src/server/ApiManagers/UploadManager.ts7
-rw-r--r--src/server/DashUploadUtils.ts14
93 files changed, 1347 insertions, 1286 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 29b6ab989..9cb47995c 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,5 +1,6 @@
import { ChatCompletionMessageParam, Image } from 'openai/resources';
import { openai } from './setup';
+import { imageUrlToBase64 } from '../../../ClientUtils';
export enum GPTDocCommand {
AssignTags = 1,
@@ -270,7 +271,6 @@ const gptImageLabel = async (src: string, prompt: string): Promise<string> => {
],
});
if (response.choices[0].message.content) {
- console.log(response.choices[0].message.content);
return response.choices[0].message.content;
}
return 'Missing labels';
@@ -310,7 +310,9 @@ const gptHandwriting = async (src: string): Promise<string> => {
}
};
-const gptDescribeImage = async (image: string): Promise<string> => {
+const gptDescribeImage = async (userPrompt: string, url: string): Promise<string> => {
+ if (userPrompt) return userPrompt;
+ const image = imageUrlToBase64(url);
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 0bb6c0ce9..f5eee84aa 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -283,10 +283,10 @@ export namespace DocUtils {
}
return val1 === val2;
};
- Object.entries(reqdOpts).forEach(pair => {
- const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
- if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
- targetDoc[pair[0]] = pair[1];
+ Object.entries(reqdOpts).forEach(([key, val]) => {
+ const targetDoc = key.startsWith('_') ? doc : Doc.GetProto(doc as Doc);
+ if (!Object.getOwnPropertyNames(targetDoc).includes(key.replace(/^_/, '')) || !compareValues(val, targetDoc[key])) {
+ targetDoc[key] = val as FieldType;
}
});
items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
@@ -578,7 +578,7 @@ export namespace DocUtils {
Docs.Create.FreeformDocument([], {
_width: NumCast(layoutDoc._width) + 10,
_height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10),
- _isLightbox: true,
+ isLightbox: true,
_layout_fitWidth: true,
title: StrCast(doc.title) + ' [Portal]',
}),
@@ -726,12 +726,12 @@ export namespace DocUtils {
: {
_width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
_height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
- _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
_layout_fitWidth: true,
_layout_autoHeight: true,
backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
borderColor: Doc.UserDoc().borderColor as string,
borderWidth: Doc.UserDoc().borderWidth as number,
+ text_centered: BoolCast(Doc.UserDoc().textCentered),
text_fitBox: BoolCast(Doc.UserDoc().fitBox),
text_align: StrCast(Doc.UserDoc().textAlign),
text_fontColor: StrCast(Doc.UserDoc().fontColor),
@@ -752,12 +752,12 @@ export namespace DocUtils {
: {
_width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
_height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
- _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
_layout_fitWidth: true,
_layout_autoHeight: true,
backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
borderColor: Doc.UserDoc().borderColor as string,
borderWidth: Doc.UserDoc().borderWidth as number,
+ text_centered: BoolCast(Doc.UserDoc().textCentered),
text_fitBox: BoolCast(Doc.UserDoc().fitBox),
text_align: StrCast(Doc.UserDoc().textAlign),
text_fontColor: StrCast(Doc.UserDoc().fontColor),
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 03626107f..dd0985182 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -47,25 +47,30 @@ export enum DocumentType {
JOURNAL = 'journal', // AARAV ADD
}
export enum CollectionViewType {
- Invalid = 'invalid',
+ // general collections
Freeform = 'freeform',
- Calendar = 'calendar',
Card = 'card',
Carousel = 'carousel',
Carousel3D = '3D Carousel',
- Docking = 'docking',
Grid = 'grid',
- Linear = 'linear',
- Map = 'map',
Masonry = 'masonry',
Multicolumn = 'multicolumn',
Multirow = 'multirow',
NoteTaking = 'notetaking',
- Pile = 'pileup',
Pivot = 'pivot',
Schema = 'schema',
Stacking = 'stacking',
- StackedTimeline = 'stacked timeline',
Time = 'time',
Tree = 'tree',
+ // under development
+ Calendar = 'calendar',
+ // special collections
+ Docking = 'docking',
+ Pile = 'pileup',
+ StackedTimeline = 'stacked timeline',
+ Linear = 'linear',
+ Invalid = 'invalid',
}
+
+export const specialCollectionTypes = [CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear, CollectionViewType.Invalid];
+export const standardViewTypes = Object.values(CollectionViewType).filter(key => !specialCollectionTypes.includes(key));
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 668725d2b..0e3c614fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -4,7 +4,7 @@ import { basename } from 'path';
import { ClientUtils, OmitKeys } from '../../ClientUtils';
import { DateField } from '../../fields/DateField';
import { CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc';
-import { Initializing } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
import { HtmlField } from '../../fields/HtmlField';
import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
@@ -18,7 +18,6 @@ import { PointData } from '../../pen-gestures/GestureTypes';
import { DocServer } from '../DocServer';
import { dropActionType } from '../util/DropActionTypes';
import { CollectionViewType, DocumentType } from './DocumentTypes';
-import { Id } from '../../fields/FieldSymbols';
class EmptyBox {
public static LayoutString() {
@@ -43,7 +42,6 @@ export class FInfo {
readOnly: boolean = false;
fieldType?: FInfoFieldType;
values?: FieldType[];
- onLayout?: boolean;
filterable?: boolean = true; // can be used as a Filter in FilterPanel
// format?: string; // format to display values (e.g, decimal places, $, etc)
// parse?: ScriptField; // parse a value from a string
@@ -156,12 +154,16 @@ type DROPt = DAInfo | dropActionType;
type DATEt = DateInfo | number;
type DTYPEt = DTypeInfo | string;
export class DocumentOptions {
+ [key: string]: FInfo | FieldType | undefined;
// coordinate and dimensions depending on view
x?: NUMt = new NumInfo('horizontal coordinate in freeform view', false);
y?: NUMt = new NumInfo('vertical coordinate in freeform view', false);
z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]);
+ zIndex?: NUMt = new NumInfo('stacking index of documents in freeform view (higher numbers are towards the top');
overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false);
overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', false);
+ embedContainer?: DOCt = new DocInfo('document that displays (contains) this document', false);
+
text?: RTFt = new RtfInfo('plain or rich text', true);
text_html?: STRt = new StrInfo('plain text or html', true);
_dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
@@ -215,11 +217,11 @@ export class DocumentOptions {
author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
author_date?: DATEt = new DateInfo('date the document was created', true);
annotationOn?: DOCt = new DocInfo('document annotated by this document', false);
- _embedContainer?: DOCt = new DocInfo('document that displays (contains) this document', false);
rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)');
color?: STRt = new StrInfo('foreground color data doc', false);
hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
backgroundColor?: STRt = new StrInfo('background color for data doc', false);
+ _override_backgroundColor?: BOOLt = new BoolInfo("whether the layout template overrides the data doc's background color");
opacity?: NUMt = new NumInfo('document opacity', false);
viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false);
dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
@@ -281,7 +283,6 @@ export class DocumentOptions {
_layout_autoHeightMargins?: NUMt = new NumInfo('Margin heights to be added to the computed auto height of a Doc');
_layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
_layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video) value is in seconds', false);
- _layout_centered?: BOOLt = new BoolInfo('whether text should be vertically centered in Doc');
_layout_fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
_layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
_layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button');
@@ -307,6 +308,7 @@ export class DocumentOptions {
text_fontSize?: string;
text_fontFamily?: string;
text_fontWeight?: string;
+ text_centered?: BOOLt = new BoolInfo('whether text should be vertically centered in Doc');
text_fitBox?: BOOLt = new BoolInfo("whether text box should be scaled to fit it's containing render box");
text_align?: STRt = new StrInfo('horizontal text alignment default', undefined, undefined, ['left', 'center', 'right']);
title_align?: STRt = new StrInfo('horizontal title alignment in label box', undefined, undefined, ['left', 'center', 'right']);
@@ -366,7 +368,7 @@ export class DocumentOptions {
isGroup?: BOOLt = new BoolInfo('should collection use a grouping UI behavior');
isFolder?: BOOLt = new BoolInfo('is document a tree view folder');
_isTimelineLabel?: BOOLt = new BoolInfo('is document a timeline label');
- _isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
+ isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
cloneOnCopy?: BOOLt = new BoolInfo('if this Doc is a field of another Doc, then it should be copied when the other Doc is copied');
mapPin?: DOCt = new DocInfo('pin associated with a config anchor', false);
@@ -686,10 +688,11 @@ export namespace Docs {
layout: layout.view?.LayoutString(layout.dataField),
};
Object.entries(options)
- .filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@'))
- .forEach(pair => {
- if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) {
- (options as { [key: string]: unknown })[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1));
+ .filter(([, val]) => (val as string)?.startsWith?.('@'))
+ .map(([key, val]) => [key, val as string])
+ .forEach(([key, val]) => {
+ if (!existing || ScriptCast(existing[key])?.script.originalScript !== val.substring(1)) {
+ options[key] = ComputedField.MakeFunction(val.substring(1));
}
});
return Doc.assign(existing ?? new Doc(prototypeId, true), OmitKeys(options, Object.keys(existing ?? {})).omit as { [key: string]: FieldType }, undefined, true);
@@ -722,7 +725,7 @@ export namespace Docs {
*/
function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) {
const placeholderDoc = placeholderDocIn;
- const viewKeys = ['x', 'y', 'isSystem']; // keys that should be addded to the view document even though they don't begin with an "_"
+ const viewKeys = ['x', 'y', 'isSystem', 'overlayX', 'overlayY', 'zIndex', 'embedContainer']; // keys that should be addded to the view document even though they don't begin with an "_"
const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_') as { omit: { [key: string]: FieldType | undefined }; extract: { [key: string]: FieldType | undefined } };
// dataProps.acl_Override = SharingPermissions.Unset;
@@ -837,7 +840,7 @@ export namespace Docs {
return TextDocument(RichTextField.textToRtf(text, img?.[Id]), {
title, //
_layout_autoHeight: true,
- _layout_centered: true,
+ text_centered: true,
text_align: 'center',
_layout_fitWidth: true,
...opts,
@@ -947,9 +950,9 @@ export namespace Docs {
return InstanceFromProto(
Prototypes.get(DocumentType.JOURNAL),
- "",
+ '',
{
- title: "",
+ title: '',
...options,
},
undefined,
@@ -979,26 +982,22 @@ export namespace Docs {
export function InkDocument(points: PointData[], options: DocumentOptions = {}, strokeWidth: number, color: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, isInkMask: boolean) {
const ink = InstanceFromProto(Prototypes.get(DocumentType.INK), '', { title: 'ink', ...options });
- const I = Doc.GetProto(ink);
- // I.layout_hideOpenButton = true; // don't show open full screen button when selected
- I.color = color;
- I.fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined;
- I.stroke = new InkField(points);
- I.stroke_width = strokeWidth;
- I.stroke_bezier = strokeBezier;
- I.stroke_startMarker = arrowStart;
- I.stroke_endMarker = arrowEnd;
- I.stroke_dash = dash;
- I.stroke_isInkMask = isInkMask;
- I.text_align = 'center';
- I.rotation = 0;
- I.width_min = 1;
- I.height_min = 1;
- I.defaultDoubleClick = 'ignore';
- I.author_date = new DateField();
- I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
- // I.acl_Override = SharingPermissions.Unset;
- I[Initializing] = false;
+ ink.$color = color;
+ ink.$fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined;
+ ink.$stroke = new InkField(points);
+ ink.$stroke_width = strokeWidth;
+ ink.$stroke_bezier = strokeBezier;
+ ink.$stroke_startMarker = arrowStart;
+ ink.$stroke_endMarker = arrowEnd;
+ ink.$stroke_dash = dash;
+ ink.$stroke_isInkMask = isInkMask;
+ ink.$text_align = 'center';
+ ink.$rotation = 0;
+ ink.$width_min = 1;
+ ink.$height_min = 1;
+ ink.$defaultDoubleClick = 'ignore';
+ ink.$author_date = new DateField();
+ ink.$acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
return ink;
}
@@ -1073,10 +1072,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Linear }, id);
}
- export function MapCollectionDocument(documents: Array<Doc>, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Map });
- }
-
export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Carousel });
}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 1a5a5f889..4e321a893 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -236,7 +236,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
className="calendar-interface"
style={{
background: SnappingManager.userBackgroundColor,
- color: StrCast(Doc.UserDoc().userColor),
+ color: SnappingManager.userColor,
}}>
<p className="selected-doc-title" style={{ color: SnappingManager.userColor }}>
<b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 537c703b4..4c492cae0 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -15,7 +15,7 @@ import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { Gestures } from "../../pen-gestures/GestureTypes";
import { DocServer } from "../DocServer";
import { DocUtils, FollowLinkScript } from '../documents/DocUtils';
-import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
+import { CollectionViewType, DocumentType, standardViewTypes } from "../documents/DocumentTypes";
import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
@@ -57,6 +57,7 @@ export interface Button {
expertMode?: boolean;// available only in expert mode
btnList?: List<string>;
ignoreClick?: boolean;
+ showUntilToggle?: boolean; // whether the popup should stay open when the background is clicked.
buttonText?: string;
backgroundColor?: string;
waitForDoubleClickToClick?: boolean;
@@ -143,9 +144,9 @@ export class CurrentUserUtils {
static setupNoteTemplates(doc: Doc, field="template_notes") {
const tempNotes = DocCast(doc[field]);
const reqdTempOpts:DocumentOptions[] = [
- { title: "Postit", backgroundColor: "yellow", icon: "sticky-note", _layout_showTitle: "title", layout_borderRounding: "5px"},
- { title: "Idea", backgroundColor: "pink", icon: "lightbulb" , _layout_showTitle: "title"},
- { title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}];
+ { title: "Postit", _override_backgroundColor: true, backgroundColor: "yellow", icon: "sticky-note", _layout_showTitle: "title", layout_borderRounding: "5px"},
+ { title: "Idea", _override_backgroundColor: true, backgroundColor: "pink", icon: "lightbulb" , _layout_showTitle: "title"},
+ { title: "Topic", _override_backgroundColor: true, backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}];
const reqdNoteList = [...reqdTempOpts.map(opts => {
const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
@@ -480,22 +481,24 @@ pie title Minerals in my tap water
const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(this.target?.data).filter(doc => !docList(this.target.viewed).includes(doc)).length.toString())";
const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails";
return [
- { title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
- { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), ignoreClick: true, icon: "search", },
- { title: "Files", toolTip: "Files", target: this.setupFilesystem(doc, "myFilesystem"), ignoreClick: true, icon: "folder-open", },
- { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), ignoreClick: true, icon: "wrench", },
- { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), ignoreClick:false, icon: "upload", },
- { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field
- { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}},
- { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}},
- { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), ignoreClick: true, icon: "folder-open", hidden: false },
- { title: "Faces", toolTip: "Unique Faces", target: this.setupFaceCollection(doc, "myFaceCollection"), ignoreClick: true, icon: "face-smile", hidden: false },
- { title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), icon: "search", },
+ { title: "Files", toolTip: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", },
+ { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", },
+ { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
+ { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field
+ { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue: badgeValue}},
+ { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}},
+ { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), icon: "folder-open", hidden: false },
+ { title: "Faces", toolTip: "Unique Faces", target: this.setupFaceCollection(doc, "myFaceCollection"), icon: "face-smile", hidden: false },
+ { title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(this)'}}));
}
/// the empty panel that is filled with whichever left menu button's panel has been selected
static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") {
+ const panel = DocCast(doc[field]);
+ if (panel) panel.proto = undefined;
DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as {[key:string]: FieldType}), {title:"leftSidebarPanel", isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
}
@@ -601,7 +604,7 @@ pie title Minerals in my tap water
const myFilesystem = DocCast(doc[field]);
const newFolderOpts: DocumentOptions = {
- _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
+ _forceActive: true, _dragOnlyWithinContainer: true, embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
};
const newFolderScript = { onClick: CollectionTreeView.AddTreeFunc};
@@ -736,12 +739,12 @@ pie title Minerals in my tap water
}
static viewTools(): Button[] {
return [
- { title: "Tags", icon: "id-card", toolTip: "Toggle Tags display", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
- { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Fit All", icon: "object-group", toolTip:"Fit Docs to View (double tap to persist)",
- btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Tags", icon: "id-card", toolTip: "Toggle Tags display", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Fit All", icon: "object-group", toolTip:"Fit Docs to View (double tap to persist)",
+ btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
@@ -759,14 +762,14 @@ pie title Minerals in my tap water
{ title: "Vcenter", toolTip: "Vertical center", btnType: ButtonType.ToggleButton, icon: "pallet", toolType:"vcent", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment",ignoreClick: true,
subMenu: [
- { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
- { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
]},
- { title: "Fit Box", toolTip: "Fit text to box", btnType: ButtonType.ToggleButton, icon: "object-group",toolType:"fitBox", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
- { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
- { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
- { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
+ { title: "Fit Box", toolTip: "Fit text to box", btnType: ButtonType.ToggleButton, icon: "object-group",toolType:"fitBox", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+ { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink",expertMode: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
// { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
@@ -778,7 +781,7 @@ pie title Minerals in my tap water
{ title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
- { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' },
+ { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' },
subMenu: [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
{ title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
@@ -786,7 +789,7 @@ pie title Minerals in my tap water
]},
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40},
{ title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}},
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
subMenu: [
{ title: "Stroke", toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkEraserTool.Stroke, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
{ title: "Segment", toolTip: "Erase between intersections",btnType:ButtonType.ToggleButton,icon:"xmark", toolType:InkEraserTool.Segment, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
@@ -824,36 +827,33 @@ pie title Minerals in my tap water
{ title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }},
];
}
- static contextMenuTools(doc:Doc):Button[] {
+ static contextMenuTools():Button[] {
return [
- { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Card, CollectionViewType.Carousel,CollectionViewType.Carousel3D,
- CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear,
- CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Pivot, CollectionViewType.Schema, CollectionViewType.Stacking,
- CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]),
- title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}},
+ { title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, expertMode: false, btnList: new List<string>(standardViewTypes), ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}},
{ title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
{ title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
{ title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
{ title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
- { title: "Border", icon: "pen", toolTip: "Border Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBorderColor(value, _readOnly_)'} }, // Only when a document is selected
+ { title: "Border", icon: "border-style", toolTip: "Border Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBorderColor(value, _readOnly_)'} }, // Only when a document is selected
{ title: "B.Width", toolTip: "Border width", btnType: ButtonType.NumberSliderButton, ignoreClick: true, scripts: {script: '{ return setBorderWidth(value, _readOnly_);}'}, numBtnMin: 0, linearBtnWidth:40},
{ title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
{ title: "Chat", icon: "lightbulb", toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, width: 30, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
- { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
- { title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: CurrentUserUtils.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
- { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-
- { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
- { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
- { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
- { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearBtnWidth:58 }, // Only when Schema is selected
+ { title: "Filter", icon: "=", toolTip: "Filter cards by tags", btnType: ButtonType.MultiToggleButton,
+ subMenu: this.filterTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},ignoreClick:true, width: 30},
+ { title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: this.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: this.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: this.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+ { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: this.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "View", icon: "View", toolTip: "View tools", subMenu: this.viewTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: this.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+
+ { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: this.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+ { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: this.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+ { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: this.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+ { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: this.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearBtnWidth:58 }, // Only when Schema is selected
];
}
@@ -867,7 +867,7 @@ pie title Minerals in my tap water
_height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
toolType: params.toolType, expertMode: params.expertMode,
_dragOnlyWithinContainer: true, _lockedPosition: true,
- _embedContainer: btnContainer
+ embedContainer: btnContainer
};
const reqdFuncs:{[key:string]:string} = {
...params.funcs,
@@ -899,7 +899,7 @@ pie title Minerals in my tap water
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
- const ctxtMenuBtns = CurrentUserUtils.contextMenuTools(doc).map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
+ const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
/// Initializes all the default buttons for the top bar context menu
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 44fbda319..2eef3da0e 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -356,23 +356,16 @@ export namespace DictationManager {
action: (target: DocumentView, matches: RegExpExecArray) => {
const count = interpretNumber(matches[1]);
const what = matches[2];
- const dataDoc = Doc.GetProto(target.Document);
- const fieldKey = 'data';
- if (isNaN(count)) {
- return;
- }
- for (let i = 0; i < count; i++) {
- let created: Doc | undefined;
- switch (what) {
- case 'image':
- created = Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
- break;
- case 'nested collection':
- created = Docs.Create.FreeformDocument([], {});
- break;
- default:
+ if (!isNaN(count)) {
+ for (let i = 0; i < count; i++) {
+ const created = (() => {
+ switch (what) {
+ case 'image': return Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
+ case 'nested collection':return Docs.Create.FreeformDocument([], {});
+ } // prettier-ignore
+ })();
+ created && Doc.AddDocToList(target.dataDoc, Doc.LayoutFieldKey(target.Document), created);
}
- created && Doc.AddDocToList(dataDoc, fieldKey, created);
}
},
restrictTo: [DocumentType.COL],
@@ -388,13 +381,15 @@ export namespace DictationManager {
},
];
}
- export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+ export function recordAudioAnnotation(doc: Doc, fieldIn: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+ const field = '$' + fieldIn + '_audioAnnotations';
let gumStream: MediaStream | undefined;
let recorder: MediaRecorder | undefined;
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
- let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+ let audioTextAnnos = Cast(doc[field + '_text'], listSpec('string'), null);
if (audioTextAnnos) audioTextAnnos.push('');
- else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+ else audioTextAnnos = doc[field + '_text'] = new List<string>(['']);
+ doc._layout_showTags = true;
DictationManager.Controls.listen({
interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
continuous: { indefinite: false },
@@ -415,16 +410,16 @@ export namespace DictationManager {
const [{ result }] = await Networking.UploadFilesToServer({ file: file as Blob & { name: string; lastModified: number; webkitRelativePath: string } });
if (!(result instanceof Error)) {
const audioField = new AudioField(result.accessPaths.agnostic.client);
- const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+ const audioAnnos = Cast(doc[field], listSpec(AudioField), null);
if (audioAnnos) audioAnnos.push(audioField);
- else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+ else doc[field] = new List([audioField]);
}
};
recorder.start();
const stopFunc = () => {
recorder?.stop();
DictationManager.Controls.stop(/* false */);
- dataDoc.audioAnnoState = AudioAnnoState.stopped;
+ doc._audioAnnoState = AudioAnnoState.stopped;
gumStream?.getAudioTracks()[0].stop();
};
if (onRecording) onRecording(stopFunc);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b5d29be4c..7d3f63448 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,5 +1,5 @@
import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
@@ -93,7 +93,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
data?.draggedDocuments.forEach((doc, i) => {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
+ if (doc.type === DocumentType.FONTICON || StrCast(doc[DocLayout].layout).includes(FontIconBox.name)) {
if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
// dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
dbox = Doc.MakeEmbedding(dbox);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 1ec85c9d9..79d009720 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -19,6 +19,7 @@ import './GroupManager.scss';
import { GroupMemberView } from './GroupMemberView';
import { SharingManager, User } from './SharingManager';
import { SnappingManager } from './SnappingManager';
+import { SettingsManager } from './SettingsManager';
/**
* Interface for options for the react-select component
@@ -290,11 +291,12 @@ export class GroupManager extends ObservableReactComponent<object> {
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
})}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
/>
</div>
</div>
- <div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+ <div className="group-input" style={{ border: SettingsManager.userColor }}>
<input
ref={this.inputRef}
onKeyDown={this.handleKeyDown}
@@ -306,7 +308,7 @@ export class GroupManager extends ObservableReactComponent<object> {
})}
/>
</div>
- <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+ <div style={{ border: SettingsManager.userColor }}>
<Select
className="select-users"
isMulti
@@ -332,15 +334,15 @@ export class GroupManager extends ObservableReactComponent<object> {
}),
valueContainer: () => ({
display: 'inline-flex',
- fontStyle: StrCast(Doc.UserDoc().userColor),
- color: StrCast(Doc.UserDoc().userColor),
+ fontStyle: SettingsManager.userColor,
+ color: SettingsManager.userColor,
width: '100%',
}),
}}
/>
</div>
<div className="create-button">
- <Button text="Create" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ <Button text="Create" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} onClick={this.createGroup} />
</div>
</div>
);
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index b0886a67c..2fa6e3059 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -51,6 +51,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error).filter(diag => //
diag.code !== 2304 &&
diag.code !== 2339 &&
+ diag.code !== 2314 &&
(diag.code !== 2552 ||!Object.keys(scriptingGlobals).includes(diagnostics[0].messageText.toString().match(/Cannot find name '([A-Za-z0-9$-_]+)'/)?.[1]??"-------"))
); // prettier-ignore
if ((options.typecheck !== false && errors.length) || !script) {
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 6ea242fc3..89e3686b2 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -135,9 +135,9 @@ export class SettingsManager extends React.Component<object> {
reaction(
() => [SettingsManager.userBackgroundColor, SettingsManager.userColor, SettingsManager.userVariantColor],
([back, user, variant]) => {
- SnappingManager.userBackgroundColor = back;
- SnappingManager.userVariantColor = variant;
- SnappingManager.userColor = user;
+ SnappingManager.SetUserBackgroundColor(back);
+ SnappingManager.SetUserVariantColor(variant);
+ SnappingManager.SetUserColor(user);
},
{ fireImmediately: true }
);
@@ -177,6 +177,7 @@ export class SettingsManager extends React.Component<object> {
}))}
dropdownType={DropdownType.SELECT}
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
fillWidth
/>
<Toggle formLabel="Match System" toggleType={ToggleType.SWITCH} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.UserDoc().userThemeSystem)} onClick={this.userThemeSystemToggle} />
@@ -300,6 +301,7 @@ export class SettingsManager extends React.Component<object> {
<NumberDropdown
number={NumCast(Doc.UserDoc().headerHeight, 30)}
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
numberDropdownType="slider"
min={6}
max={60}
@@ -342,6 +344,7 @@ export class SettingsManager extends React.Component<object> {
<Group formLabel="Default Font">
<NumberDropdown
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
numberDropdownType="slider"
min={0}
max={50}
@@ -355,10 +358,10 @@ export class SettingsManager extends React.Component<object> {
/>
<ColorPicker
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
type={Type.PRIM}
defaultPickerType="Classic"
selectedColor={StrCast(Doc.UserDoc().textBackgroundColor, Colors.LIGHT_GRAY)}
- background={SnappingManager.userBackgroundColor}
icon={<FontAwesomeIcon icon="palette" size="lg" />}
tooltip="default text background color"
label="background"
@@ -390,6 +393,7 @@ export class SettingsManager extends React.Component<object> {
this.changeFontFamily(val as string);
}}
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
fillWidth
/>
</Group>
@@ -402,12 +406,12 @@ export class SettingsManager extends React.Component<object> {
@computed get passwordContent() {
return (
<div className="password-content">
- <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
- <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
- <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
+ <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} val="" setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+ <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} val="" setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+ <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} val="" setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
{!this._passwordResultText ? null : <div className={`${this._passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this._passwordResultText}</div>}
- <Button type={Type.SEC} text="Forgot Password" color={SettingsManager.userColor} />
- <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} />
+ <Button type={Type.SEC} text="Forgot Password" color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} />
+ <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} background={SettingsManager.userBackgroundColor} />
</div>
);
}
@@ -415,7 +419,7 @@ export class SettingsManager extends React.Component<object> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <Button type={Type.TERT} text="Connect to Google" iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
+ <Button type={Type.TERT} text="Connect to Google" color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
</div>
);
}
@@ -465,6 +469,7 @@ export class SettingsManager extends React.Component<object> {
type={Type.TERT}
placement="bottom-start"
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
fillWidth
/>
<Toggle formLabel="Playground Mode" toggleType={ToggleType.SWITCH} toggleStatus={this._playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
@@ -494,13 +499,14 @@ export class SettingsManager extends React.Component<object> {
type={Type.TERT}
placement="bottom-start"
color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
/>
</div>
</div>
<div className="tab-column">
<div className="tab-column-title">Permissions</div>
<div className="tab-column-content">
- <Button text="Manage Groups" type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
+ <Button text="Manage Groups" type={Type.TERT} color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} onClick={() => GroupManager.Instance?.open()} />
<Toggle
toggleType={ToggleType.SWITCH}
formLabel="Default access private"
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 962f51cd4..db8c6c5cb 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -286,7 +286,7 @@ export class SharingManager extends React.Component<object> {
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
<div className="share-copy-link">
- <Button type={Type.TERT} color={SnappingManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
</div>
<div className="close-button">
<Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SnappingManager.userColor} />
@@ -331,7 +331,7 @@ export class SharingManager extends React.Component<object> {
</select>
</div>
<div className="share-button">
- <Button text="SHARE" type={Type.TERT} color={SnappingManager.userColor} onClick={this.share} />
+ <Button text="SHARE" type={Type.TERT} color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} onClick={this.share} />
</div>
</div>
<div className="sort-checkboxes">
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 3bbc297b8..54c91087f 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,4 +1,4 @@
-import { observable, action, runInAction, makeObservable } from 'mobx';
+import { observable, action, makeObservable } from 'mobx';
import { Gestures } from '../../pen-gestures/GestureTypes';
export enum freeformScrollMode {
@@ -34,6 +34,9 @@ export class SnappingManager {
@observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _inkShape: Gestures | undefined = undefined;
@observable _chatVisible: boolean = false;
+ @observable _userBackgroundColor: string | undefined = undefined;
+ @observable _userVariantColor: string | undefined = undefined;
+ @observable _userColor: string | undefined = undefined;
private constructor() {
SnappingManager._manager = this;
@@ -48,6 +51,9 @@ export class SnappingManager {
this.Instance._vertSnapLines.push(...vertLines);
};
+ public static get userBackgroundColor() { return this.Instance._userBackgroundColor; } // prettier-ignore
+ public static get userVariantColor() { return this.Instance._userVariantColor; } // prettier-ignore
+ public static get userColor() { return this.Instance._userColor; } // prettier-ignore
public static get HorizSnapLines() { return this.Instance._horizSnapLines; } // prettier-ignore
public static get VertSnapLines() { return this.Instance._vertSnapLines; } // prettier-ignore
public static get LongPress() { return this.Instance._longPress; } // prettier-ignore
@@ -71,29 +77,29 @@ export class SnappingManager {
public static get InkShape() { return this.Instance._inkShape; } // prettier-ignore
public static get ChatVisible() { return this.Instance._chatVisible; } // prettier-ignore
- public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore
- public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
- public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
- public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
- public static SetHideUI = (vis: boolean) => runInAction(() => {this.Instance._hideUI = vis}); // prettier-ignore
- public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
- public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
- public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
- public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore
- public static SetCanEmbed = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore
- public static SetExploreMode = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore
- public static TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore
- public static SetServerVersion = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore
- public static SetLastPressedBtn = (id:string) =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore
- public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore
- public static SetPrintToConsole = (state:boolean) =>runInAction(() => {this.Instance._printToConsole = state}); // prettier-ignore
- public static SetHideDecorations= (state:boolean) =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore
- public static SetKeepGestureMode= (state:boolean) =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore
- public static SetInkShape = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore
- public static SetChatVisible = (vis:boolean) =>runInAction(() => {this.Instance._chatVisible = vis}); // prettier-ignore
+ public static SetUserBackgroundColor = action((color: string) => (this.Instance._userBackgroundColor = color)); // prettier-ignore
+ public static SetUserVariantColor = action((color: string) => (this.Instance._userVariantColor = color)); // prettier-ignore
+ public static SetUserColor = action((color: string) => (this.Instance._userColor = color)); // prettier-ignore
+ public static SetLongPress = action((press: boolean)=> (this.Instance._longPress = press)); // prettier-ignore
+ public static SetShiftKey = action((down: boolean) => (this.Instance._shiftKey = down)); // prettier-ignore
+ public static SetCtrlKey = action((down: boolean) => (this.Instance._ctrlKey = down)); // prettier-ignore
+ public static SetMetaKey = action((down: boolean) => (this.Instance._metaKey = down)); // prettier-ignore
+ public static SetHideUI = action((vis: boolean) => (this.Instance._hideUI = vis)); // prettier-ignore
+ public static SetShowPresPaths = action((paths:boolean) => (this.Instance._showPresPaths = paths)); // prettier-ignore
+ public static SetIsLinkFollowing = action((follow:boolean)=> (this.Instance._isLinkFollowing = follow)); // prettier-ignore
+ public static SetIsDragging = action((drag: boolean) => (this.Instance._isDragging = drag)); // prettier-ignore
+ public static SetIsResizing = action((docid?:string) => (this.Instance._isResizing = docid)); // prettier-ignore
+ public static SetCanEmbed = action((embed:boolean) => (this.Instance._canEmbed = embed)); // prettier-ignore
+ public static SetExploreMode = action((state:boolean) => (this.Instance._exploreMode = state)); // prettier-ignore
+ public static TriggerUserPanned = action(() => (this.Instance._userPanned = !this.Instance._userPanned)); // prettier-ignore
+ public static SetServerVersion = action((version:string)=> (this.Instance._serverVersion = version)); // prettier-ignore
+ public static SetLastPressedBtn = action((id:string) => (this.Instance._lastBtnId = id)); // prettier-ignore
+ public static SetPropertiesWidth = action((wid:number) => (this.Instance._propertyWid = wid)); // prettier-ignore
+ public static SetPrintToConsole = action((state:boolean) => (this.Instance._printToConsole = state)); // prettier-ignore
+ public static SetHideDecorations = action((state:boolean) => (this.Instance._hideDecorations = state)); // prettier-ignore
+ public static SetKeepGestureMode = action((state:boolean) => (this.Instance._keepGestureMode = state)); // prettier-ignore
+ public static SetInkShape = action((shape?:Gestures)=>(this.Instance._inkShape = shape)); // prettier-ignore
+ public static SetChatVisible = action((vis:boolean) => (this.Instance._chatVisible = vis)); // prettier-ignore
- public static userColor: string | undefined;
- public static userVariantColor: string | undefined;
- public static userBackgroundColor: string | undefined;
public static SettingsStyle: CSSStyleSheet | null;
}
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 99dee6410..b5e56cad5 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -4,6 +4,7 @@ import { SnappingManager } from '../util/SnappingManager';
import './AntimodeMenu.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface AntimodeMenuProps {}
/**
@@ -162,6 +163,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
position: this.Pinned ? 'unset' : undefined,
+ border: `${SnappingManager.userColor} solid 1px`,
}}>
{buttons}
</div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 8f254ddcd..45c80c6f7 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { returnFalse } from '../../ClientUtils';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocLayout } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { DocCast, toList } from '../../fields/Types';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
@@ -19,6 +19,7 @@ import { FieldViewProps } from './nodes/FieldView';
* */
export interface DocComponentProps {
Document: Doc;
+ TemplateDataDocument?: Doc;
LayoutTemplate?: () => Opt<Doc>;
LayoutTemplateString?: string;
}
@@ -36,7 +37,7 @@ export function DocComponent<P extends DocComponentProps>() {
* NOTE: it is very unlikely that you really want to use this method. Instead
* consider: Document, layoutDoc, dataDoc
*/
- get _renderDoc() {
+ get Document() {
return this._props.Document;
}
@@ -46,8 +47,15 @@ export function DocComponent<P extends DocComponentProps>() {
* necessarily the Doc being rendered in the current React component.
* This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
*/
- get Document() {
- return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+ get rootDoc() {
+ return DocCast(this.Document.rootDocument, this.Document);
+ }
+
+ /**
+ * Whether the doc is a sub-componentn of a compound template doc.
+ */
+ get isTemplateForField() {
+ return this.rootDoc !== this.layoutDoc && this._props.TemplateDataDocument;
}
/**
* This is the document being rendered by the React component. In the
@@ -56,14 +64,14 @@ export function DocComponent<P extends DocComponentProps>() {
* This may or may not inherit from the data doc.
*/
@computed get layoutDoc() {
- return this._props.LayoutTemplateString ? this._renderDoc : Doc.Layout(this._renderDoc, this._props.LayoutTemplate?.());
+ return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
}
/**
* This is the unique data repository for a document that stores the intrinsic document data.
*/
@computed get dataDoc() {
- return this._renderDoc[DocData];
+ return this.Document[DocData];
}
}
return Component;
@@ -92,10 +100,8 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
* This is the doc that is being rendered. It will be either:
* 1) the same as Document if the root of a regular or compound Doc is rendered
* 2) the same as the layoutDoc if a component of a compound Doc is rendered.
- * NOTE: it is very unlikely that you really want to use this method. Instead
- * consider: Document, layoutDoc, dataDoc
*/
- get _renderDoc() {
+ get Document() {
return this._props.Document;
}
@@ -104,9 +110,12 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
* this is the outermost Doc that represents the entire compound Doc. It is not
* necessarily the Doc being rendered in the current React component.
* This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
+ *
+ * NOTE: it is very unlikely that you really want to use this method. Instead
+ * consider: Document, layoutDoc, dataDoc
*/
- get Document() {
- return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+ get rootDoc() {
+ return DocCast(this.Document.rootDocument, this.Document);
}
/**
* This is the document being rendered by the React component. In the
@@ -115,14 +124,14 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
* This may or may not inherit from the data doc.
*/
@computed get layoutDoc() {
- return Doc.Layout(this._renderDoc);
+ return this.Document[DocLayout];
}
/**
* This is the unique data repository for a dcoument that stores the intrinsic document data
*/
@computed get dataDoc() {
- return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : this._renderDoc[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
}
/**
@@ -165,10 +174,8 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
* This is the doc that is being rendered. It will be either:
* 1) the same as Document if the root of a regular or compound Doc is rendered
* 2) the same as the layoutDoc if a component of a compound Doc is rendered.
- * NOTE: it would unlikely that you really want to use this instead of the
- * other Doc options (Document, layoutDoc, dataDoc)
*/
- get _renderDoc() {
+ get Document() {
return this._props.Document;
}
@@ -177,22 +184,27 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
* this is the outermost Doc that represents the entire compound Doc. It is not
* necessarily the Doc being rendered in the current React component.
* This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
+ *
+ * NOTE: it would unlikely that you really want to use this instead of the
+ * other Doc options (Document, layoutDoc, dataDoc)
*/
- @computed get Document() {
- return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+ @computed get rootDoc() {
+ return DocCast(this.Document.rootDocument, this.Document);
}
/**
* This is the document being rendered. It may be a template so it may or may no inherit from the data doc.
*/
@computed get layoutDoc() {
- return Doc.Layout(this._renderDoc);
+ return this.Document[DocLayout];
}
/**
* This is the unique data repository for a dcoument that stores the intrinsic document data
*/
@computed get dataDoc() {
- return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : this._props.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ?
+ (this._props.TemplateDataDocument ?? this.Document[DocData]) :
+ this.Document[DocData]; // prettier-ignore
}
/**
diff --git a/src/client/views/DocViewUtils.ts b/src/client/views/DocViewUtils.ts
index 1f5f29c7e..5710187b5 100644
--- a/src/client/views/DocViewUtils.ts
+++ b/src/client/views/DocViewUtils.ts
@@ -1,6 +1,3 @@
-/* eslint-disable prefer-destructuring */
-/* eslint-disable default-param-last */
-/* eslint-disable no-use-before-define */
import { runInAction } from 'mobx';
import { Doc, SetActiveAudioLinker } from '../../fields/Doc';
import { DocUtils } from '../documents/DocUtils';
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b17dbc93d..9a57a5e6c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -358,7 +358,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
view =>
view &&
DictationManager.recordAudioAnnotation(
- view.dataDoc,
+ view.Document,
view.LayoutFieldKey,
stopFunc => {
this._stopFunc = stopFunc;
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a5afb1305..09a13634f 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -40,9 +40,10 @@ $resizeHandler: 8px;
}
.documentDecorations-tagsView {
position: absolute;
- height: 100%;
+ width: 100%;
pointer-events: all;
border-radius: 50%;
+ top: 30; // offset by height of documentButtonBar so that items can be clicked without overlap interference
color: black;
}
}
@@ -91,30 +92,71 @@ $resizeHandler: 8px;
}
}
- .documentDecorations-closeButton {
+ .documentDecorations-closeWrapper {
display: flex;
- align-items: center;
- justify-content: center;
- background: #fb9d75;
- border: solid 1.5px #a94442;
- color: #fb9d75;
- transition: 0.1s ease;
- opacity: 1;
- pointer-events: all;
- width: 20px;
- height: 20px;
- min-width: 20px;
- border-radius: 100%;
- opacity: 0.5;
- cursor: pointer;
-
- &:hover {
- color: #a94442;
+ .documentDecorations-closeButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #fb9d75;
+ border: solid 1.5px #a94442;
+ color: #fb9d75;
+ transition: 0.1s ease;
opacity: 1;
+ pointer-events: all;
+ width: 20px;
+ height: 20px;
+ min-width: 20px;
+ border-radius: 100%;
+ opacity: 0.5;
+ cursor: pointer;
+
+ &:hover {
+ color: #a94442;
+ opacity: 1;
+ width: 20px;
+ min-width: 20px;
+ }
+
+ > svg {
+ margin: 0;
+ }
}
- > svg {
- margin: 0;
+ .documentDecorations-minimizeButton {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ background: #ffdd00;
+ border: solid 1.5px #a94442;
+ color: #ffdd00;
+ transition: 0.1s ease;
+ font-size: 11px;
+ opacity: 1;
+ grid-column: 2;
+ pointer-events: all;
+ width: 20px;
+ height: 20px;
+ min-width: 20px;
+ border-radius: 100%;
+ opacity: 0.5;
+ cursor: pointer;
+
+ &:hover {
+ color: #a94442;
+ opacity: 1;
+ }
+
+ > svg {
+ margin: 0;
+ }
+ }
+ &:hover {
+ width: 40px;
+ min-width: 40px;
+ .documentDecorations-minimizeButton {
+ display: flex;
+ }
}
}
@@ -145,38 +187,9 @@ $resizeHandler: 8px;
}
}
- .documentDecorations-minimizeButton {
- display: flex;
- align-items: center;
- justify-content: center;
- background: #ffdd00;
- border: solid 1.5px #a94442;
- color: #ffdd00;
- transition: 0.1s ease;
- font-size: 11px;
- opacity: 1;
- grid-column: 2;
- pointer-events: all;
- width: 20px;
- height: 20px;
- min-width: 20px;
- border-radius: 100%;
- opacity: 0.5;
- cursor: pointer;
-
- &:hover {
- color: #a94442;
- opacity: 1;
- }
-
- > svg {
- margin: 0;
- }
- }
-
.documentDecorations-title {
opacity: 1;
- width: calc(100% - 60px); // = margin-left + margin-right
+ width: 100%;
grid-column: 3;
pointer-events: auto;
overflow: hidden;
@@ -315,7 +328,7 @@ $resizeHandler: 8px;
.documentDecorations-rightResizer {
pointer-events: auto;
background: global.$medium-gray-dim;
- //opacity: 0.2;
+ opacity: 0.2;
&:hover {
opacity: 1;
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 19b987cb5..0756d3d9e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -49,7 +49,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
static Instance: DocumentDecorations;
private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
- private _resizeBorderWidth = 16;
+ private _resizeBorderWidth = 8;
private _linkBoxHeight = 20 + 3; // link button height + margin
private _titleHeight = 20;
private _resizeUndo?: UndoManager.Batch;
@@ -98,11 +98,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const bounds = { ...this.Bounds };
const leftBounds = this._props.boundsLeft;
const topBounds = DocumentView.LightboxDoc() ? 0 : this._props.boundsTop;
- bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
- bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
+ bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth) + this._resizeBorderWidth;
+ bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth - this._titleHeight) + this._resizeBorderWidth + this._titleHeight;
const borderRadiusDraggerWidth = 15;
- bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
- bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
+ bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth) - this._resizeBorderWidth - borderRadiusDraggerWidth));
+ bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth + this._linkBoxHeight) - this._resizeBorderWidth - this._linkBoxHeight));
return bounds;
}
@@ -320,7 +320,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
const dist = moveEv.clientX < x && moveEv.clientY < y ? 0 : Math.sqrt((moveEv.clientX - x) * (moveEv.clientX - x) + (moveEv.clientY - y) * (moveEv.clientY - y));
DocumentView.SelectedDocs().forEach(doc => {
- const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
+ const docMax = Math.min(NumCast(doc._width) / 2, NumCast(doc._height) / 2);
const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
doc._layout_borderRounding = `${radius}px`;
});
@@ -388,7 +388,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
DocumentView.Selected().forEach(dv => {
const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
- const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
+ const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document._width) / 2, localRotCtr[1] - NumCast(dv.Document._height) / 2];
const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
@@ -623,8 +623,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
.forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
doc.$data = new InkField(inkPts.map(
(ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
- X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
- Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
+ X: NumCast(doc.x) - x + (NumCast(doc._width) * ipt.X) / width,
+ Y: NumCast(doc.y) - y + (NumCast(doc._height) * ipt.Y) / height,
}))); // prettier-ignore
Doc.SetNativeWidth(doc, undefined);
Doc.SetNativeHeight(doc, undefined);
@@ -635,7 +635,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
if (DocumentView.Selected().length === 1) {
const selected = DocumentView.Selected()[0];
if (this._titleControlString.startsWith('$')) {
- return Field.toJavascriptString(selected._renderDoc[this._titleControlString.substring(1)] as FieldType) || '-unset-';
+ return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
}
return this._accumulatedTitle;
}
@@ -719,7 +719,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
// Radius constants
const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null));
- const docMax = Math.min(NumCast(seldocview.Document.width) / 2, NumCast(seldocview.Document.height) / 2);
+ const docMax = Math.min(NumCast(seldocview.Document._width) / 2, NumCast(seldocview.Document._height) / 2);
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
@@ -773,7 +773,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
</span>
)}
{sharingMenu}
- {!useLock ? null : (
+ {!useLock || hideTitle ? null : (
<Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
<div className="documentDecorations-lock" style={{ color: seldocview.Document._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
<FontAwesomeIcon size="sm" icon="lock" />
@@ -790,10 +790,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className="documentDecorations-background"
style={{
- width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
- height: bounds.b - bounds.y + this._resizeBorderWidth + 'px',
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2,
+ width: bounds.r - bounds.x + 2 * this._resizeBorderWidth + 'px',
+ height: bounds.b - bounds.y + 2 * this._resizeBorderWidth + 'px',
+ left: bounds.x - this._resizeBorderWidth,
+ top: bounds.y - this._resizeBorderWidth,
transformOrigin,
background: SnappingManager.ShiftKey ? undefined : 'yellow',
pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all',
@@ -807,10 +807,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className={`documentDecorations-container ${this._showNothing ? 'showNothing' : ''}`}
style={{
- transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transform: `translate(${bounds.x - this._resizeBorderWidth}px, ${bounds.y - this._resizeBorderWidth - this._titleHeight}px) rotate(${rotation}deg)`,
transformOrigin,
- width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
- height: bounds.b - bounds.y + this._resizeBorderWidth + (this._showNothing ? 0 : this._titleHeight) + 'px',
+ width: bounds.r - bounds.x + 2 * this._resizeBorderWidth + 'px',
+ height: bounds.b - bounds.y + 2 * this._resizeBorderWidth + (this._showNothing ? 0 : this._titleHeight) + 'px',
}}>
<div
className="documentDecorations-topbar"
@@ -818,8 +818,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
}}
onPointerDown={this.onContainerDown}>
- {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
- {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
+ <div className="documentDecorations-closeWrapper">
+ {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
+ {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
+ </div>
{titleArea}
{hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection, opption: in editor view)')}
</div>
@@ -832,7 +834,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div key="c" className="documentDecorations-centerCont" />
<div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} />
<div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} />
- <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />
+ {seldocview.TagPanelHeight !== 0 || seldocview.TagPanelEditing ? null : <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />}
<div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
</>
)}
@@ -848,12 +850,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
/>
)}
- {hideDocumentButtonBar || this._showNothing ? null : (
+ {seldocview.TagPanelEditing || hideDocumentButtonBar || this._showNothing ? null : (
<div
className="link-button-container"
style={{
- top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
- transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ top: DocumentView.Selected().length > 1 || !seldocview.showTags ? 0 : `${seldocview.TagPanelHeight}px`,
+ transform: `translate(${-this._resizeBorderWidth + 10}px, ${(seldocview.TagPanelHeight === 0 ? 2 * this._resizeBorderWidth : this._resizeBorderWidth) + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
<DocumentButtonBar views={() => DocumentView.Selected()} />
</div>
@@ -861,10 +863,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className="documentDecorations-tagsView"
style={{
- top: 30, // offset by height of documentButtonBar so that items can be clicked without overlap interference
- transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ display: DocumentView.Selected().length > 1 || !seldocview.showTags ? 'none' : undefined,
+ transform: `translate(${-this._resizeBorderWidth + 10}px, ${2 * this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
- {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
+ {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} background={SnappingManager.userBackgroundColor ?? ''} /> : null}
</div>
</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index e2490cec8..d9447b7ec 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -287,8 +287,8 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
staticDisplay = () => {
let toDisplay;
- const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
if (this._props.inputString) {
+ const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
toDisplay = (
<input
className="editableView-input"
@@ -315,15 +315,17 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
};
render() {
- const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
- if (this._editing && gval !== undefined) {
- return this._props.sizeToContent ? (
- <div style={{ display: 'grid', minWidth: 100 }}>
- <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.renderEditor()}</div>
- </div>
- ) : (
- this.renderEditor()
- );
+ if (this._editing) {
+ const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
+ if (gval !== undefined) {
+ return this._props.sizeToContent ? (
+ <div style={{ display: 'grid', minWidth: 100 }}>
+ <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.renderEditor()}</div>
+ </div>
+ ) : (
+ this.renderEditor()
+ );
+ }
}
setTimeout(() => this._props.autosuggestProps?.resetValue());
return (
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 37060d20c..94c023330 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -11,14 +11,13 @@ import { GroupManager } from '../util/GroupManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
-import { UndoManager, undoable } from '../util/UndoManager';
+import { UndoManager } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
import { InkStrokeProperties } from './InkStrokeProperties';
import { MainView } from './MainView';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
@@ -242,7 +241,7 @@ export class KeyManager {
{
const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
if (importBtn) {
- MainView.Instance.selectMenu(importBtn);
+ MainView.Instance.selectLeftSidebarButton(importBtn);
}
}
break;
@@ -250,7 +249,7 @@ export class KeyManager {
{
const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
if (trailsBtn) {
- MainView.Instance.selectMenu(trailsBtn);
+ MainView.Instance.selectLeftSidebarButton(trailsBtn);
}
}
break;
@@ -260,7 +259,7 @@ export class KeyManager {
} else {
const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
if (searchBtn) {
- MainView.Instance.selectMenu(searchBtn);
+ MainView.Instance.selectLeftSidebarButton(searchBtn);
}
}
break;
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index f91f98807..1293aa9d0 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -382,7 +382,7 @@ export class InkTranscription extends React.Component {
render() {
return (
- <div className="ink-transcription">
+ <div className="ink-transcription" style={{pointerEvents: 'none'}}>
<div className="math-editor" ref={this.setMathRef}></div>
<div className="text-editor" ref={this.setTextRef}></div>
</div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e70f9e5ed..be6e2fecb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -529,6 +529,7 @@ export class MainView extends ObservableReactComponent<object> {
fa.faColumns,
fa.faChevronCircleUp,
fa.faUpload,
+ fa.faBorderStyle,
fa.faBorderAll,
fa.faBraille,
fa.faPersonChalkboard,
@@ -847,7 +848,7 @@ export class MainView extends ObservableReactComponent<object> {
}
@action
- selectMenu = (button: Doc) => {
+ selectLeftSidebarButton = (button: Doc) => {
const title = StrCast(button.$title);
const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
this.closeFlyout();
@@ -859,7 +860,9 @@ export class MainView extends ObservableReactComponent<object> {
case 'Help':
break;
default:
- this.expandFlyout(button);
+ this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
+ this._sidebarContent.proto = DocCast(button.target);
+ SnappingManager.SetLastPressedBtn(button[Id]);
}
}
return true;
@@ -935,19 +938,6 @@ export class MainView extends ObservableReactComponent<object> {
</div>
);
}
-
- expandFlyout = action((button: Doc) => {
- // bcz: What's going on here!? --- may be fixed now, so commenting out ...
- // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
- // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
- // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
- this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
- // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
-
- this._sidebarContent.proto = DocCast(button.target);
- SnappingManager.SetLastPressedBtn(button[Id]);
- });
-
closeFlyout = action(() => {
SnappingManager.SetLastPressedBtn('');
this._panelContent = 'none';
@@ -1161,7 +1151,7 @@ export class MainView extends ObservableReactComponent<object> {
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
- MainView.Instance.selectMenu(doc);
+ MainView.Instance.selectLeftSidebarButton(doc);
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function hideUI() {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index a1e8fe7ba..6778a6691 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -118,7 +118,7 @@ export class PropertiesButtons extends React.Component {
// select text
return this.propertyToggleBtn(
on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'),
- '_layout_centered',
+ 'text_centered',
on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `,
() => <MdTouchApp /> // 'eye'
);
@@ -293,7 +293,7 @@ export class PropertiesButtons extends React.Component {
@computed get onClickVal() {
const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
const followLoc = this.selectedDoc._followLinkLocation;
- const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+ const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?.$isLightbox);
if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace';
if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 7e9cd002b..7fcb15afe 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -13,7 +13,7 @@ import ResizeObserver from 'resize-observer-polyfill';
import { ClientUtils, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast, returnEmptyDoclist } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
+import { AclAdmin, DocAcl, DocData, DocLayout } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
@@ -205,7 +205,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const ids = new Set<string>(reqdKeys);
const docs: Doc[] =
DocumentView.Selected().length < 2 //
- ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc]
+ ? [this.layoutFields ? this.selectedDoc[DocLayout] : this.dataDoc]
: DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc =>
Object.keys(doc)
@@ -264,7 +264,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const docs =
DocumentView.Selected().length < 2 && this.selectedDoc
? [this.layoutFields
- ? Doc.Layout(this.selectedDoc) //
+ ? this.selectedDoc[DocLayout] //
: this.dataDoc!]
: DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); // prettier-ignore
docs.forEach(doc => {
@@ -330,7 +330,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return '-- multiple selected --';
}
if (this.selectedDoc) {
- const layoutDoc = Doc.Layout(this.selectedDoc);
+ const layoutDoc = this.selectedDoc[DocLayout];
const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfHeight : this.docHeight;
const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfWidth : this.docWidth;
return (
@@ -894,9 +894,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
});
}
@computed get borderColor() {
- const doc = this.selectedDoc;
- const layoutDoc = doc ? Doc.Layout(doc) : doc;
- return StrCast(layoutDoc.color);
+ return StrCast(this.selectedDoc._color);
}
set borderColor(value) { this.selectedDoc && (this.selectedDoc.$color = value || undefined); } // prettier-ignore
diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss
index d7de2b641..abfd04f11 100644
--- a/src/client/views/SidebarAnnos.scss
+++ b/src/client/views/SidebarAnnos.scss
@@ -1,3 +1,13 @@
+.sidebarAnnos-container {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ right: 0;
+ .sidebarAnnos-stacking {
+ width: 100%;
+ position: relative;
+ }
+}
.sidebarAnnos-tagList {
display: flex;
flex-direction: row;
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3c0611f03..b7e6318b1 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -213,14 +213,11 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
// TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
return !this._props.showSidebar ? null : (
<div
+ className="sidebarAnnos-container"
style={{
- position: 'absolute',
pointerEvents: this._props.isContentActive() ? 'all' : undefined,
top: this._props.Doc.type !== DocumentType.RTF && StrCast(this._props.Doc._layout_showTitle) === 'title' ? 15 : 0,
- right: 0,
background: this._props.styleProvider?.(this._props.Doc, this._props, StyleProp.WidgetColor) as string,
- width: `100%`,
- height: '100%',
}}>
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
@@ -228,7 +225,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
{Array.from(this.allMetadata.keys()).sort().map(renderMeta)}
</div>
- <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
+ <div className="sidebarAnnos-stacking" style={{ height: `calc(100% - ${this.filtersHeight()}px)` }}>
<CollectionStackingView
{...this._props}
setContentViewBox={emptyFunction}
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index ce00f6101..99796f1fb 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -1,14 +1,13 @@
.styleProvider-filter,
-.styleProvider-audio,
.styleProvider-paint,
.styleProvider-paint-selected,
.styleProvider-lock {
z-index: 2; // has to be above title which is z-index 1
font-size: 10;
- width: 15;
- height: 15;
+ width: 20;
+ height: 20;
position: absolute;
- right: -15;
+ right: -20;
top: 0;
background: black;
pointer-events: all;
@@ -21,25 +20,25 @@
cursor: default;
}
.styleProvider-filter {
- right: 15;
+ right: 20;
.styleProvider-filterShift {
left: 0;
top: 0;
position: absolute;
}
-}
-.styleProvider-audio {
- right: 30;
+ .dropdown-container {
+ width: 15px !important;
+ margin: auto !important;
+ }
}
.styleProvider-paint-selected,
.styleProvider-paint {
top: 15;
}
.styleProvider-paint-selected {
- right: -30;
+ right: -40;
}
.styleProvider-lock:hover,
-.styleProvider-audio:hover,
.styleProvider-filter:hover {
opacity: 1;
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 331ee1707..e8876f96f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,7 +1,6 @@
import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@mui/material';
import { action, untracked } from 'mobx';
import { extname } from 'path';
import * as React from 'react';
@@ -9,18 +8,19 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
+import { DocLayout } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkInkTool } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { AudioAnnoState } from '../../server/SharedMediaTypes';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { IsFollowLinkScript } from '../documents/DocUtils';
import { SnappingManager } from '../util/SnappingManager';
import { undoable, UndoManager } from '../util/UndoManager';
import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
-import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps } from './nodes/DocumentContentsView';
+import { DocumentView } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { StyleProp } from './StyleProp';
import './StyleProvider.scss';
@@ -87,7 +87,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const isNonTransparent = property.includes(':nonTransparent');
const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
const isAnnotated = property.includes(':annotated');
- const layoutDoc = doc ? Doc.Layout(doc) : doc;
+ const layoutDoc = doc?.[DocLayout];
const isOpen = property.includes(':treeOpen');
const boxBackground = property.includes(':docView'); // background color of docView's bounds can be different than the background of contents -- eg FontIconBox
const {
@@ -108,8 +108,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
LayoutTemplateString,
disableBrushing,
NativeDimScaling,
- PanelWidth,
- PanelHeight,
isSelected,
isHovering,
} = props || {}; // extract props that are not shared between fieldView and documentView props.
@@ -250,16 +248,23 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
: 0;
case StyleProp.BackgroundColor: {
if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
- const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
- const usePath = StrCast(doc?.[dataKey + "_usePath"]);
- const alternate = usePath.includes(":hover") ? ( isHovering?.() ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
- let docColor: Opt<string> = StrCast(doc?.[fieldKey+alternate], StrCast(doc?.['backgroundColor' +alternate], isCaption ? 'rgba(0,0,0,0.4)' : ''));
- if (!docColor && doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
+ const dataKey = doc ? Doc.LayoutFieldKey(doc) : '';
+ const usePath = StrCast(doc?.[dataKey + '_usePath']);
+ const alternate = usePath.includes(':hover') ? ( isHovering?.() ? '_' + usePath.replace(':hover','') : '') : usePath ? "_" +usePath:usePath;
+ let docColor:Opt<string> = layoutDoc &&
+ StrCast(alternate ? layoutDoc['backgroundColor' + alternate]:undefined,
+ doc.rootDocument
+ ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument).backgroundColor)) // for nested templates: use template's color, then root doc's color
+ : layoutDoc === doc
+ ? StrCast(doc.backgroundColor)
+ : StrCast(StrCast(Doc.GetT(layoutDoc, 'backgroundColor', 'string', true), StrCast(doc.backgroundColor, StrCast(layoutDoc.backgroundColor)) // otherwise, use expanded template coloor, then root doc's color, then template's inherited color
+ )));
+
// prettier-ignore
switch (layoutDoc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
case DocumentType.PRES: docColor = docColor || 'transparent'; break;
- case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
+ case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || SnappingManager.userBackgroundColor; break;
case DocumentType.RTF: docColor = docColor || StrCast(Doc.UserDoc().textBackgroundColor, Colors.LIGHT_GRAY); break;
case DocumentType.LINK: docColor = (isAnchor ? docColor : undefined); break;
case DocumentType.INK: docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
@@ -322,7 +327,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
? undefined // if it's a background & has a cluster color, make the shadow spread really big
: fieldKey.includes('_inline') // if doc is an inline document in a text box
? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
- :doc.rootDocument !== doc.embedContainer && DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline) and this isn't a simple text template (where the layoutDoc's rootDocument is its embed container)
+ : doc.rootDocument !== doc.embedContainer && DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline) and this isn't a simple text template (where the layoutDoc's rootDocument is its embed container)
? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
: StrCast(doc.layout_boxShadow, '');
}
@@ -337,25 +342,28 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
case StyleProp.Decorations: {
- const lock = () => doc?.pointerEvents !== 'none' ? null : (
+ const showLock = doc?.pointerEvents === 'none'
+ const lock = () => !showLock ? null : (
<div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
<FontAwesomeIcon icon='lock' size="lg" />
</div>
);
- const paint = () => !doc?.onPaint ? null : (
+ const showPaint = doc?.onPaint;
+ const paint = () => !showPaint ? null : (
<div className={`styleProvider-paint${isSelected?.() ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
<FontAwesomeIcon icon='pen' size="lg" />
</div>
);
+ const showFilterIcon =
+ StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
+ ? 'green' // #18c718bd' //'hasFilter'
+ : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
+ ? 'orange' // 'inheritsFilter'
+ : undefined;
+ const showFilter = showFilterIcon && !hideFilterStatus;
const filter = () => {
const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard));
- const showFilterIcon =
- StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
- ? 'green' // #18c718bd' //'hasFilter'
- : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
- ? 'orange' // 'inheritsFilter'
- : undefined;
- return !showFilterIcon || hideFilterStatus ? null : (
+ return !showFilter ? null : (
<div className="styleProvider-filter">
<Dropdown
type={Type.TERT}
@@ -387,26 +395,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
</div>
);
};
- const audio = () => {
- const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
- const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
- if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
- const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
- return (
- <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
- <div className="styleProvider-audio" onPointerDown={() => DocumentView.getFirstDocumentView(doc)?.playAnnotation()}>
- <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
- </div>
- </Tooltip>
- );
- };
return (
+ !showPaint && !showLock && !showFilter ? null:
<>
{paint()}
{lock()}
{filter()}
- {audio()}
</>
);
}
diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss
index b21d303fb..a940502d4 100644
--- a/src/client/views/TagsView.scss
+++ b/src/client/views/TagsView.scss
@@ -2,21 +2,23 @@
display: flex;
flex-wrap: wrap;
flex-direction: column;
- border: 1px solid;
- border-radius: 4px;
width: 100%;
position: relative;
.tagsView-content {
width: 100%;
height: inherit;
+ position: absolute;
.tagsView-list {
display: flex;
flex-wrap: wrap;
- height: 1;
+ height: 100%;
.iconButton-container {
min-height: unset !important;
}
}
+ .tagsView-editing-box {
+ pointer-events: all;
+ }
}
}
@@ -57,10 +59,6 @@
align-items: center;
}
-.tagsView-editing-box {
- margin-top: 20px;
-}
-
.tagsView-input-box {
margin: auto;
align-self: center;
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 7d2a6be5e..79c4d8af1 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, Colors, IconButton } from '@dash/components';
+import { Button, Colors, IconButton, Type } from '@dash/components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import React from 'react';
@@ -20,6 +20,7 @@ import { DocumentView } from './nodes/DocumentView';
import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
import { IconTagBox } from './nodes/IconTagBox';
import { Id } from '../../fields/FieldSymbols';
+import { StyleProp } from './StyleProp';
/**
* The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -254,6 +255,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
interface TagViewProps {
Views: DocumentView[];
+ background: string;
}
/**
@@ -265,11 +267,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
super(props);
makeObservable(this);
}
- InsetDist = 25; // how far tag panel is moved up to overlap DocumentView.
@observable _panelHeightDirty = 0;
@observable _currentInput = '';
- @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length;
+ @observable _isEditing: boolean | undefined = undefined;
_heightDisposer: IReactionDisposer | undefined;
_lastXf = this.View.screenToContentsTransform();
@@ -295,7 +296,9 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
}
@computed get isEditing() {
- return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View)));
+ const selected = DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View);
+ if (this._isEditing === undefined) return selected && this.View.TagPanelEditing; // && !StrListCast(this.View.dataDoc.tags).length && !StrListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']).length;
+ return this._isEditing && (this._props.Views.length > 1 || (selected && this.View.TagPanelEditing));
}
/**
@@ -305,7 +308,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
@action
setToEditing = (editing = true) => {
this._isEditing = editing;
- editing && this._props.Views.length === 1 && this.View.select(false);
+ if (this._props.Views.length === 1) {
+ this.View.TagPanelEditing = editing;
+ editing && this.View.select(false);
+ }
};
/**
@@ -346,10 +352,19 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
<div
className="tagsView-container"
- ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
+ ref={r =>
+ r &&
+ new ResizeObserver(
+ action(() => {
+ if (this._props.Views.length === 1) {
+ this.View.TagPanelHeight = Math.floor(r?.children[0].children[0].getBoundingClientRect().height ?? 0) - Math.floor(r?.children[0].children[0].children[0].getBoundingClientRect().height ?? 0);
+ }
+ })
+ ).observe(r?.children[0])
+ }
style={{
display: SnappingManager.IsResizing === this.View.Document[Id] ? 'none' : undefined,
- backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
+ backgroundColor: this.isEditing ? this._props.background : Colors.TRANSPARENT,
borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
height: !this._props.Views.lastElement()?.isSelected() ? 0 : undefined,
}}>
@@ -357,18 +372,21 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
<div className="tagsView-list">
{this._props.Views.length === 1 && !this.View.showTags ? null : ( //
<IconButton
- style={{ width: '8px' }}
+ style={{ width: '8px', height: this._props.Views.lastElement().TagBtnHeight }}
tooltip="Close Menu"
onPointerDown={e =>
setupMoveUpEvents(this, e, returnFalse, emptyFunction, upEv => {
- this.setToEditing(!this._isEditing);
+ this.setToEditing(!this.isEditing);
upEv.stopPropagation();
})
}
- icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
+ type={Type.TERT}
+ background="transparent"
+ color={this.View._props.styleProvider?.(this.View.Document, this.View.ComponentView?._props, StyleProp.FontColor) as string}
+ icon={<FontAwesomeIcon icon={this.isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
/>
)}
- <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
+ <IconTagBox Views={this._props.Views} IsEditing={this.isEditing} />
{Array.from(tagsList)
.filter(tag => (tag.startsWith('#') || tag.startsWith('@')) && !Doc.MyFilterHotKeys.some(key => key.toolType === tag))
.map(tag => (
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 756b37f99..50de7c601 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -23,7 +23,8 @@ import { undoable, UndoManager } from '../../util/UndoManager';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
import { TagItem } from '../TagsView';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FocusViewOptions';
import './CollectionCardDeckView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 544b3e262..962b590c8 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -1,12 +1,16 @@
.collectionCarouselView-outer {
height: 100%;
- position: relative;
+ position: absolute;
overflow: hidden;
display: flex;
+ transform-origin: top left;
+
.collectionCarouselView-caption {
height: 50;
display: inline-block;
width: 100%;
+ bottom: 0;
+ position: absolute;
}
.collectionCarouselView-image {
height: calc(100% - 50px);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index a7d217076..2d3f8cb0e 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -115,12 +115,14 @@ export class CollectionCarouselView extends CollectionSubView() {
? false
: undefined; // prettier-ignore
onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+
renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
return (
<DocumentView
{...this._props}
ref={overlayFunc}
Document={doc}
+ TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined}
NativeWidth={returnZero}
NativeHeight={returnZero}
fitWidth={this._props.childLayoutFitWidth}
@@ -136,7 +138,6 @@ export class CollectionCarouselView extends CollectionSubView() {
renderDepth={this._props.renderDepth + 1}
LayoutTemplate={this._props.childLayoutTemplate}
LayoutTemplateString={this._props.childLayoutString}
- TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)}
childFilters={this.childDocFilters}
focus={this.focus}
hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
@@ -182,7 +183,15 @@ export class CollectionCarouselView extends CollectionSubView() {
}
@computed get content() {
- const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
+ const captionProps = {
+ ...this._props, //
+ NativeScaling: returnOne,
+ PanelWidth: this.captionWidth,
+ fieldKey: 'caption',
+ setHeight: undefined,
+ setContentView: undefined,
+ noSidebar: true,
+ };
const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
return !this.curDoc() ? null : (
<>
@@ -242,11 +251,9 @@ export class CollectionCarouselView extends CollectionSubView() {
color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
left: NumCast(this.layoutDoc._xMargin),
top: NumCast(this.layoutDoc._yMargin),
- transformOrigin: 'top left',
transform: `scale(${this.nativeScaling()})`,
width: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._xMargin)) / this.nativeScaling()}px)`,
height: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._yMargin)) / this.nativeScaling()}px)`,
- position: 'relative',
}}>
{this.content}
{this.navButtons}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index c79610595..1576a8e4a 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -23,6 +23,7 @@ import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
import './CollectionMenu.scss';
import { CollectionLinearView } from './collectionLinear';
+import { SettingsManager } from '../../util/SettingsManager';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -118,7 +119,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
<div className="hardCodedButtons">
<Toggle
toggleType={ToggleType.BUTTON}
- type={Type.PRIM}
+ type={Type.TERT}
color={SnappingManager.userColor}
onClick={this.props.toggleTopBar}
toggleStatus={this.props.topBarHeight() > 0}
@@ -127,7 +128,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
/>
<Toggle
toggleType={ToggleType.BUTTON}
- type={Type.PRIM}
+ type={Type.TERT}
color={SnappingManager.userColor}
onClick={this._props.togglePropertiesFlyout}
toggleStatus={SnappingManager.PropertiesWidth > 0}
@@ -138,19 +139,17 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
);
// dash col linear view buttons
- const contMenuButtons = (
+ return (
<div
className="collectionMenu-container"
style={{
- background: SnappingManager.userBackgroundColor,
+ background: SettingsManager.userBackgroundColor,
// borderColor: SettingsManager.userColor
}}>
{this.contMenuButtons}
{hardCodedButtons}
</div>
);
-
- return contMenuButtons;
}
}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 01695dbaf..173147f64 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -258,11 +258,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
return (
<DocumentView
- ref={r => {
- dref = r || undefined;
- }}
+ ref={r => (dref = r || undefined)}
Document={doc}
- TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
+ TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined}
pointerEvents={this.blockPointerEventsWhenDragging}
renderDepth={this._props.renderDepth + 1}
PanelWidth={width}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index fd48a9dc1..4f60acb18 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -112,7 +112,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@computed get columnWidth() {
const availableWidth = this._props.PanelWidth() - 2 * this.xMargin;
const cwid = availableWidth / (NumCast(this.Document._layout_columnCount) || this._props.PanelWidth() / NumCast(this.Document._layout_columnWidth, this._props.PanelWidth() / 4));
- return Math.min(availableWidth, this.isStackingView ? Number.MAX_VALUE : cwid - this.gridGap);
+ return Math.min(availableWidth, this.isStackingView ? availableWidth / (this.numGroupColumns || 1) : cwid - this.gridGap);
}
@computed get NodeWidth() {
@@ -675,12 +675,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return35 = () => 35;
+ @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore
@computed get buttonMenu() {
- const menuDoc = DocCast(this.layoutDoc.layout_headerButton);
- return !menuDoc ? null : (
- <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+ return !this.menuBtnDoc ? null : (
+ <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}>
<DocumentView
- Document={menuDoc}
+ Document={this.menuBtnDoc}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
addDocument={this._props.addDocument}
@@ -728,13 +728,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
SetValue: this.addGroup,
contents: '+ ADD A GROUP',
};
- const buttonMenu = this.layoutDoc.layout_headerButton;
const noviceExplainer = this.layoutDoc.layout_explainer;
return (
<>
- {buttonMenu || noviceExplainer ? (
+ {this.menuBtnDoc || noviceExplainer ? (
<div className="documentButtonMenu">
- {buttonMenu ? this.buttonMenu : null}
+ {this.menuBtnDoc ? this.buttonMenu : null}
{Doc.noviceMode && noviceExplainer ? <div className="documentExplanation">{StrCast(noviceExplainer)}</div> : null}
</div>
) : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3b9d167c6..66839ba7f 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, NumCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
@@ -26,6 +26,7 @@ import { EditableView } from '../EditableView';
import { DocumentView } from '../nodes/DocumentView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import './CollectionStackingView.scss';
+import { DocData } from '../../../fields/DocSymbols';
// So this is how we are storing a column
interface CSVFieldColumnProps {
@@ -270,7 +271,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
event: () => {
const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
if (created) {
- const container = this._props.Doc.resolvedDataDoc ? Doc.GetProto(this._props.Doc) : this._props.Doc;
+ const container = DocCast(this._props.Doc.rootDocument)?.[DocData] ? Doc.GetProto(this._props.Doc) : this._props.Doc;
if (container.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, container);
return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
@@ -304,10 +305,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
@computed get innards() {
TraceMobx();
const key = this._props.pivotField;
- const headings = this._props.headings();
const heading = this._heading;
const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
- const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader);
const headingView = this._props.headingObject ? (
@@ -317,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
ref={this._headerRef}
style={{
marginTop: this._props.yMargin,
- width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
+ width: this._props.columnWidth
}}>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
@@ -360,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
margin: 'auto',
marginTop: this._props.dontCenter.includes('y') ? undefined : 'auto',
marginBottom: this._props.dontCenter.includes('y') ? undefined : 'auto',
- width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
+ width: this._props.columnWidth,
}}>
<div
key={`${heading}-stack`}
@@ -368,7 +367,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: this._props.dontCenter.includes('x') ? undefined : 'auto',
- // width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
gridGap: this._props.gridGap,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a43cf0755..375c0fe53 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -13,6 +13,7 @@ import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { Upload } from '../../../server/SharedMediaTypes';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { DocUtils } from '../../documents/DocUtils';
@@ -24,11 +25,11 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+import { DocumentView } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FlashcardPracticeUI } from './FlashcardPracticeUI';
import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
-import { Upload } from '../../../server/SharedMediaTypes';
+import { FlashcardPracticeUI } from './FlashcardPracticeUI';
export enum docSortings {
Time = 'time',
@@ -108,11 +109,11 @@ export function CollectionSubView<X>() {
}
get dataDoc() {
- return this._props.TemplateDataDocument instanceof Doc && this.layoutDoc.isTemplateForField //
- ? this._props.TemplateDataDocument[DocData]
- : this.layoutDoc.resolvedDataDoc
- ? this._props.Document
- : this.Document[DocData]; // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+ return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField //
+ ? Doc.GetProto(this._props.TemplateDataDocument)
+ : this.Document.rootDocument
+ ? this.Document
+ : this.Document[DocData]; // if the layout document has a rootDocument, then we don't want to get its parent which would be the unexpanded template
}
get childContainerViewPath() {
@@ -131,9 +132,9 @@ export function CollectionSubView<X>() {
hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
@computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
- const { Document: Document, TemplateDataDocument } = this._props;
+ const { TemplateDataDocument } = this._props;
const validPairs = this.childDocs
- .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
+ .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
.filter(
pair =>
// filter out any documents that have a proto that we don't have permissions to
@@ -299,7 +300,7 @@ export function CollectionSubView<X>() {
const dragData = de.complete.docDragData;
if (dragData) {
const sourceDragAction = dragData.dropAction;
- const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._renderDoc);
+ const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this.Document);
dragData.dropAction = !sameCollection // if doc from another tree
? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's
: sourceDragAction === dropActionType.inPlace // if source drag is inPlace
@@ -589,7 +590,7 @@ export function CollectionSubView<X>() {
/**
* How much the content of the collection is being scaled based on its nesting and its fit-to-width settings
*/
- @computed get contentScaling() { return this.ScreenToLocalBoxXf().Scale; } // prettier-ignore
+ @computed get contentScaling() { return this.ScreenToLocalBoxXf().Scale * (!this._props.fitWidth?.(this.Document) ? this._props.NativeDimScaling?.()||1: 1); } // prettier-ignore
/**
* The maximum size a UI widget can be in collection coordinates based on not wanting the widget to visually obscure too much of the collection
* This takes the desired screen space size and converts into collection coordinates. It then returns the smaller of the converted
@@ -602,7 +603,12 @@ export function CollectionSubView<X>() {
*/
@computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore
- screenXPadding = () => (this.uiBtnScaling * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale;
+ screenXPadding = (docView?: DocumentView) => {
+ if (!docView) return 0;
+ const diff = this._props.PanelWidth() - docView.PanelWidth();
+ const xpad1 = this.uiBtnScaling * (this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) - diff / 2; // this._sideBtnWidth;
+ return xpad1 / (docView.NativeDimScaling?.() || 1);
+ };
filteredChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
childDocsFunc = () => this.childDocs;
@action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1960e12bd..962fbdcd7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
@@ -187,7 +187,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@action
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
- if (this.Document.resolvedDataDoc instanceof Promise) return false;
+ if (this.Document.rootDocument instanceof Promise) return false;
const doclist = toList(docs);
const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist);
res &&
@@ -200,7 +200,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
onContextMenu = (): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
const layoutItems: ContextMenuProps[] = [];
- const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc;
+ const menuDoc = ScriptCast(this.menuBtnDoc?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc;
menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' });
if (!Doc.noviceMode) {
layoutItems.push({
@@ -347,14 +347,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
return35 = () => 35;
+ @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore
@computed get buttonMenu() {
- const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null);
// To create a multibutton menu add a CollectionLinearView
- return !menuDoc ? null : (
- <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+ return !this.menuBtnDoc ? null : (
+ <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}>
<DocumentView
- Document={menuDoc}
- TemplateDataDocument={menuDoc}
+ Document={this.menuBtnDoc}
+ TemplateDataDocument={this.menuBtnDoc}
isContentActive={this._props.isContentActive}
isDocumentActive={returnTrue}
addDocument={this._props.addDocument}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7ba8989ce..c9af92a1b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -130,7 +130,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
{ description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns' },
{ description: 'Pivot', event: () => func(CollectionViewType.Pivot), icon: 'columns' },
{ description: 'Time', event: () => func(CollectionViewType.Time), icon: 'columns' },
- { description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' },
{ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' },
];
@@ -162,7 +161,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
if (this.Document.childClickedOpenTemplateView instanceof Doc) {
optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
- !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore
+ !Doc.noviceMode && optionItems.push({ description: `${this.dataDoc.$isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.dataDoc.$isLightbox = !this.dataDoc.$isLightbox; }, icon: 'project-diagram' }); // prettier-ignore
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index c071c5fb8..f24a8acb7 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -7,13 +7,16 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { returnFalse, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
import './FlashcardPracticeUI.scss';
+import { StyleProp } from '../StyleProp';
+import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
export enum practiceMode {
PRACTICE = 'practice',
@@ -60,8 +63,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
@computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
@computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
- btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale);
- btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
+ btnHeight = () => NumCast(this.filterDoc?.height);
+ btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width));
/**
* Sets the practice mode answer style for flashcards
@@ -131,7 +134,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
<div
className="FlashcardPracticeUI-practiceModes"
style={{
- transform: this._props.ScreenToLocalBoxXf().Scale >= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`,
+ background: SnappingManager.userVariantColor,
}}>
<MultiToggle
tooltip="Practice flashcards one at a time"
@@ -139,7 +142,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
color={SnappingManager.userColor}
background={SnappingManager.userVariantColor}
multiSelect={false}
- isToggle={false}
toggleStatus={!!this.practiceMode}
label="Practice"
items={[
@@ -159,7 +161,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
color={SnappingManager.userColor}
background={SnappingManager.userVariantColor}
multiSelect={false}
- isToggle={false}
toggleStatus={!!this.practiceMode}
label={StrCast(this._props.layoutDoc.revealOp, flashcardRevealOp.FLIP)}
items={[
@@ -179,6 +180,12 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
</div>
);
}
+ childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
+ if (doc instanceof Doc && property === StyleProp.BackgroundColor) {
+ return SnappingManager.userVariantColor;
+ }
+ return this._props.docViewProps().styleProvider?.(doc, props, property);
+ };
tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
render() {
return (
@@ -186,24 +193,27 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
{this.emptyMessage}
{this.practiceButtons}
{this._props.layoutDoc._chromeHidden ? null : (
- <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaling})` }}>
+ <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnWidth(), transform: `scale(${this._props.uiBtnScaling})` }}>
{!this.filterDoc ? null : (
- <DocumentView
- {...this._props.docViewProps()}
- Document={this.filterDoc}
- TemplateDataDocument={undefined}
- PanelWidth={this.btnWidth}
- PanelHeight={this.btnHeight}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)}
- hideCaptions={true}
- hideFilterStatus={true}
- renderDepth={this._props.renderDepth + 1}
- fitWidth={undefined}
- showTags={false}
- setContentViewBox={undefined}
- />
+ <div style={{ background: SnappingManager.userVariantColor }}>
+ <DocumentView
+ {...this._props.docViewProps()}
+ Document={this.filterDoc}
+ TemplateDataDocument={undefined}
+ PanelWidth={this.btnWidth}
+ PanelHeight={this.btnHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)}
+ hideCaptions={true}
+ hideFilterStatus={true}
+ renderDepth={this._props.renderDepth + 1}
+ styleProvider={this.childStyleProvider}
+ fitWidth={undefined}
+ showTags={false}
+ setContentViewBox={undefined}
+ />
+ </div>
)}
{this.practiceModesMenu}
</div>
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 397e35ca9..931cdac2b 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -4,7 +4,6 @@
height: 100%;
width: 100%;
}
-
input.lm_title:focus,
input.lm_title {
max-width: unset !important;
@@ -22,7 +21,6 @@ input.lm_title {
.lm_iconWrap {
display: flex;
color: black;
- width: 15px;
height: 15px;
align-items: center;
align-self: center;
@@ -30,6 +28,16 @@ input.lm_title {
margin: 3px;
border-radius: 20%;
+ width: auto;
+ svg:nth-of-type(2) {
+ display: none;
+ }
+ &:hover {
+ svg:nth-of-type(2) {
+ display: block;
+ }
+ }
+
.moreInfoDot {
background-color: white;
border-radius: 100%;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 620be2726..568a08792 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,7 +10,7 @@ import ResizeObserver from 'resize-observer-polyfill';
import { ClientUtils, DashColor, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { FieldId } from '../../../fields/RefField';
@@ -41,6 +41,7 @@ import { CollectionDockingView } from './CollectionDockingView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { Tooltip } from '@mui/material';
interface TabMinimapViewProps {
doc: Doc;
@@ -311,7 +312,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@observable _view: DocumentView | undefined = undefined;
@observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver. this hack allows the resize observer to trigger a react update
- @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore
+ @computed get layoutDoc() { return this._document?.[DocLayout]; } // prettier-ignore
@computed get isUserActivated() { return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore
@computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore
@@ -353,7 +354,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
if (tab.element[0].children[1].children.length === 1) {
iconWrap.className = 'lm_iconWrap lm_moreInfo';
- iconWrap.title = 'click for menu, drag to embed in document';
const dragBtnDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -377,7 +377,26 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
);
};
- const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
+ const docIcon = (
+ <>
+ <Tooltip title="click for menu, drag to embed in document">
+ <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />
+ </Tooltip>
+ <Tooltip title="click to open in lightbox">
+ <FontAwesomeIcon
+ onPointerDown={dragBtnDown}
+ icon="external-link-alt"
+ onClick={() => {
+ if (doc.layout_fieldKey === 'layout_icon') {
+ const odoc = Doc.GetEmbeddings(doc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(doc);
+ Doc.deiconifyView(odoc);
+ }
+ this.addDocTab(doc, OpenWhere.lightboxAlways);
+ }}
+ />
+ </Tooltip>
+ </>
+ );
const closeIcon = <FontAwesomeIcon icon="eye" />;
ReactDOM.createRoot(iconWrap).render(docIcon);
ReactDOM.createRoot(closeWrap).render(closeIcon);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index cb7da8c84..4dc937864 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
@@ -156,7 +156,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return this.Document[DocData];
}
@computed get layoutDoc() {
- return Doc.Layout(this.Document);
+ return this.Document[DocLayout];
}
@computed get fieldKey() {
return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
@@ -594,7 +594,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const key = match[1];
const assign = match[2];
const val = match[3];
- Doc.SetField(doc, key, assign + val, false);
+ Doc.SetField(doc, key, assign + val);
return true;
}
return false;
@@ -1309,7 +1309,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
- const childLayout = Doc.Layout(pair.layout);
+ const childLayout = pair.layout[DocLayout];
const rowHeight = () => {
const aspect = Doc.NativeAspect(childLayout);
return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
index 3838852dd..903d92c90 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
@@ -31,16 +31,14 @@ export class CollectionFreeFormClusters {
get selectDocuments() { return this._view.selectDocuments; } // prettier-ignore
static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
- const doc2Layout = Doc.Layout(doc2);
- const doc1Layout = Doc.Layout(doc1);
const x2 = NumCast(doc2.x) - clusterDistance;
const y2 = NumCast(doc2.y) - clusterDistance;
- const w2 = NumCast(doc2Layout._width) + clusterDistance;
- const h2 = NumCast(doc2Layout._height) + clusterDistance;
+ const w2 = NumCast(doc2._width) + clusterDistance;
+ const h2 = NumCast(doc2._height) + clusterDistance;
const x = NumCast(doc1.x) - clusterDistance;
const y = NumCast(doc1.y) - clusterDistance;
- const w = NumCast(doc1Layout._width) + clusterDistance;
- const h = NumCast(doc1Layout._height) + clusterDistance;
+ const w = NumCast(doc1._width) + clusterDistance;
+ const h = NumCast(doc1._height) + clusterDistance;
return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
}
handlePointerDown(probe: number[]) {
@@ -49,12 +47,11 @@ export class CollectionFreeFormClusters {
.reduce((cluster, cd) => {
const grouping = this.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
if (grouping !== -1) {
- const layoutDoc = Doc.Layout(cd);
const cx = NumCast(cd.x) - this._clusterDistance / 2;
const cy = NumCast(cd.y) - this._clusterDistance / 2;
- const cw = NumCast(layoutDoc._width) + this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + this._clusterDistance;
- return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
+ const cw = NumCast(cd._width) + this._clusterDistance;
+ const ch = NumCast(cd._height) + this._clusterDistance;
+ return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
}
return cluster;
}, -1);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 4ea1de680..158bac7ba 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,5 +1,6 @@
/* eslint-disable no-use-before-define */
import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
+import { DocLayout } from '../../../../fields/DocSymbols';
import { Id, ToString } from '../../../../fields/FieldSymbols';
import { ObjectField } from '../../../../fields/ObjectField';
import { RefField } from '../../../../fields/RefField';
@@ -237,7 +238,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
payload: val,
});
val?.docs.forEach((doc, i) => {
- const layoutDoc = Doc.Layout(doc);
+ const layoutDoc = doc[DocLayout];
let wid = pivotAxisWidth;
let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
if (hgt > pivotAxisWidth) {
@@ -249,7 +250,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
- backgroundColor: StrCast(layoutDoc.backgroundColor, 'white'),
+ backgroundColor: StrCast(doc.backgroundColor, 'white'),
pair: { layout: doc },
replica: val.replicas[i],
});
@@ -362,7 +363,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
keyDocs.forEach(doc => {
const stack = findStack(x, stacking);
- const layoutDoc = Doc.Layout(doc);
+ const layoutDoc = doc[DocLayout];
let wid = pivotAxisWidth;
let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
if (hgt > pivotAxisWidth) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cce0ff684..6c47a71b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,7 +41,7 @@
transition: background-color 1s ease 0s;
}
.collectionfreeformview-mask {
- mix-blend-mode: multiply;
+ mix-blend-mode: hard-light;
background-color: rgba(0, 0, 0, 0.8);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7fd5d71fd..626538976 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -11,7 +11,7 @@ import ReactLoading from 'react-loading';
import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
-import { DocData, Height, Width } from '../../../../fields/DocSymbols';
+import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkEraserTool, InkField, InkInkTool, InkTool, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(this.Document.freeform_panX, 1));
panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(this.Document.freeform_panY, 1));
- zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.rootDocument)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -440,7 +440,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
docDragData.droppedDocuments.forEach((d, i) => {
- const layoutDoc = Doc.Layout(d);
+ const layoutDoc = d[DocLayout];
const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
if (this.Document._currentFrame !== undefined) {
CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
@@ -1440,9 +1440,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
- const layoutdoc = Doc.Layout(doc);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
- const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height));
+ const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(doc._width), NumCast(doc.y) + NumCast(doc._height));
const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
if (scale !== undefined) {
@@ -1489,9 +1488,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
* @returns whether the new text doc was created and added successfully
*/
createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => {
- const textDoc = DocCast(textBox.Document.rootDocument, textBox.Document);
+ const textDoc = DocCast(textBox.Document);
const newDoc = Doc.MakeCopy(textDoc, true);
- newDoc['$' + Doc.LayoutFieldKey(newDoc, textBox._props.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+ newDoc['$' + Doc.LayoutFieldKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style
newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
DocumentView.SetSelectOnLoad(newDoc);
@@ -1588,7 +1587,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
case undefined:
case OpenWhere.lightbox:
- if (this.layoutDoc._isLightbox) {
+ if (this.dataDoc.$isLightbox) {
this._lightboxDoc = docs[0];
return true;
}
@@ -1600,14 +1599,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
const childDoc = pair.layout;
- const childDocLayout = Doc.Layout(childDoc);
const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
- const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
- const { z, zIndex } = childDoc;
+ const contentFrameNumber = Cast(childDoc._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { z, zIndex, stroke_isInkMask } = childDoc;
const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
const { x, y, autoDim, _width, _height, opacity, _rotation } =
layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
- ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
+ ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDoc._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
: CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
// prettier-ignore
const rotation = Cast(_rotation,'number',
@@ -1625,8 +1624,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
- transition: StrCast(childDocLayout.dataTransition),
- showTags: BoolCast(childDocLayout.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags),
+ transition: StrCast(childDoc.dataTransition),
+ showTags: BoolCast(childDoc.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags),
pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
pair,
replica: '',
@@ -1719,7 +1718,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
elements.push({
ele: this.getChildDocView(entry[1]),
bounds: entry[1].opacity === 0 ? { payload: undefined, type: '', ...entry[1], width: 0, height: 0 } : { payload: undefined, type: '', ...entry[1] },
- inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
+ inkMask: BoolCast(entry[1].pair.layout?.$stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
})
);
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index b40189d76..c7aa410c6 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -11,7 +11,7 @@ import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { List } from '../../../../fields/List';
-import { DocCast, ImageCast, NumCast, StrCast } from '../../../../fields/Types';
+import { DocCast, ImageCastToNameType, NumCast, StrCast } from '../../../../fields/Types';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
@@ -187,7 +187,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
})}>
{FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png'];
+ const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]) ?? ['-missing-', '.png'];
return (
<div
className="image-wrapper"
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index c983d7c26..7c8ccb92d 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -10,7 +10,7 @@ import { imageUrlToBase64 } from '../../../../ClientUtils';
import { Utils, numberRange } from '../../../../Utils';
import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
-import { ImageCast } from '../../../../fields/Types';
+import { ImageCastToNameType, ImageCastWithSuffix } from '../../../../fields/Types';
import { gptGetEmbedding, gptImageLabel } from '../../../apis/gpt/GPT';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
@@ -165,8 +165,8 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
const imageInfos = this._selectedImages.map(async doc => {
if (!doc.$tags_chat) {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
- return imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
+ const url = ImageCastWithSuffix(doc[Doc.LayoutFieldKey(doc)], '_o') ?? '';
+ return imageUrlToBase64(url).then(hrefBase64 =>
!hrefBase64 ? undefined :
gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels =>
({ doc, labels }))) ; // prettier-ignore
@@ -199,13 +199,11 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
groupImagesInBox = action(async () => {
this.startLoading();
- for (const doc of this._selectedImages) {
- for (let index = 0; index < (doc.$tags_chat as List<string>).length; index++) {
- const label = (doc.$tags_chat as List<string>)[index];
- const embedding = await gptGetEmbedding(label);
- doc[`$tags_embedding_${index + 1}`] = new List<number>(embedding);
- }
- }
+ await Promise.all(
+ this._selectedImages
+ .map(doc => ({ doc, labels: doc.$tags_chat as List<string> }))
+ .map(({ doc, labels }) => labels.map((label, index) => gptGetEmbedding(label).then(embedding => (doc[`$tags_embedding_${index + 1}`] = new List<number>(embedding)))))
+ );
const labelToEmbedding = new Map<string, number[]>();
// Create embeddings for the labels.
@@ -312,7 +310,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
{this._displayImageInformation ? (
<div className="image-information-list">
{this._selectedImages.map(doc => {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
+ const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]);
return (
<div className="image-information" style={{ borderColor: SettingsManager.userColor }} key={Utils.GenerateGuid()}>
<img
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index eaa8826ed..5fbf72f39 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -8,7 +8,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { GetEffectiveAcl } from '../../../../fields/util';
import { DocUtils } from '../../../documents/DocUtils';
@@ -18,7 +18,6 @@ import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManag
import { Transform } from '../../../util/Transform';
import { UndoManager, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
-import { MainView } from '../../MainView';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { MarqueeViewBounds } from '../../PinFuncs';
import { PreviewCursor } from '../../PreviewCursor';
@@ -442,7 +441,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
if (groupButton) {
this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG);
ImageLabelBoxData.Instance.setData(this._selectedDocs);
- MainView.Instance.expandFlyout(groupButton);
+ ScriptCast(groupButton.onClick)?.script.run({ this: groupButton });
}
};
@@ -591,8 +590,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
marqueeSelect = (selectBackgrounds: boolean = false, docType: DocumentType | undefined = undefined) => {
const selection: Doc[] = [];
const selectFunc = (doc: Doc) => {
- const layoutDoc = Doc.Layout(doc);
- const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) };
+ const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) };
if (!this._lassoFreehand) {
intersectRect(bounds, this.Bounds) && selection.push(doc);
} else {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e7f1de7f1..a7603a45b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -182,17 +182,17 @@ export class CollectionGridView extends CollectionSubView() {
isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
/**
*
- * @param layout
+ * @param childLayout
* @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform
* @param width
* @param height
* @returns the `ContentFittingDocumentView` of the node
*/
- getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => (
+ getDisplayDoc = (childLayout: Doc, dxf: () => Transform, width: () => number, height: () => number) => (
<DocumentView
{...this._props}
- Document={layout}
- TemplateDataDocument={layout.resolvedDataDoc as Doc}
+ Document={childLayout}
+ TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
NativeWidth={returnZero}
NativeHeight={returnZero}
fitWidth={this._props.childLayoutFitWidth}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 8aae24df0..adaceb053 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,14 +1,17 @@
+import { Button, IconButton } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { Button, IconButton } from '@dash/components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { FaChevronRight } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoable } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
@@ -16,8 +19,6 @@ import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'
import './CollectionMulticolumnView.scss';
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
-import { dropActionType } from '../../../util/DropActionTypes';
-import { SnappingManager } from '../../../util/SnappingManager';
interface WidthSpecifier {
magnitude: number;
@@ -197,16 +198,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- if (this.columnUnitLength === undefined) {
- return Transform.Identity(); // we're still waiting on promises to resolve
- }
- let offset = 0;
- // eslint-disable-next-line no-restricted-syntax
- for (const { layout: candidate } of this.childLayoutPairs) {
- if (candidate === layout) {
- return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+ if (this.columnUnitLength !== undefined) {
+ let offset = 0;
+ for (const { layout: candidate } of this.childLayoutPairs) {
+ if (candidate === layout) {
+ return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+ }
+ offset += this.lookupPixels(candidate) + resizerWidth;
}
- offset += this.lookupPixels(candidate) + resizerWidth;
}
return Transform.Identity();
};
@@ -262,50 +261,51 @@ export class CollectionMulticolumnView extends CollectionSubView() {
? true
: undefined;
};
- getDisplayDoc = (childLayout: Doc) => {
- const width = () => this.lookupPixels(childLayout);
- const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
- const dxf = () =>
- this.lookupIndividualTransform(childLayout)
+ childHeight = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+ childWidth = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc));
+ childXf = computedFn(
+ (childDoc: Doc) => () =>
+ this.lookupIndividualTransform(childDoc)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
- .scale(this._props.NativeDimScaling?.() || 1);
- return (
- <DocumentView
- Document={childLayout}
- TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
- styleProvider={this._props.styleProvider}
- containerViewPath={this.childContainerViewPath}
- LayoutTemplate={this._props.childLayoutTemplate}
- LayoutTemplateString={this._props.childLayoutString}
- renderDepth={this._props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- rootSelected={this.rootSelected}
- dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
- onClickScript={this.onChildClickHandler}
- onDoubleClickScript={this.onChildDoubleClickHandler}
- suppressSetHeight
- ScreenToLocalTransform={dxf}
- isContentActive={this.isChildContentActive}
- isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
- hideDecorationTitle={this._props.childHideDecorationTitle}
- fitContentsToBox={this._props.fitContentsToBox}
- focus={this._props.focus}
- childFilters={this.childDocFilters}
- childFiltersByRanges={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- dontRegisterView={this._props.dontRegisterView}
- addDocument={this._props.addDocument}
- moveDocument={this._props.moveDocument}
- removeDocument={this._props.removeDocument}
- whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- addDocTab={this._props.addDocTab}
- pinToPres={this._props.pinToPres}
- dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
- />
- );
- };
+ .scale(this._props.NativeDimScaling?.() || 1)
+ );
+ getDisplayDoc = (childLayout: Doc) => (
+ <DocumentView
+ Document={childLayout}
+ TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
+ styleProvider={this._props.styleProvider}
+ containerViewPath={this.childContainerViewPath}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ renderDepth={this._props.renderDepth + 1}
+ PanelWidth={this.childWidth(childLayout)}
+ PanelHeight={this.childHeight}
+ rootSelected={this.rootSelected}
+ dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
+ onClickScript={this.onChildClickHandler}
+ onDoubleClickScript={this.onChildDoubleClickHandler}
+ suppressSetHeight
+ ScreenToLocalTransform={this.childXf(childLayout)}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ fitContentsToBox={this._props.fitContentsToBox}
+ focus={this._props.focus}
+ childFilters={this.childDocFilters}
+ childFiltersByRanges={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ dontRegisterView={this._props.dontRegisterView}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
+ dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
+ />
+ );
+
/**
* @returns the resolved list of rendered child documents, displayed
* at their resolved pixel widths, each separated by a resizer.
@@ -315,7 +315,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
const collector: JSX.Element[] = [];
this.childLayouts.forEach((layout, i) => {
collector.push(
- // eslint-disable-next-line react/no-array-index-key
<Tooltip title={'Doc: ' + StrCast(layout.title)} key={'wrapper' + i}>
<div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
{this.getDisplayDoc(layout)}
@@ -327,7 +326,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
</Tooltip>,
<ResizeBar
width={resizerWidth}
- // eslint-disable-next-line react/no-array-index-key
key={'resizer' + i}
styleProvider={this._props.styleProvider}
isContentActive={this._props.isContentActive}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bda8e91ac..27c41583c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,5 +1,6 @@
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -178,16 +179,14 @@ export class CollectionMultirowView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- if (this.rowUnitLength === undefined) {
- return Transform.Identity(); // we're still waiting on promises to resolve
- }
- let offset = 0;
- // eslint-disable-next-line no-restricted-syntax
- for (const { layout: candidate } of this.childLayoutPairs) {
- if (candidate === layout) {
- return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+ if (this.rowUnitLength !== undefined) {
+ let offset = 0;
+ for (const { layout: candidate } of this.childLayoutPairs) {
+ if (candidate === layout) {
+ return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+ }
+ offset += this.lookupPixels(candidate) + resizerHeight;
}
- offset += this.lookupPixels(candidate) + resizerHeight;
}
return Transform.Identity(); // type coersion, this case should never be hit
};
@@ -243,49 +242,51 @@ export class CollectionMultirowView extends CollectionSubView() {
? true
: undefined;
};
- getDisplayDoc = (layout: Doc) => {
- const height = () => this.lookupPixels(layout);
- const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
- const dxf = () =>
- this.lookupIndividualTransform(layout)
+ childHeight = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc));
+ childWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+ childXf = computedFn(
+ (childDoc: Doc) => () =>
+ this.lookupIndividualTransform(childDoc)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
- .scale(this._props.NativeDimScaling?.() || 1);
- return (
- <DocumentView
- Document={layout}
- TemplateDataDocument={layout.resolvedDataDoc as Doc}
- styleProvider={this._props.styleProvider}
- containerViewPath={this.childContainerViewPath}
- LayoutTemplate={this._props.childLayoutTemplate}
- LayoutTemplateString={this._props.childLayoutString}
- renderDepth={this._props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- rootSelected={this.rootSelected}
- dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
- onClickScript={this.onChildClickHandler}
- onDoubleClickScript={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={dxf}
- isContentActive={this.isChildContentActive}
- isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
- hideDecorationTitle={this._props.childHideDecorationTitle}
- fitContentsToBox={this._props.fitContentsToBox}
- focus={this._props.focus}
- childFilters={this.childDocFilters}
- childFiltersByRanges={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- dontRegisterView={this._props.dontRegisterView}
- addDocument={this._props.addDocument}
- moveDocument={this._props.moveDocument}
- removeDocument={this._props.removeDocument}
- whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- addDocTab={this._props.addDocTab}
- pinToPres={this._props.pinToPres}
- dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'}
- />
- );
- };
+ .scale(this._props.NativeDimScaling?.() || 1)
+ );
+
+ getDisplayDoc = (childLayout: Doc) => (
+ <DocumentView
+ Document={childLayout}
+ TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
+ styleProvider={this._props.styleProvider}
+ containerViewPath={this.childContainerViewPath}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ renderDepth={this._props.renderDepth + 1}
+ PanelWidth={this.childWidth}
+ PanelHeight={this.childHeight(childLayout)}
+ rootSelected={this.rootSelected}
+ dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
+ onClickScript={this.onChildClickHandler}
+ onDoubleClickScript={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={this.childXf(childLayout)}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ fitContentsToBox={this._props.fitContentsToBox}
+ focus={this._props.focus}
+ childFilters={this.childDocFilters}
+ childFiltersByRanges={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ dontRegisterView={this._props.dontRegisterView}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
+ dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'}
+ />
+ );
+
/**
* @returns the resolved list of rendered child documents, displayed
* at their resolved pixel widths, each separated by a resizer.
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 05670562e..5803acca0 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -200,7 +200,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._props.setContentViewBox?.(this);
document.addEventListener('keydown', this.onKeyDown);
- Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
+ Object.entries(this._documentOptions).forEach(pair => this.fieldInfos.set(pair[0], pair[1] as FInfo));
this._keysDisposer = observe(
this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
change => {
diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
index daffdf1f5..9ad94cb31 100644
--- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
@@ -85,6 +85,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
() => this._editing,
editing => {
if (editing) {
+ this.setContent((this._unrenderedContent = this._props.GetValue() ?? ''));
this.setupRefSelect(this.refSelectConditionMet);
} else {
this._overlayDisposer?.();
@@ -99,7 +100,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
() => this._props.GetValue(),
fieldVal => {
this._unrenderedContent = fieldVal ?? '';
- this.finalizeEdit(false, false, false);
+ this._editing && this.finalizeEdit(false, false, false);
}
);
}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index e6fe46638..173984dc7 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -32,6 +32,7 @@ import { FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaCellField } from './SchemaCellField';
+import { DocLayout } from '../../../../fields/DocSymbols';
/**
* SchemaTableCells make up the majority of the visual representation of the SchemaView.
@@ -104,13 +105,14 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
public static renderProps(props: SchemaTableCellProps) {
const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
let protoCount = 0;
- let doc: Doc | undefined = Document;
+ const layoutDoc = fieldKey.startsWith('_') ? Document[DocLayout] : Document;
+ let doc = Document;
while (doc) {
if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break;
protoCount++;
doc = DocCast(doc.proto);
}
- const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells
+ const color = layoutDoc !== Document ? 'red' : protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells
const textDecoration = '';
const fieldProps: FieldViewProps = {
childFilters: returnEmptyFilter,
@@ -130,7 +132,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
focus: emptyFunction,
addDocTab: SchemaTableCell.addFieldDoc,
pinToPres: returnZero,
- Document: DocCast(Document.rootDocument, Document),
+ Document: Document,
fieldKey: fieldKey,
PanelWidth: columnWidth,
PanelHeight: props.rowHeight,
@@ -175,7 +177,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
const inQuotes = (strField: string) => {
return (strField.startsWith('`') && strField.endsWith('`')) || (strField.startsWith("'") && strField.endsWith("'")) || (strField.startsWith('"') && strField.endsWith('"'));
};
- if (!inQuotes(this._submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1);
+ const submittedValue = this._submittedValue.startsWith(eqSymbol) ? this._submittedValue.slice(eqSymbol.length) : this._submittedValue;
+ if (!inQuotes(submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1);
return eqSymbol + modField;
};
@@ -211,8 +214,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
this._props.finishEdit?.();
return true;
}
- const hasNoLayout = Doc.IsDataProto(fieldProps.Document) ? true : undefined; // the "delegate" is a a data document so never write to it's proto
- const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, hasNoLayout);
+ const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey, value);
this._submittedValue = value;
this._props.finishEdit?.();
return ret;
diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss
index ad0c5c21d..82f6caa52 100644
--- a/src/client/views/global/globalCssVariables.module.scss
+++ b/src/client/views/global/globalCssVariables.module.scss
@@ -4,7 +4,7 @@ $white: #ffffff;
$off-white: #fdfdfd;
$light-gray: #dfdfdf;
$medium-gray: #9f9f9f;
-$medium-gray-dim: #9f9f9f30;
+$medium-gray-dim: #9f9f9f;
$dark-gray: #323232;
$black: #000000;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 79d0ee88f..32afb3d3d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -38,6 +38,7 @@ import { VideoBox } from '../nodes/VideoBox';
import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
+import { DocData } from '../../../fields/DocSymbols';
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function IsNoneSelected() {
@@ -119,7 +120,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
- return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] || defaultFill();
+ return (contentFrameNumber !== undefined ? CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] : selView.backgroundColor()) || defaultFill();
}
!selectedViews.length && setDefaultFill(color ?? 'transparent');
selectedViews.forEach(dv => {
@@ -131,10 +132,10 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
obj[fieldKey] = color;
CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
} else {
- const dataKey = Doc.LayoutFieldKey(dv.Document);
+ const colorDoc = dv.isTemplateForField ? dv.layoutDoc : dv.dataDoc; // assigning to a template's compoment field should not assign to the data doc
+ const dataKey = Doc.LayoutFieldKey(colorDoc);
const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
- dv.layoutDoc[fieldKey + alternate] = undefined;
- dv.dataDoc[fieldKey + alternate] = color;
+ colorDoc[fieldKey + alternate] = color;
}
});
} else {
@@ -367,8 +368,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
- ['dictation', { checkResult: () => !!textView?._recordingDictation,
- toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }],
+ ['dictation', { checkResult: () => !!textView?.recordingDictation,
+ toggle: () => textView && runInAction(() => { textView.recordingDictation = !textView.recordingDictation;} ) }],
['fitBox', { checkResult: () => RichTextMenu.Instance?.fitBox ?? false,
toggle: () => RichTextMenu.Instance?.toggleFitBox()}],
['elide', { checkResult: () => false,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6f86383c2..53a3d3631 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,9 +17,10 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProp';
import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps } from './DocumentView';
+import { DocumentView } from './DocumentView';
import { FieldViewProps } from './FieldView';
import { OpenWhere } from './OpenWhere';
+import { DocumentViewProps } from './DocumentContentsView';
export enum GroupActive { // flags for whether a view is activate because of its relationship to a group
group = 'group', // this is a group that is activated because it's on an active canvas, but is not part of some other group
@@ -304,7 +305,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
) : (
<DocumentView
{...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
- Document={this._renderDoc}
+ Document={this.Document}
renderDepth={this._props.renderDepth}
isContentActive={this._props.isContentActive}
childFilters={this._props.childFilters}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 992fbba66..6d5891003 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -29,7 +29,6 @@ import '../pdf/GPTPopup/GPTPopup.scss';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { TraceMobx } from '../../../fields/util';
const API_URL = 'https://api.unsplash.com/search/photos';
@@ -285,13 +284,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
};
@action handleRenderGPTClick = () => {
- const phonTrans = DocCast(this.Document.audio) ? DocCast(this.Document.audio).phoneticTranscription : undefined;
- if (phonTrans) {
- this._inputValue = StrCast(phonTrans);
- this.askGPTPhonemes(this._inputValue);
- this._renderSide = this.backKey;
- this._outputValue = '';
- } else if (this._inputValue) this.askGPT(GPTCallType.QUIZDOC);
+ if (this._inputValue) this.askGPT(GPTCallType.QUIZDOC);
};
onPointerMove = ({ movementX }: PointerEvent) => {
@@ -468,45 +461,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
};
/**
- * Gets the transcription of an audio recording by sending the
- * recording to backend.
- */
- pushInfo = () =>
- axios
- .post(
- 'http://localhost:105/recognize/', //
- { file: DocCast(this.Document.audio).$url },
- { headers: { 'Content-Type': 'application/json' } }
- )
- .then(response => {
- this.Document.phoneticTranscription = response.data.transcription;
- });
-
- /**
- * Extracts the id of the youtube video url.
- * @param url
- * @returns
- */
- getYouTubeVideoId = (url: string) => {
- const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=|\?v=)([^#&?]*).*/;
- const match = url.match(regExp);
- return match && match[2].length === 11 ? match[2] : null;
- };
-
- /**
- * Gets the transcript of a youtube video by sending the video url to the backend.
- * @returns transcription of youtube recording
- */
- youtubeUpload = async () =>
- axios
- .post(
- 'http://localhost:105/youtube/', //
- { file: this.getYouTubeVideoId(this.frontText) },
- { headers: { 'Content-Type': 'application/json' } }
- )
- .then(response => response.data.transcription);
-
- /**
* Calls GPT for each flashcard type.
*/
askGPT = async (callType: GPTCallType) => {
@@ -540,45 +494,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
layoutHeight = () => NumCast(this.layoutDoc.height, 200);
/**
- * Ask GPT for advice on how to improve speech by comparing the phonetic transcription of
- * a users audio recording with the phonetic transcription of their intended sentence.
- * @param phonemes
- */
- askGPTPhonemes = async (phonemes: string) => {
- const sentence = this.frontText;
- const phon6 = 'huː ɑɹ juː tədeɪ';
- const phon4 = 'kamo estas hɔi';
- const promptEng =
- 'Consider all possible phonetic transcriptions of the intended sentence "' +
- sentence +
- '" that is standard in American speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
- phon6 +
- '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in American speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Only note the difference if they are not allophones of the same phoneme and if they are far away on the vowel chart. The goal is to be understood, not sound like a native speaker. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Provide a response like this: "Lets work on improving the pronunciation of "coffee." You said "ceeffee," which is close, but we need to adjust the vowel sound. In American English, "coffee" is pronounced /ˈkɔːfi/, with a long "aw" sound. Try saying "kah-fee." Your intonation is good, but try putting a bit more stress on "like" in the sentence "I would like a coffee with milk." This will make your speech sound more natural. Keep practicing, and lets try saying the whole sentence again!"';
- const promptSpa =
- 'Consider all possible phonetic transcriptions of the intended sentence "' +
- 'como estás hoy' +
- '" that is standard in Spanish speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
- phon4 +
- '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in Spanish speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Only note the difference if they are not allophones of the same phoneme and if they are far away on the vowel chart; say good job if it would be understood by a native Spanish speaker. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". Do not make "θ" and "f" interchangable. Do not make "n" and "ɲ" interchangable. Do not make "e" and "i" interchangable. If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Identify "ɔi" sounds like "oy". Ignore accents and do not say anything to the user about this.';
- const promptAll =
- 'Consider all possible phonetic transcriptions of the intended sentence "' +
- sentence +
- '" that is standard in ' +
- this.convertAbr() +
- ' speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
- phonemes +
- '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in ' +
- this.convertAbr() +
- ' speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". Do not make "θ" and "f" interchangable. Do not make "n" and "ɲ" interchangable. Do not make "e" and "i" interchangable. If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Provide a response like this: "Lets work on improving the pronunciation of "coffee." You said "cawffee," which is close, but we need to adjust the vowel sound. In American English, "coffee" is pronounced /ˈkɔːfi/, with a long "aw" sound. Try saying "kah-fee." Your intonation is good, but try putting a bit more stress on "like" in the sentence "I would like a coffee with milk." This will make your speech sound more natural. Keep practicing, and lets try saying the whole sentence again!"';
-
- switch (this._recognition.lang) {
- case 'en-US': this._outputValue = await gptAPICall(promptEng, GPTCallType.PRONUNCIATION); break;
- case 'es-ES': this._outputValue = await gptAPICall(promptSpa, GPTCallType.PRONUNCIATION); break;
- default: this._outputValue = await gptAPICall(promptAll, GPTCallType.PRONUNCIATION); break;
- } // prettier-ignore
- };
-
- /**
* Display a user's speech to text result.
* @param e
*/
@@ -637,31 +552,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
};
- testForTextFields = (whichSlot: string) => {
- const slotData = Doc.Get(this.dataDoc, whichSlot, true);
- const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
- const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
- const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
- const layoutTemplateString =
- slotHasText ? FormattedTextBox.LayoutString(whichSlot):
- whichSlot === this.frontKey ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) :
- altText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore
-
- // A bit hacky to try out the concept of using GPT to fill in flashcards
- // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
- // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.<field>).
- // eg., this.text_alternate is
- // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
- // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
- // The GPT call will put the "answer" in the second slot of the comparison (eg., text_0)
- if (whichSlot === this.backKey && !layoutTemplateString?.includes(whichSlot)) {
- const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ...
- if (queryText?.match(/\(\(.*\)\)/)) {
- Doc.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
- }
- }
- return layoutTemplateString;
- };
childActiveFunc = () => this._childActive;
contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
@@ -682,24 +572,21 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
displayDoc = (whichSlot: string) => {
const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
- return targetDoc || layoutString ? (
+ return targetDoc ? (
<>
<DocumentView
{...this._props}
- Document={layoutString ? this.Document : targetDoc}
+ Document={targetDoc}
NativeWidth={returnZero}
NativeHeight={returnZero}
renderDepth={this.props.renderDepth + 1}
- LayoutTemplateString={layoutString}
containerViewPath={this._props.docViewPath}
ScreenToLocalTransform={this.contentScreenToLocalXf}
isDocumentActive={returnFalse}
isContentActive={this.childActiveFunc}
showTags={undefined}
fitWidth={this.childFitWidth} // set to returnTrue to make images fill the comparisonBox-- should be a user option
- ignoreUsePath={layoutString ? true : undefined}
moveDocument={whichSlot === this.frontKey ? this.moveDocFront : this.moveDocBack}
removeDocument={whichSlot === this.frontKey ? this.remDocFront : this.remDocBack}
dontSelect={returnTrue}
@@ -745,18 +632,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
<div>
<div className="submit-button">
- {/* <div className="submit-buttonschema-header-button" onPointerDown={e => this.openContextMenu(e.clientX, e.clientY, false)}>
- <FontAwesomeIcon color="white" icon="caret-down" />
- </div> */}
- {/* <button className="submit-buttonrecord" onClick={this._listening ? this.stopListening : this.startListening} style={{ background: this._listening ? 'lightgray' : '' }}>
- {<FontAwesomeIcon icon="microphone" size="lg" />}
- </button> */}
- {/* <div className="submit-buttonschema-header-button" onPointerDown={e => this.openContextMenu(e.clientX, e.clientY, true)} style={{ left: '50px', zIndex: '100' }}>
- <FontAwesomeIcon color="white" icon="caret-down" />
- </div> */}
- {/* <button className="submit-buttonpronunciation" onClick={this.evaluatePronunciation}>
- Evaluate Pronunciation
- </button> */}
<button className="submit-buttonsubmit" type="button" onClick={this._renderSide === this.backKey ? () => this.animateFlipping(this.frontKey) : this.handleRenderGPTClick}>
{this._renderSide === this.backKey ? 'Redo the Question' : 'Submit'}
</button>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 47c5734f7..6f004bed3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -11,10 +11,85 @@ import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { ObservableReactComponent, ObserverJsxParser } from '../ObservableReactComponent';
import './DocumentView.scss';
-import { FieldViewProps } from './FieldView';
+import { FieldViewProps, FieldViewSharedProps } from './FieldView';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
+import { Property } from 'csstype';
+
+interface DocOnlyProps {
+ LayoutTemplate?: () => Opt<Doc>;
+ LayoutTemplateString?: string;
+ hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
+ hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
+ hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDocumentButtonBar?: boolean;
+ hideOpenButton?: boolean;
+ hideDeleteButton?: boolean;
+ hideLinkAnchors?: boolean;
+ hideLinkButton?: boolean;
+ 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;
+ showAIEditor?: boolean;
+ hideFilterStatus?: 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.
+ dragWhenActive?: boolean;
+ dontHideOnDrag?: boolean;
+ onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+ DataTransition?: () => string | undefined;
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
+ dragConfig?: (data: DragManager.DocumentDragData) => void;
+ dragStarting?: () => void;
+ dragEnding?: () => void;
+
+ reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
+}
+const DocOnlyProps = [
+ 'layoutFieldKey',
+ 'LayoutTemplate',
+ 'LayoutTemplateString',
+ 'hideDecorations', // whether to suppress all DocumentDecorations when doc is selected
+ 'hideResizeHandles', // whether to suppress resized handles on doc decorations when this document is selected
+ 'hideTitle', // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ 'hideDecorationTitle', // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ 'hideDocumentButtonBar',
+ 'hideOpenButton',
+ 'hideDeleteButton',
+ 'hideLinkAnchors',
+ 'hideLinkButton',
+ 'hideCaptions',
+ 'contentPointerEvents', // 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',
+ 'showTags',
+ 'showAIEditor',
+ 'hideFilterStatus',
+ 'childHideDecorationTitle',
+ 'childHideResizeHandles',
+ 'childDragAction', // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+ 'dragWhenActive',
+ 'dontHideOnDrag',
+ 'onClickScriptDisable', // undefined = only when selected
+ 'DataTransition',
+ 'NativeWidth',
+ 'NativeHeight',
+ 'contextMenuItems',
+ 'dragConfig',
+ 'dragStarting',
+ 'dragEnding',
+
+ 'reactParent', // parent React component view (see CollectionFreeFormDocumentView)
+];
+
+export interface DocumentViewProps extends DocOnlyProps, FieldViewSharedProps {}
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
-export interface JsxBindings {
+interface JsxBindings {
props: BindingProps;
}
@@ -77,7 +152,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
}
-export interface DocumentContentsViewProps extends FieldViewProps {
+interface DocumentContentsViewProps extends DocumentViewProps, FieldViewProps {
layoutFieldKey: string;
}
@observer
@@ -113,29 +188,11 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
this._props.LayoutTemplate?.() ||
(this._props.LayoutTemplateString && this._props.Document) ||
(this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
- Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined);
+ Doc.Layout(this._props.Document, DocCast(this._props.Document[this._props.layoutFieldKey]));
return Doc.expandTemplateLayout(template, this._props.Document);
}
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
- const docOnlyProps = [
- // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
- 'hideResizeHandles',
- 'hideTitle',
- 'bringToFront',
- 'childContentPointerEvents',
- 'LayoutTemplateString',
- 'LayoutTemplate',
- 'showTags',
- 'layoutFieldKey',
- 'dontCenter',
- 'DataTransition',
- 'contextMenuItems',
- // 'onClick', // don't need to omit this since it will be set
- 'onDoubleClickScript',
- 'onPointerDownScript',
- 'onPointerUpScript',
- ];
const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
...this._props,
@@ -146,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
};
return {
props: {
- ...OmitKeys(list, [...docOnlyProps], '').omit,
+ ...OmitKeys(list, DocOnlyProps, '').omit,
} as BindingProps,
};
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index dd5fd0d0c..5ac66f2cd 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -273,7 +273,15 @@
.documentView-noAIWidgets {
transform-origin: top left;
- position: relative;
+ position: absolute;
+ bottom: 0;
+ pointer-events: none;
+}
+.documentView-widgetDecorations {
+ transform-origin: top right;
+ position: absolute;
+ top: 0;
+ right: 0;
}
.documentView-editorView-history {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c355e57d4..bec5ea5c1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,10 +43,10 @@ import { StyleProp } from '../StyleProp';
import { TagsView } from '../TagsView';
import { ViewBoxInterface } from '../ViewBoxInterface';
import { GroupActive } from './CollectionFreeFormDocumentView';
-import { DocumentContentsView } from './DocumentContentsView';
+import { DocumentContentsView, DocumentViewProps } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
-import { FieldViewProps, FieldViewSharedProps } from './FieldView';
+import { FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import { OpenWhere, OpenWhereMod } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
@@ -54,41 +54,10 @@ import { PresEffect, PresEffectDirection } from './trails/PresEnums';
import SpringAnimation from './trails/SlideEffect';
import { SpringType, springMappings } from './trails/SpringUtils';
-export interface DocumentViewProps extends FieldViewSharedProps {
- hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
- hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
- hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDocumentButtonBar?: boolean;
- hideOpenButton?: boolean;
- hideDeleteButton?: boolean;
- hideLinkAnchors?: boolean;
- hideLinkButton?: boolean;
- 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;
- hideFilterStatus?: 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.
- dragWhenActive?: boolean;
- dontHideOnDrag?: boolean;
- onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
- DataTransition?: () => string | undefined;
- NativeWidth?: () => number;
- NativeHeight?: () => number;
- contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
- dragConfig?: (data: DragManager.DocumentDragData) => void;
- dragStarting?: () => void;
- dragEnding?: () => void;
-
- reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
-}
@observer
-export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
+export class DocumentViewInternal extends DocComponent<DocumentViewProps & FieldViewProps>() {
// this makes mobx trace() statements more descriptive
- public get displayName() { return 'DocumentViewInternal(' + this.Document.$title + ')'; } // prettier-ignore
+ public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
/**
@@ -134,14 +103,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@computed get border() { return this.style(this.layoutDoc, StyleProp.Border) as string || ""; } // prettier-ignore
@computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding) as string; } // prettier-ignore
@computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations) as JSX.Element; } // prettier-ignore
- @computed get backgroundBoxColor(){ return this.style(this.Document, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
@computed get showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
@computed get showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) as string ?? ""; } // prettier-ignore
@computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) as number ?? 0; } // prettier-ignore
@computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) as number ?? 0; } // prettier-ignore
- @computed get docContents() { return this.style(this.Document, StyleProp.DocContents) as JSX.Element; } // prettier-ignore
- @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
- @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
+ @computed get backgroundBoxColor(){ return this.style(this.Document, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
+ @computed get docContents() { return this.style(this.Document, StyleProp.DocContents) as JSX.Element; } // prettier-ignore
+ @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+ @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
@computed get onClickHdlr() { return this._props.onClickScript?.() ?? ScriptCast(this.layoutDoc.onClick ?? this.Document.onClick); } // prettier-ignore
@computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
@@ -322,7 +291,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
DocumentView.DeselectAll();
Doc.UnBrushDoc(this.Document);
} else this._singleClickFunc?.();
- }, 'on double click: ' + this.Document.$title)();
+ }, 'on double click: ' + this.Document.title)();
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
this._doubleClickTimeout = undefined;
this._singleClickFunc = undefined;
@@ -340,7 +309,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false;
preventDefault = false;
}
- this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.$title);
+ this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.title);
const waitForDblClick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitForDblClick !== 'never') || waitForDblClick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -519,7 +488,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (e && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) {
const onDisplay = () => {
- if (this.Document.$type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
};
if (navigator.userAgent.includes('Macintosh')) {
@@ -545,7 +514,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
);
- if (!this.Document.$isFolder) {
+ if (!this.Document.isFolder) {
const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
const appearance = cm.findByDescription('Appearance...');
const appearanceItems = appearance?.subitems ?? [];
@@ -635,7 +604,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
let documentationDescription: string | undefined;
let documentationLink: string | undefined;
- switch (this.Document.$type) {
+ switch (this.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -692,7 +661,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
.ScreenToLocalTransform()
.translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
.scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
- onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr;
+ onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
isContentActive = (): boolean | undefined => this._isContentActive;
@@ -719,7 +688,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@observable _aiWinHeight = 88;
- private _tagsBtnHeight = 22;
+ TagsBtnHeight = 22;
@computed get currentScale() {
const viewXfScale = this._props.DocumentView!().screenToLocalScale();
const x = NumCast(this.Document._height) / viewXfScale / 80;
@@ -733,20 +702,68 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
*/
@computed get viewScaling() { return 1 / this.currentScale; } // prettier-ignore
/**
- * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+ * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its nominal pixel size.
*/
- @computed get maxWidgetSize() { return Math.min(this._tagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
+ @computed get maxWidgetSize() { return Math.min(this.TagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
/**
* How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
*/
- @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this._tagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
+ @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
+ aiEditor = () => (
+ <>
+ <div
+ className="documentView-editorView-history"
+ ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
+ style={{
+ transform: `scale(${this.uiBtnScaling})`,
+ height: this.aiContentsHeight() / this.uiBtnScaling,
+ width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
+ }}>
+ {this._componentView?.componentAIViewHistory?.() ?? null}
+ </div>
+ <div
+ className="documentView-editorView"
+ style={{
+ background: SnappingManager.userVariantColor,
+ width: `${100 / this.uiBtnScaling}%`, //
+ transform: `scale(${this.uiBtnScaling})`,
+ }}
+ ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+ <div className="documentView-editorView-resizer" />
+ {this._componentView?.componentAIView?.() ?? null}
+ {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+ </div>
+ </>
+ );
+ @computed get tagsOverlay() {
+ return (
+ <div
+ className="documentView-noAiWidgets"
+ style={{
+ width: `${100 / this.uiBtnScaling}%`, //
+ transform: `scale(${this.uiBtnScaling})`,
+ height: Number.isNaN(this.maxWidgetSize) ? undefined : this.TagsBtnHeight * this.uiBtnScaling,
+ }}>
+ {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+ </div>
+ );
+ }
+ tagsOverlayFunc = () => (this._props.DocumentView?.().showTags ? this.tagsOverlay : null);
+ @computed get widgetOverlay() {
+ return (
+ <div className="documentView-widgetDecorations" style={{ transform: `scale(${this.uiBtnScaling})` }}>
+ {this.widgetDecorations}
+ </div>
+ );
+ }
+ widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
- const noBackground = this.Document.$isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return (
<>
<div
@@ -758,7 +775,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}}>
<DocumentContentsView
{...this._props}
- layoutFieldKey={StrCast(this._renderDoc.layout_fieldKey, 'layout')}
+ layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
pointerEvents={this.contentPointerEvents}
setContentViewBox={this.setContentView}
childFilters={this.childFilters}
@@ -773,43 +790,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
</div>
- {!this._props.showAIEditor ? (
- <div
- className="documentView-noAiWidgets"
- style={{
- width: `${100 / this.uiBtnScaling}%`, //
- transform: `scale(${this.uiBtnScaling})`,
- bottom: Number.isNaN(this.maxWidgetSize) ? undefined : this.maxWidgetSize,
- }}>
- {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
- </div>
- ) : (
- <>
- <div
- className="documentView-editorView-history"
- ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
- style={{
- transform: `scale(${this.uiBtnScaling})`,
- height: this.aiContentsHeight() / this.uiBtnScaling,
- width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
- }}>
- {this._componentView?.componentAIViewHistory?.() ?? null}
- </div>
- <div
- className="documentView-editorView"
- style={{
- background: SnappingManager.userVariantColor,
- width: `${100 / this.uiBtnScaling}%`, //
- transform: `scale(${this.uiBtnScaling})`,
- }}
- ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
- <div className="documentView-editorView-resizer" />
- {this._componentView?.componentAIView?.() ?? null}
- {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
- </div>
- </>
- )}
- {this.widgetDecorations ?? null}
+ {this._props.showAIEditor ? this.aiEditor() : this.tagsOverlayFunc()}
+ {this.widgetOverlayFunc()}
</>
);
}
@@ -957,7 +939,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: (this.docContents ?? (
<div
className="documentView-node"
- id={this.Document.$type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
+ id={this.Document.type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
style={{
...style,
background: this.backgroundBoxColor,
@@ -1015,7 +997,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
borderRadius: this._componentView?.isUnstyledView?.() ? undefined : this.borderRounding,
pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
}}>
- {this._componentView?.isUnstyledView?.() || this.Document.$type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
+ {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
{jsx}
</div>
);
@@ -1194,6 +1176,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@observable private _selected = false;
@observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
@observable public TagPanelHeight = 0;
+ @observable public TagPanelEditing = false;
@computed get showTags() {
return this.Document._layout_showTags || this._props.showTags;
@@ -1210,7 +1193,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
@computed private get nativeScaling() {
if (this.shouldNotScale) return 1;
- const minTextScale = this.Document.$type === DocumentType.RTF ? 0.1 : 0;
+ const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
@@ -1271,6 +1254,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore
public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore
public get allLinks() { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
+ public get TagBtnHeight() { return this._docViewInternal?.TagsBtnHeight; } // prettier-ignore
+ public get UIBtnScaling() { return this._docViewInternal?.uiBtnScaling; } // prettier-ignore
get LayoutFieldKey() {
return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
@@ -1325,7 +1310,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public startDragging = (x: number, y: number, dropAction: dropActionType | undefined, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
- public toggleNativeDimensions = () => this._docViewInternal && this.Document.$type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight());
+ public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight());
public iconify(finished?: () => void, animateTime?: number) {
this.ComponentView?.updateIcon?.();
@@ -1348,7 +1333,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
public playAnnotation = () => {
- const audioAnnoState = this.dataDoc.audioAnnoState ?? AudioAnnoState.stopped;
+ const audioAnnoState = this.Document._audioAnnoState ?? AudioAnnoState.stopped;
const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
const anno = audioAnnos?.lastElement();
if (anno instanceof AudioField) {
@@ -1360,13 +1345,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
autoplay: true,
loop: false,
volume: 0.5,
- onend: action(() => { this.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
+ onend: action(() => { this.Document._audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
});
- this.dataDoc.audioAnnoState = AudioAnnoState.playing;
+ this.Document._audioAnnoState = AudioAnnoState.playing;
break;
case AudioAnnoState.playing:
(this.dataDoc[AudioPlay] as Howl)?.stop();
- this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
+ this.Document._audioAnnoState = AudioAnnoState.stopped;
break;
default:
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f82e980f5..ad6d93d43 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -48,11 +48,9 @@ export type StyleProviderFuncType = (
export interface FieldViewSharedProps {
Document: Doc;
TemplateDataDocument?: Doc;
- LayoutTemplateString?: string;
- LayoutTemplate?: () => Opt<Doc>;
renderDepth: number;
scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
- screenXPadding?: () => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
+ screenXPadding?: (view: DocumentView | undefined) => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
xPadding?: number;
yPadding?: number;
dontRegisterView?: boolean;
@@ -62,7 +60,8 @@ export interface FieldViewSharedProps {
ignoreAutoHeight?: boolean;
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
- ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
+ suppressSetHeight?: boolean;
+ dontCenter?: 'x' | 'y' | 'xy' | undefined;
LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view
containerViewPath?: () => DocumentView[];
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
@@ -80,13 +79,12 @@ export interface FieldViewSharedProps {
styleProvider: Opt<StyleProviderFuncType>;
setTitleFocus?: () => void;
focus: FocusFuncType;
- onClickScript?: () => ScriptField;
+ onClickScript?: () => ScriptField | undefined;
onDoubleClickScript?: () => ScriptField;
onPointerDownScript?: () => ScriptField;
onPointerUpScript?: () => ScriptField;
onKey?: (e: KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined;
fitWidth?: (doc: Doc) => boolean | undefined;
- dontCenter?: 'x' | 'y' | 'xy' | undefined;
searchFilterDocs: () => Doc[];
showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -102,7 +100,6 @@ export interface FieldViewSharedProps {
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt<Property.PointerEvents>;
- suppressSetHeight?: boolean;
}
/**
@@ -110,13 +107,13 @@ export interface FieldViewSharedProps {
* */
export interface FieldViewProps extends FieldViewSharedProps {
DocumentView?: () => DocumentView;
- fieldKey: string;
isSelected: () => boolean;
select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
docViewPath: () => DocumentView[];
setHeight?: (height: number) => void;
NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
isHovering?: () => boolean;
+ fieldKey: string;
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
// See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index f699568f1..3190757e2 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { ClientUtils, DashColor, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
+import { ClientUtils, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -134,7 +134,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
min={NumCast(this.dataDoc.numBtnMin, 0)}
max={NumCast(this.dataDoc.numBtnMax, 100)}
number={checkResult}
- size={Size.XSMALL}
+ size={Size.XXSMALL}
setNumber={undoable(value => numScript(value), `${this.Document.title} button set from list`)}
fillWidth
/>
@@ -252,7 +252,9 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
* Color button
*/
@computed get colorButton() {
- const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
+ const color = SnappingManager.userColor;
+ const pickedColor = 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 curColor = (this.colorScript?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result as string) ?? 'transparent';
const tooltip: string = StrCast(this.Document.toolTip);
@@ -270,10 +272,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
}}
defaultPickerType="Classic"
selectedColor={curColor}
- type={Type.PRIM}
+ type={Type.TERT}
color={color}
- background={SnappingManager.userBackgroundColor}
- icon={this.Icon(color) ?? undefined}
+ background={background}
+ icon={this.Icon(pickedColor) ?? undefined}
tooltip={tooltip}
label={this.label}
/>
@@ -287,19 +289,20 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean;
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
+ const background = this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string;
const items = DocListCast(this.dataDoc.data);
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={`Click to Toggle ${tooltip} or select new option`}
- type={Type.PRIM}
+ type={Type.TERT}
color={color}
- background={undefined}
+ background={background}
multiSelect={true}
onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
- isToggle={false}
toggleStatus={toggleStatus}
+ showUntilToggle={BoolCast(this.Document.showUntilToggle)}
label={selectedItems.length === 1 ? selectedItems[0] : this.label}
items={items.map(item => ({
icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
@@ -339,11 +342,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
<Toggle
tooltip={`Toggle ${tooltip}`}
toggleType={ToggleType.BUTTON}
- type={inkShapeHack ? Type.TERT : Type.PRIM}
+ type={Type.TERT}
toggleStatus={toggleStatus}
text={buttonText}
color={color}
- background={inkShapeHack ? DashColor(SnappingManager.userBackgroundColor).darken(0.05).toString() : undefined}
+ triState={inkShapeHack}
+ background={color}
icon={this.Icon(color)!}
label={this.label}
onPointerDown={e =>
@@ -404,12 +408,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
case ButtonType.ToggleButton: return this.toggleButton;
- case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
- case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
- case ButtonType.TextButton: return <Button {...btnProps} color={color}
- background={SnappingManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
- case ButtonType.MenuButton: return <IconButton {...btnProps} color={color}
- background={SnappingManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
+ case ButtonType.ClickButton: return <IconButton {...btnProps} size={Size.MEDIUM} color={color} background={color} />;
+ case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} background={color} />;
+ case ButtonType.TextButton: return <Button {...btnProps} color={color} background={color}
+ text={StrCast(this.dataDoc.buttonText)}/>;
+ case ButtonType.MenuButton: return <IconButton size={Size.LARGE} {...btnProps} color={color} background={color}
+ tooltipPlacement='right' onClick={scriptFunc} />;
default:
}
return this.defaultButton;
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 202b0c701..d6cf95958 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -4,7 +4,6 @@
display: flex;
position: relative;
pointer-events: none;
- background-color: rgb(218, 218, 218);
border-radius: 50px;
align-items: center;
gap: 5px;
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index ddabd61e1..e3924eca7 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,22 +1,23 @@
-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 { Doc, StrListCast } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
+import { AudioAnnoState } from '../../../server/SharedMediaTypes';
import { undoable } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { TagItem } from '../TagsView';
import { DocumentView } from './DocumentView';
import './IconTagBox.scss';
+import { Size, Toggle, ToggleType, Type } from '@dash/components';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { StyleProp } from '../StyleProp';
export interface IconTagProps {
Views: DocumentView[];
- IsEditing: boolean;
+ IsEditing: boolean | undefined;
}
/**
@@ -52,13 +53,46 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
* @param key metadata icon button
* @returns an icon for the metdata button
*/
- getButtonIcon = (doc: Doc, key: Doc): JSX.Element => {
+ getButtonIcon = (dv: DocumentView, 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
+ const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
+ return (
+ <Toggle
+ tooltip={`Click to add/remove the tag ${tag}`}
+ toggleStatus={TagItem.docHasTag(dv.Document, tag)}
+ toggleType={ToggleType.BUTTON}
+ icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
+ size={Size.XSMALL}
+ type={Type.PRIM}
+ onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
+ color={color}
+ />
+ );
+ };
- return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
+ /**
+ * Displays a button to play audio annotations on the document.
+ * NOTE: This should be generalized -- audio should
+ * @returns
+ */
+ renderAudioButtons = (dv: DocumentView, anno: string) => {
+ const fcolor = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
+ const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: fcolor ?? 'blue', recording: 'red' };
+ const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+ const color = audioIconColors[audioAnnoState(this.View.Document)];
+ return (
+ <Toggle
+ tooltip={`click to play:${anno}`}
+ toggleStatus={true}
+ toggleType={ToggleType.BUTTON}
+ icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon="file-audio" color={color} />}
+ size={Size.XSMALL}
+ type={Type.PRIM}
+ onClick={() => this.View?.playAnnotation()}
+ color={color}
+ />
+ );
};
/**
@@ -69,22 +103,15 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
.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 key={tag} title={<div className="dash-tooltip">Click to add/remove the {tag} tag</div>}>
+ {this.getButtonIcon(this.View, key)}
</Tooltip>
)); // prettier-ignore
- return !buttons.length ? null : (
+ const audioannos = StrListCast(this.View.Document[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']);
+ return !buttons.length && !audioannos.length ? null : (
<div className="card-button-container" style={{ fontSize: '50px' }}>
+ {audioannos.length ? this.renderAudioButtons(this.View, audioannos.lastElement()) : null}
{buttons}
</div>
);
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 3d6942e6f..4a6e8eb49 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -114,7 +114,6 @@
width: 100%;
height: 100%;
position: absolute;
- background: black;
display: flex;
flex-direction: row;
align-items: center;
@@ -143,11 +142,8 @@
}
}
.imageBox-regenerateDropTarget {
- right: 30;
- border-radius: 50%;
- svg {
- border-radius: 50%;
- }
+ right: 35;
+ transform-origin: 70px 35px;
}
.imageBox-fader img {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3b3bc808a..2b9a78596 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -45,8 +45,9 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
-import { RichTextField } from '../../../fields/RichTextField';
+import { ComputedField } from '../../../fields/ScriptField';
+const DefaultPath = '/assets/unknown-file-icon-hi.png';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
private static _instance: ImageEditorData;
@@ -146,11 +147,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
},
{ fireImmediately: true, delay: 1000 }
);
- const { layoutDoc } = this;
this._disposers.path = reaction(
() => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
({ nativeSize, width, height }) => {
- if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !height) {
+ if (!this.layoutDoc._layout_nativeDimEditable || !height) {
this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
}
},
@@ -210,15 +210,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
if (de.metaKey || hitDropTarget(e.target as HTMLElement, this._overlayIconRef.current)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
+ this.Document.$backgroundColor_alternate = ComputedField.MakeFunction('this.data_alternates[0]?.$backgroundColor');
return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
}, true);
} else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) {
this._regenerateLoading = true;
const drag = de.complete.docDragData.draggedDocuments.lastElement();
const dragField = drag[Doc.LayoutFieldKey(drag)];
+ const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title);
const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title));
const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text);
- DrawingFillHandler.drawingToImage(this.Document, 100, newPrompt(dragField instanceof RichTextField ? dragField.Text : ''), drag)?.then(action(() => (this._regenerateLoading = false)));
+ DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false)));
added = false;
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -252,12 +254,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
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);
- this.dataDoc._freeform_panY = nw * NumCast(this.dataDoc._freeform_panY);
- this.dataDoc._freeform_panX_max = this.dataDoc._freeform_panX_max ? nw * NumCast(this.dataDoc._freeform_panX_max) : undefined;
- 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;
+ this.dataDoc.freeform_panX = nw * NumCast(this.dataDoc.freeform_panX);
+ this.dataDoc.freeform_panY = nw * NumCast(this.dataDoc.freeform_panY);
+ this.dataDoc.freeform_panX_max = this.dataDoc.freeform_panX_max ? nw * NumCast(this.dataDoc.freeform_panX_max) : undefined;
+ 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;
@@ -293,12 +295,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const anchw = NumCast(cropping._width);
const anchh = NumCast(cropping._height);
const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) / anchh;
- cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
+ cropping.onClick = undefined;
cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
- cropping.onClick = undefined;
+ cropping.$title = 'crop: ' + this.Document.title;
cropping.$annotationOn = undefined;
cropping.$isDataDoc = true;
cropping.$backgroundColor = undefined;
@@ -309,7 +311,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
cropping.$data_nativeWidth = anchw;
cropping.$data_nativeHeight = anchh;
cropping.$freeform_scale = viewScale;
- cropping.$reeform_panX = anchx / viewScale;
+ cropping.$freeform_panX = anchx / viewScale;
cropping.$freeform_panY = anchy / viewScale;
cropping.$freeform_scale_min = viewScale;
cropping.$freeform_panX_min = anchx / viewScale;
@@ -399,13 +401,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
// updateIcon = () => new Promise<void>(res => res());
- updateIcon = (/* usePanelDimensions?: boolean */) => {
- const contentDiv = this._mainCont;
- return !contentDiv
+ updateIcon = (/* usePanelDimensions?: boolean */) =>
+ !this._mainCont || !DocListCast(this.dataDoc[this.annotationKey]).length
? new Promise<void>(res => res())
: UpdateIcon(
this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
- contentDiv,
+ this._mainCont,
this._props.PanelWidth(), // usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
this._props.PanelHeight(), // usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
this._props.PanelWidth(),
@@ -420,14 +421,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.dataDoc.icon_nativeHeight = nativeHeight;
}
);
- };
choosePath = (url: URL) => {
if (!url?.href) return '';
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith(DefaultPath)) return DefaultPath;
const ext = extname(url.href);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -441,7 +441,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get nativeSize() {
TraceMobx();
- if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
+ if (this.paths.length && this.paths[0].includes(DefaultPath)) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
@@ -451,15 +451,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
/**
* How much the content of the view is being scaled based on its nesting and its fit-to-width settings
*/
- @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * ( this._props.NativeDimScaling?.() || 1); } // prettier-ignore
+ @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * (this._props.NativeDimScaling?.()??1); } // prettier-ignore
/**
* The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
*/
- @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.5 * Math.min(NumCast(this.Document.width)))* this.viewScaling; } // prettier-ignore
+ @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore
/**
* How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
*/
- @computed get uiBtnScaling() { return Math.min(this.maxWidgetSize / this._sideBtnWidth, 1); } // prettier-ignore
+ @computed get uiBtnScaling() { return Math.min(1/(this._props.NativeDimScaling?.()??1), this.maxWidgetSize / this._sideBtnWidth); } // prettier-ignore
@computed get overlayImageIcon() {
const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
@@ -506,27 +506,30 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
@computed get regenerateImageIcon() {
return (
- <div
- className="imageBox-regenerateDropTarget"
- ref={this._regenerateIconRef}
- onClick={() => DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs), { openLocation: OpenWhere.addRight })}
- style={{
- display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none',
- transform: `scale(${this.uiBtnScaling})`,
- width: this._sideBtnWidth,
- height: this._sideBtnWidth,
- background: 'transparent',
- // color: SettingsManager.userBackgroundColor,
- }}>
- {this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width="100%" height="100%" /> : <FontAwesomeIcon icon="portrait" color={SettingsManager.userColor} size="lg" />}
- </div>
+ <Tooltip title={'click to show AI generations. Drop an image on to create a new generation'}>
+ <div
+ className="imageBox-regenerateDropTarget"
+ ref={this._regenerateIconRef}
+ onClick={() => DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs), { openLocation: OpenWhere.addRight })}
+ style={{
+ display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none',
+ transform: `scale(${this.uiBtnScaling})`,
+ width: this._sideBtnWidth,
+ height: this._sideBtnWidth,
+ background: 'black',
+ color: 'white',
+ // color: SettingsManager.userBackgroundColor,
+ }}>
+ {this._regenerateLoading ? <ReactLoading type="spin" width="100%" height="100%" /> : <FontAwesomeIcon icon="portrait" size="lg" />}
+ </div>
+ </Tooltip>
);
}
@computed get paths() {
const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc
const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
- const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
+ const defaultUrl = new URL(ClientUtils.prepend(DefaultPath));
const altpaths =
alts
?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl) : defaultUrl))
@@ -539,11 +542,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get content() {
TraceMobx();
+ const usePath = StrCast(this.Document[this.fieldKey + '_usePath']);
+ const alternate = '_' + usePath.replace(':hover', '');
+ const altColor = DashColor(StrCast(this.Document[this.fieldKey + alternate], StrCast(this.Document['$backgroundColor' + alternate], 'black')));
+
const backColor = DashColor((this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string) ?? Colors.WHITE);
// 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 fadepath = this.layoutDoc.hideImage ? '' : this.paths[0];
+ const srcpath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']);
const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
@@ -577,14 +584,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
key="paths"
src={srcpath}
style={{ transform, transformOrigin }}
- onError={action(e => {
- this._error = e.toString();
- })}
+ onError={action(e => (this._error = e.toString()))}
draggable={false}
width={nativeWidth}
/>
{fadepath === srcpath ? null : (
- <div className={`imageBox-fadeBlocker${this.usingAlternate ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
+ <div
+ className={`imageBox-fadeBlocker${!this.usingAlternate ? '-hover' : ''}`}
+ style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms'), background: altColor.alpha() === 0 ? 'transparent' : altColor.toString() }}>
<img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
</div>
)}
@@ -648,7 +655,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onClick={action(async () => {
this._regenerateLoading = true;
if (this._fireflyRefStrength) {
- DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(action(() => (this._regenerateLoading = false)));
+ DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false)));
} else {
SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
action(newImgs => {
@@ -702,21 +709,24 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!this.dataDoc[this.fieldKey]) {
- this.chooseImage();
- } else if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
- setupMoveUpEvents(
- this,
- e,
- action(moveEv => {
- MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this.marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
- return true;
- }),
- returnFalse,
- () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
- false
- );
+ if (!e.altKey && e.button === 0) {
+ if (NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(moveEv => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this.marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
+ return true;
+ }),
+ returnFalse,
+ () => {
+ if (!this.dataDoc[this.fieldKey]) this.chooseImage();
+ else MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ },
+ false
+ );
+ }
}
};
@action
@@ -741,8 +751,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
TraceMobx();
const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
- const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
- const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document;
return (
<div
className="imageBox"
@@ -767,7 +775,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<CollectionFreeFormView
ref={this._ffref}
{...this._props}
- Document={doc}
+ Document={this.Document}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8911fac6d..be897b3f3 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -9,7 +9,7 @@ import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { DocCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocumentOptions } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
import { CompiledScript } from '../../util/Scripting';
import { undoable } from '../../util/UndoManager';
@@ -22,6 +22,7 @@ import './KeyValueBox.scss';
import { KeyValuePair } from './KeyValuePair';
import { OpenWhere } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { DocLayout } from '../../../fields/DocSymbols';
export type KVPScript = {
script: CompiledScript;
@@ -91,10 +92,11 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
return !script.compiled ? undefined : { script, type, onDelegate };
};
- public static ApplyKVPScript = (doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
+ public static ApplyKVPScript = (doc: Doc, keyIn: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
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 : DocCast(doc.proto, doc);
+ const chooseDelegate = forceOnDelegate || onDelegate || keyIn.startsWith('_');
+ const key = chooseDelegate && keyIn.startsWith('$') ? keyIn.slice(1) : keyIn;
+ const target = chooseDelegate ? doc : DocCast(doc.proto, doc);
let field: FieldType | undefined;
switch (type) {
case 'computed': field = new ComputedField(script); break; // prettier-ignore
@@ -121,10 +123,16 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
};
- public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
+ public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegateIn?: boolean, setResult?: (value: FieldResult) => void) => {
const script = KeyValueBox.CompileKVPScript(value);
if (!script) return false;
- return KeyValueBox.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult);
+ const ldoc = key.startsWith('_') ? doc[DocLayout] : doc;
+ const forceOnDelegate = forceOnDelegateIn || (ldoc !== doc && !value.startsWith('='));
+ // an '=' value redirects a key targeting the render template to the root document.
+ // also, if ldoc and doc are equal, allow redirecting to data document when not using an equal
+ // in either case, get rid of initial '_' which forces writing to layout
+ const setKey = value.startsWith('=') || ldoc === doc ? key.replace(/^_/, '') : key;
+ return KeyValueBox.ApplyKVPScript(doc, setKey, script, forceOnDelegate, setResult);
}, 'Set Doc Field');
onPointerDown = (e: React.PointerEvent): void => {
@@ -156,12 +164,16 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
});
- const layoutProtos = Doc.GetAllPrototypes(this.layoutDoc);
+ const docinfos = new DocumentOptions();
+
+ const layoutProtos = this.layoutDoc !== doc ? Doc.GetAllPrototypes(this.layoutDoc) : [];
layoutProtos.forEach(proto => {
Object.keys(proto)
+ .filter(key => !(key in ids) || docinfos['_' + key]) // if '_key' is in docinfos (as opposed to just 'key'), then the template Doc's value should be displayed instead of the root document's value
.map(key => '_' + key)
.forEach(key => {
- if (!(key.replace(/^_/, '') in ids) && doc[key] !== ComputedField.undefined) {
+ if (doc[key] !== ComputedField.undefined) {
+ if (key.replace(/^_/, '') in ids) delete ids[key.replace(/^_/, '')];
ids[key] = key;
}
});
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 93f5231cb..c9e0aea5a 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -16,6 +16,7 @@ import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
import './KeyValueBox.scss';
import './KeyValuePair.scss';
import { OpenWhere } from './OpenWhere';
+import { DocLayout } from '../../../fields/DocSymbols';
// Represents one row in a key value plane
@@ -60,31 +61,23 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
};
render() {
- // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
- const layoutField = this._props.keyName.startsWith('_');
- let doc = layoutField ? Doc.Layout(this._props.doc) : this._props.doc;
- let protoCount = doc !== this._props.doc && !layoutField ? 1 : 0;
- while (doc) {
- if (Object.keys(doc).includes(this._props.keyName.replace(/^_/, ''))) {
- break;
- }
+ let doc = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc;
+ const layoutField = doc !== this._props.doc;
+ const key = layoutField ? this._props.keyName.replace(/^_/, '') : this._props.keyName;
+ let protoCount = 0;
+ while (doc && !Object.keys(doc).includes(key)) {
protoCount++;
doc = DocCast(doc.proto);
}
- const parenCount = Math.max(0, protoCount);
- const keyStyle = protoCount === 0 && doc === this._props.doc ? 'black' : 'blue';
-
+ const parenCount = Math.max(0, protoCount - 1);
+ const keyStyle = layoutField ? 'red' : protoCount === 0 ? 'black' : 'blue';
const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
-
+ const docOpts = Object.entries(new DocumentOptions());
return (
<tr
- className={this._props.rowStyle}
- onPointerEnter={action(() => {
- this.isPointerOver = true;
- })}
- onPointerLeave={action(() => {
- this.isPointerOver = false;
- })}>
+ className={this._props.rowStyle} //
+ onPointerEnter={action(() => (this.isPointerOver = true))}
+ onPointerLeave={action(() => (this.isPointerOver = false))}>
<td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button
@@ -92,17 +85,16 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
style={hover}
className="keyValuePair-td-key-delete"
onClick={undoable(() => {
- if (Object.keys(this._props.doc).indexOf(this._props.keyName) !== -1) {
- delete this._props.doc[this._props.keyName];
- } else delete DocCast(this._props.doc.proto)?.[this._props.keyName];
+ delete (Object.keys(doc).indexOf(key) !== -1 ? doc : DocCast(this._props.doc.proto))[key];
}, 'set key value')}>
X
</button>
<input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
- <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
- <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.replace(/__/g, '').replace(/^_/, '').match(/_/g)?.length || 0), color: keyStyle }}>
- {(layoutField ? '_' : '$').repeat(parenCount)}
- {(keyStyle === 'blue' && !layoutField && !parenCount ? '$' : '') + this._props.keyName}
+ <Tooltip title={(docOpts.find(([k]) => k.replace(/^_/, '') === key)?.[1] as FInfo)?.description ?? ''}>
+ <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (key.match(/_/g)?.length || 0), color: keyStyle }}>
+ {'('.repeat(parenCount)}
+ {(keyStyle === 'black' ? '' : layoutField ? '_' : '$') + key}
+ {')'.repeat(parenCount)}
</div>
</Tooltip>
</div>
@@ -133,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
pinToPres: returnZero,
}}
GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
- SetValue={(value: string) => Doc.SetField(this._props.doc, this._props.keyName, value)}
+ SetValue={value => Doc.SetField(this._props.doc, this._props.keyName, value)}
/>
</div>
</td>
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index f2160feb7..eaea272dc 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -23,9 +23,9 @@
// glr: This should really be the same component as text and PDFs
.pdfBox-sidebarBtn {
background: global.$black;
- height: 25px;
- width: 25px;
- right: 5px;
+ height: 20px;
+ width: 20px;
+ right: 0px;
color: global.$white;
display: flex;
position: absolute;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 78ddafa88..36d260fb9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -499,8 +499,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
title="Toggle Sidebar"
style={{
display: !this._props.isContentActive() ? 'none' : undefined,
- top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
+ top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 0,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ transformOrigin: 'top right',
+ transform: `scale(${this._props.DocumentView?.().UIBtnScaling || 1})`,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
<FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index f9de4ab5a..547a2efa8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -141,8 +141,8 @@ audiotag:hover {
position: absolute;
top: 0;
right: 0;
- width: 17px;
- height: 17px;
+ width: 20px;
+ height: 20px;
font-size: 11px;
border-radius: 3px;
color: white;
@@ -153,6 +153,7 @@ audiotag:hover {
align-items: center;
cursor: grabbing;
box-shadow: global.$standard-box-shadow;
+ transform-origin: top right;
// transition: 0.2s;
opacity: 0.3;
&:hover {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5f132ecdf..a45b8a488 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -17,7 +17,7 @@ import { BsMarkdownFill } from 'react-icons/bs';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id, ToString } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -141,29 +141,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@observable _showSidebar = false;
- @computed get fontColor() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
- @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
- @computed get fontFamily() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
- @computed get fontWeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight) as string; } // prettier-ignore
- @computed get fontStyle() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontStyle) as string; } // prettier-ignore
- @computed get fontDecoration() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontDecoration) as string; } // prettier-ignore
+ @computed get fontColor() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
+ @computed get fontSize() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
+ @computed get fontFamily() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
+ @computed get fontWeight() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontWeight) as string; } // prettier-ignore
+ @computed get fontStyle() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontStyle) as string; } // prettier-ignore
+ @computed get fontDecoration() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontDecoration) as string; } // prettier-ignore
- set _recordingDictation(value) {
+ set recordingDictation(value) {
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
// eslint-disable-next-line no-return-assign
@computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this); } // prettier-ignore
- @computed get _recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
- @computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
+ @computed get recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
+ @computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
@computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
@computed get noSidebar() { return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar; } // prettier-ignore
- @computed get layout_sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, this._showSidebar ? '20%' :'0%'); } // prettier-ignore
+ @computed get sidebarColor() { return StrCast(this.layoutDoc._sidebar_color, StrCast(this.layoutDoc["_"+this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
@computed get layout_autoHeight() { return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight; } // prettier-ignore
- @computed get textHeight() { return NumCast(this.dataDoc[this.fieldKey + '_height']); } // prettier-ignore
- @computed get scrollHeight() { return NumCast(this.dataDoc[this.fieldKey + '_scrollHeight']); } // prettier-ignore
- @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.sidebarKey + '_height']); } // prettier-ignore
+ @computed get textHeight() { return NumCast(this.layoutDoc["_"+this.fieldKey + '_height']); } // prettier-ignore
+ @computed get scrollHeight() { return NumCast(this.layoutDoc["_"+this.fieldKey + '_scrollHeight']); } // prettier-ignore
+ @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.layoutDoc["_"+this.sidebarKey + '_height']); } // prettier-ignore
@computed get titleHeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number || 0; } // prettier-ignore
@computed get layout_autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } // prettier-ignore
@computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
@@ -215,7 +215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+ const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : this.rootDoc;
if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
this.addDocument(anchor);
@@ -246,7 +246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
anchor.followLinkAudio = true;
let stopFunc: () => void = emptyFunction;
target.$mediaState = mediaState.Recording;
- DictationManager.recordAudioAnnotation(target[DocData], Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+ DictationManager.recordAudioAnnotation(target, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
const reactionDisposer = reaction(
() => target.mediaState,
@@ -308,7 +308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
leafText = (node: Node) => {
if (node.type === this.EditorView?.state.schema.nodes.dashField) {
- const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
const fieldKey = StrCast(node.attrs.fieldKey);
return (
(node.attrs.hideKey ? '' : fieldKey + ':') + //
@@ -316,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
}
if (node.type === this.EditorView?.state.schema.nodes.dashDoc) {
- const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
return refDoc[ToString]();
}
return '';
@@ -743,15 +743,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
- const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
+ const dw = DivWidth(this._ref.current);
+ const prevWidth = 1 - this.sidebarWidth() / dw / this.nativeScaling();
if (preview) this._showSidebar = true;
else {
- this.layoutDoc[this.sidebarKey + '_freeform_scale_max'] = 1;
- this.layoutDoc._layout_showSidebar =
- (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%';
+ this.layoutDoc._layout_sidebarWidthPercent =
+ this.sidebarWidthPercent === '0%' //
+ ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` //
+ : '0%';
+ this.layoutDoc._layout_showSidebar = this.sidebarWidthPercent !== '0%';
}
- this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) + defaultSidebar : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
+ this.layoutDoc._width =
+ !preview && this.SidebarShown //
+ ? NumCast(this.layoutDoc._width) + defaultSidebar
+ : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
};
sidebarDown = (e: React.PointerEvent) => {
const batch = UndoManager.StartBatch('toggle sidebar');
@@ -769,7 +775,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const localDelta = this.DocumentView?.().screenToViewTransform().transformDirection(delta[0], delta[1]) ?? delta;
- const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
+ const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100;
const width = NumCast(this.layoutDoc._width) + localDelta[0];
this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
this.layoutDoc.width = width;
@@ -1034,14 +1040,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
breakupDictation = () => {
- if (this.EditorView && this._recordingDictation) {
+ if (this.EditorView && this.recordingDictation) {
this.stopDictation(/* true */);
this._break = true;
const { state } = this.EditorView;
const { to } = state.selection;
const updated = TextSelection.create(state.doc, to, to);
this.EditorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
- if (this._recordingDictation) {
+ if (this.recordingDictation) {
this.recordDictation();
}
}
@@ -1214,7 +1220,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.dataDoc[this.fieldKey + '_height'] = scrollHeight;
+ this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
};
@@ -1223,7 +1229,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get contentScaling() {
- return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
+ return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this.nativeScaling() : 1;
}
componentDidMount() {
!this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
@@ -1333,14 +1339,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!this._props.dontRegisterView) {
this._disposers.record = reaction(
- () => this._recordingDictation,
+ () => this.recordingDictation,
() => {
this.stopDictation(/* true */);
- this._recordingDictation && this.recordDictation();
+ this.recordingDictation && this.recordDictation();
},
{ fireImmediately: true }
);
- if (this._recordingDictation) setTimeout(this.recordDictation);
+ if (this.recordingDictation) setTimeout(this.recordDictation);
}
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
@@ -1516,8 +1522,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.TextView = this;
}
- const selectOnLoad =
- Doc.AreProtosEqual(this._props.TemplateDataDocument ?? DocCast(this.Document.rootDocument, this.Document), DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.rootDoc, DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
if (selectOnLoad) {
DocumentView.SetSelectOnLoad(undefined);
@@ -1560,8 +1565,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
componentWillUnmount() {
- if (this._recordingDictation) {
- this._recordingDictation = !this._recordingDictation;
+ if (this.recordingDictation) {
+ this.recordingDictation = !this.recordingDictation;
}
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
@@ -1596,7 +1601,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
}
- if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
+ if (this.recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
FormattedTextBoxComment.textBox = this;
@@ -1754,8 +1759,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
*/
tryKeepingFocus = (target: Element | null) => {
for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) {
- // test if parent of new focused element is a UI button (should be more specific than testing className)
- if (newFocusEle?.className === 'fonticonbox' || newFocusEle?.className === 'popup-container') {
+ // bcz: HACK!! test if parent of new focused element is a UI button (should be more specific than testing className)
+ if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) {
return this.EditorView?.focus(); // keep focus on text box
}
}
@@ -1879,10 +1884,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => {
- this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+ this.layoutDoc['_' + this.fieldKey + '_scrollHeight'] = scrollHeight;
};
- if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
+ if (this.Document === this.layoutDoc || this.layoutDoc.rootDocument) {
setScrollHeight();
} else {
setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
@@ -1891,7 +1896,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
- sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ nativeScaling = () => this._props.NativeDimScaling?.() || 1;
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.sidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
@@ -1899,17 +1904,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey);
setSidebarHeight = (height: number) => {
- this.dataDoc[this.sidebarKey + '_height'] = height;
+ this.layoutDoc['_' + this.sidebarKey + '_height'] = height;
};
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
this._props
.ScreenToLocalTransform()
- .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
- .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
+ .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / this.nativeScaling(), 0)
+ .scale(1 / this.nativeScaling());
@computed get audioHandle() {
- return !this._recordingDictation ? null : (
+ return !this.recordingDictation ? null : (
<div
className="formattedTextBox-dictation"
onPointerDown={e =>
@@ -1919,7 +1924,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
returnFalse,
emptyFunction,
action(() => {
- this._recordingDictation = !this._recordingDictation;
+ this.recordingDictation = !this.recordingDictation;
})
)
}>
@@ -1927,6 +1932,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
</div>
);
}
+ private _sideBtnWidth = 35;
+ /**
+ * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
+ */
+ @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale ; } // prettier-ignore
+ /**
+ * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+ */
+ @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore
+ /**
+ * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
+ */
+ @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore
+
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.sidebarKey]).filter(d => d?.author).length;
@@ -1941,6 +1960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
backgroundColor,
color,
opacity: annotated ? 1 : undefined,
+ transform: `scale(${this.uiBtnScaling})`,
}}>
<FontAwesomeIcon icon="comment-alt" />
</div>
@@ -1985,7 +2005,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
isAnnotationOverlay={false}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
- NativeDimScaling={this.sidebarContentScaling}
+ NativeDimScaling={this.nativeScaling}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.sidebarRemDocument}
moveDocument={this.sidebarMoveDocument}
@@ -2002,7 +2022,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
};
return (
- <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
{renderComponent(StrCast(this.layoutDoc[this.sidebarKey + '_type_collection']))}
</div>
);
@@ -2050,7 +2070,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return this._fieldKey;
}
@computed get _fieldKey() {
- const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+ const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._props.isHovering?.() || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
}
onPassiveWheel = (e: WheelEvent) => {
@@ -2060,7 +2080,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
if (this._props.isContentActive()) {
- const scale = this._props.NativeDimScaling?.() || 1;
+ const scale = this.nativeScaling();
const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
const height = Number(styleFromLayout.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
@@ -2077,15 +2097,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
render() {
TraceMobx();
- const scale = this._props.NativeDimScaling?.() || 1;
- const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
+ const scale = this.nativeScaling();
+ const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
- const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) =>
- [view?._props.PanelWidth() /(view?.screenToLocalScale()??1), view?._props.PanelHeight() / (view?.screenToLocalScale()??1)]; // prettier-ignore
- const scrMargin = [Math.max(0, (scrSize(2)[0] - scrSize(1)[0]) / 2), Math.max(0, (scrSize(2)[1] - scrSize(1)[1]) / 2)];
- const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), this._props.xPadding ?? 0, 0, ((this._props.screenXPadding?.() ?? 0) - scrMargin[0]) * this.ScreenToLocalBoxXf().Scale);
- const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, ((this._props.yPadding ?? 0) - scrMargin[1]) * this.ScreenToLocalBoxXf().Scale);
+ const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
+ const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore
const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
return this.isLabel ? (
<LabelBox {...this._props} />
@@ -2106,7 +2123,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
height: `${100 / scale}%`,
}),
transition: 'inherit',
- paddingBottom: this.tagsHeight,
// overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
color: this.fontColor,
fontSize: this.fontSize,
@@ -2139,13 +2155,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._scrollRef = r;
}}
style={{
- width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.noSidebar ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
onDrop={this.ondrop}>
<div
- className={`formattedTextBox-inner${rounded} ${this.layoutDoc._layout_centered && this.scrollHeight <= (this._props.fitWidth?.(this.Document) ? this._props.PanelHeight() : NumCast(this.layoutDoc._height)) ? 'centered' : ''} ${this.layoutDoc.hCentering}`}
+ className={`formattedTextBox-inner${rounded} ${this.dataDoc.text_centered && this.scrollHeight <= (this._props.fitWidth?.(this.Document) ? this._props.PanelHeight() : NumCast(this.layoutDoc._height)) ? 'centered' : ''} ${this.layoutDoc.hCentering}`}
ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
@@ -2159,7 +2175,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}}
/>
</div>
- {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
{this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden || this.Document.quiz ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index eabc6455f..7ae1fc202 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -50,8 +50,7 @@ export function buildKeymap<S extends Schema<string>>(schema: S, tbox?: Formatte
const canEdit = (state: EditorState) => {
if (!tbox) return true;
- const permissions = GetEffectiveAcl(tbox._props.TemplateDataDocument ?? tbox.Document[DocData]);
- switch (permissions) {
+ switch (GetEffectiveAcl(tbox.dataDoc)) {
case AclAugment:
{
// previously used hack: (state.selection as any).$cursor.nodeBefore;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 758b4035e..4fa1fb13b 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -117,7 +117,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return this._activeAlignment;
}
@computed get textVcenter() {
- return BoolCast(this.dataDoc?._layout_centered, BoolCast(Doc.UserDoc().layout_centered));
+ return BoolCast(this.dataDoc?.text_centered, BoolCast(Doc.UserDoc().textCentered));
}
@action
@@ -367,7 +367,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.TextView && this.view && fontField !== 'fitBox') {
- if (this.view.hasFocus()) {
+ const anchorNode = window.getSelection()?.anchorNode;
+ if (this.view.hasFocus() || (anchorNode && this.TextView.ProseRef?.contains(anchorNode))) {
const attrs: { [key: string]: string } = {};
attrs[fontField] = value;
const fmark = this.view.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
@@ -425,8 +426,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
vcenterToggle = () => {
- if (this.dataDoc) this.dataDoc._layout_centered = !this.dataDoc._layout_centered;
- else Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered;
+ if (this.dataDoc) this.dataDoc.text_centered = !this.dataDoc.text_centered;
+ else Doc.UserDoc().textCentered = !Doc.UserDoc().textCentered;
};
align = (view: EditorView | undefined, dispatch: undefined | ((tr: Transaction) => void), alignment: 'left' | 'right' | 'center') => {
if (view && dispatch && this.RootSelected) {
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 317719032..7e470a642 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -22,9 +22,7 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div style={{ backgroundColor: 'pink' }}>
<DocumentView
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
- LayoutTemplateString={undefined}
Document={this.Document}
isContentActive={returnFalse}
addDocument={returnFalse}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 31cd1603f..7e0375275 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -67,7 +67,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Since this node is being rendered with a template, this method retrieves
// the actual slide being rendered from the auto-generated rendering template
@computed get slideDoc() {
- return DocCast(this.Document.rootDocument, this.Document);
+ return this.rootDoc;
}
// this is the document in the workspaces that is targeted by the slide
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 18441f76e..bb43291ee 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -9,6 +9,8 @@ $headingHeight: 32px;
.gptPopup-summary-box {
position: fixed;
+ padding-left: 10px;
+ padding-right: 10px;
top: 115px;
left: 75px;
width: 100%;
@@ -48,7 +50,6 @@ $headingHeight: 32px;
}
label {
- color: $textgrey;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
@@ -73,7 +74,6 @@ $headingHeight: 32px;
justify-content: center;
align-items: center;
height: $inputHeight;
- background-color: white;
width: 100%;
pointer-events: all;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 67213382d..e3ee51424 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -411,7 +411,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Ask Firefly to create images"
text="Ask Firefly"
onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -426,7 +427,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
text="Ask GPT"
onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -445,7 +447,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
this.setMode(GPTPopupMode.QUIZ_RESPONSE);
this.onQuizRandom?.();
}}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -517,6 +520,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
onChange={e => onChange(e.target.value)}
onKeyDown={e => this.handleKeyPress(e, this._mode)}
type="text"
+ style={{ color: SnappingManager.userColor }}
placeholder={placeholder}
/>
<Button //
@@ -524,7 +528,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
type={Type.TERT}
icon={<AiOutlineSend />}
iconPlacement="right"
- color={SnappingManager.userVariantColor}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
onClick={() => this.callGpt(this._mode)}
/>
<DictationButton ref={r => (this._askDictation = r)} setInput={onChange} />
@@ -551,7 +556,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
</div>
</div>
<div key={rawSrc[0] + i + 'btn'} className="btn-container">
- <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
</>
))}
@@ -561,7 +566,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Generate Again"
onClick={() => this._imgTargetDoc && this.generateImage(this._imageDescription, this._imgTargetDoc, this._addToCollection)}
icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
/>
)}
</div>
@@ -603,14 +609,16 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Show originally selected text" //
text="Selection"
onClick={action(() => (this._showOriginal = true))}
- color={StrCast(SettingsManager.userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
/>
<Button
tooltip="Create a text Doc with this text and link to the text selection" //
text="Transfer To Text"
onClick={this.transferToText}
- color={StrCast(SettingsManager.userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
/>
</>
@@ -627,14 +635,16 @@ export class GPTPopup extends ObservableReactComponent<object> {
}
this.generateSummary(this._aiReferenceText);
})}
- color={StrCast(SettingsManager.userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
/>
<Button
tooltip="Create Flashcards" //
text="Create Flashcards"
onClick={this.createFlashcards}
- color={StrCast(SettingsManager.userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
/>
</>
@@ -644,7 +654,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
<div className="summarizing">
<span>{this._showOriginal ? 'Creating Cards...' : 'Summarizing'}</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} />
+ <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
)}
</div>
@@ -689,19 +699,19 @@ export class GPTPopup extends ObservableReactComponent<object> {
placeholder="Ask GPT a question about the data..."
id="search-input"
className="searchBox-input"
- style={{ width: '100%' }}
+ style={{ width: '100%', color: SnappingManager.userColor }}
/>
) : (
<>
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
- <Button tooltip="Chat with AI" text="Chat with AI" onClick={() => this.setChatEnabled(true)} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={() => this.setChatEnabled(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</>
)
) : (
<div className="summarizing">
<span>Summarizing</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
+ <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
)}
</div>
@@ -718,7 +728,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
);
heading = (headingText: string) => (
- <div className="summary-heading">
+ <div className="summary-heading" style={{ color: SnappingManager.userBackgroundColor }}>
<label className="summary-text">{headingText}</label>
{this._gptProcessing ? (
<ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
@@ -734,7 +744,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')}
/>
{[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && (
- <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+ <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
)}
</>
)}
@@ -743,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
render() {
return (
- <div className="gptPopup-summary-box" style={{ display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
+ <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
{(() => {
//prettier-ignore
switch (this._mode) {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 73c2f5eb8..ed2f661e6 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -30,6 +30,7 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
if (window?.Worker) GlobalWorkerOptions.workerSrc = 'files/pdf.worker.min.mjs';
export * from 'pdfjs-dist/build/pdf.mjs';
@@ -521,7 +522,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
- childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps & DocumentViewProps>, property: string) => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index 841546a04..86e28a768 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
import { ComputedField } from '../../../fields/ScriptField';
-import { DocCast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { DocCast, ImageCast, ImageCastToNameType, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
@@ -43,7 +43,7 @@ export class FaceRecognitionHandler {
* Loads an image
*/
private static loadImage = (imgUrl: ImageField): Promise<HTMLImageElement> => {
- const [name, type] = imgUrl.url.href.split('.');
+ const [name, type] = ImageCastToNameType(imgUrl.url.href);
const imageURL = `${name}_o.${type}`;
return new Promise((resolve, reject) => {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index f1d5f2cfe..a91ec23b8 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,7 +1,6 @@
-import { imageUrlToBase64 } from '../../../ClientUtils';
import { Doc, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
-import { DocCast, ImageCast } from '../../../fields/Types';
+import { DocCast, ImageCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { Upload } from '../../../server/SharedMediaTypes';
import { gptDescribeImage } from '../../apis/gpt/GPT';
@@ -16,17 +15,20 @@ export class DrawingFillHandler {
static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc) => {
const tags = StrListCast(drawing.$tags).map(tag => tag.slice(1));
const styles = tags.filter(tag => FireflyStylePresets.has(tag));
- const styleDocs = !Doc.Links(drawing).length
- ? styleDoc && !tags.length
- ? [styleDoc]
- : []
- : Doc.Links(drawing)
- .map(link => Doc.getOppositeAnchor(link, drawing))
- .map(anchor => anchor && DocCast(anchor.embedContainer));
- const styleUrl = await DocumentView.GetDocImage(styleDocs.filter(doc => doc?.data instanceof ImageField).lastElement())?.then(styleImg => {
- const hrefParts = ImageCast(styleImg).url.href.split('.');
- return `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
- });
+ const styleDocs = [drawing].concat(
+ drawing,
+ ...Doc.Links(drawing)
+ .map(link => Doc.getOppositeAnchor(link, drawing))
+ .map(anchor => DocCast(anchor?.annotationOn, anchor))
+ .map(anchor => anchor!),
+ ...(styleDoc ? [styleDoc] : [])
+ );
+ const styleUrl = tags.length
+ ? undefined
+ : await DocumentView.GetDocImage(styleDocs.filter(doc => doc?.data instanceof ImageField).lastElement())?.then(styleImg => {
+ const hrefParts = ImageCast(styleImg).url.href.split('.');
+ return `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
+ });
return DocumentView.GetDocImage(drawing)?.then(imageField => {
if (imageField) {
const aspectRatio = (drawing.width as number) / (drawing.height as number);
@@ -39,41 +41,42 @@ export class DrawingFillHandler {
const { href } = ImageCast(imageField).url;
const hrefParts = href.split('.');
const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
- return imageUrlToBase64(structureUrl)
- .then(gptDescribeImage)
- .then((prompt, newPrompt = user_prompt || prompt) =>
- Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
- .then(res => {
- const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
- drawing.$ai_firefly_generatedDocs = genratedDocs;
- (res as Upload.ImageInformation[]).map(info =>
- Doc.AddDocToList(
- genratedDocs,
- undefined,
- Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
- ai: 'firefly',
- tags: new List<string>(['@ai']),
- title: newPrompt,
- _data_usePath: 'alternate:hover',
- data_alternates: new List<Doc>([drawing]),
- ai_firefly_prompt: newPrompt,
- _width: 500,
- data_nativeWidth: info.nativeWidth,
- data_nativeHeight: info.nativeHeight,
- }),
- undefined,
- undefined,
- true
- )
- );
- if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight);
- })
- .catch(e => {
- if (e.toString().includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + e.toString().replace(/^[^"]*/, ''))) {
- window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
- } else alert(e.toString());
- })
- ); // prettier-ignore:q
+ return gptDescribeImage(user_prompt, structureUrl).then(newPrompt =>
+ Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
+ .then(res => {
+ const error = ('error' in res && (res.error as string)) || '';
+ if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
+ window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
+ return;
+ }
+ const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 });
+ drawing.$ai_firefly_generatedDocs = genratedDocs;
+ (res as Upload.ImageInformation[]).map(info =>
+ Doc.AddDocToList(
+ genratedDocs,
+ undefined,
+ Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
+ ai: 'firefly',
+ tags: new List<string>(['@ai']),
+ title: newPrompt,
+ _data_usePath: 'alternate:hover',
+ data_alternates: new List<Doc>([drawing]),
+ ai_firefly_prompt: newPrompt,
+ _width: 500,
+ data_nativeWidth: info.nativeWidth,
+ data_nativeHeight: info.nativeHeight,
+ }),
+ undefined,
+ undefined,
+ true
+ )
+ );
+ if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight);
+ })
+ .catch(e => {
+ alert(e.toString());
+ })
+ ); // prettier-ignore:q
}
});
};
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 00114a3f9..18e30b3c2 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -59,7 +59,7 @@ export class TopBar extends ObservableReactComponent<object> {
return SettingsManager.userVariantColor;
}
@computed get backgroundColor() {
- return PingManager.Instance.IsBeating ? SettingsManager.userBackgroundColor : Colors.MEDIUM_GRAY;
+ return SettingsManager.userBackgroundColor;
}
@observable happyHeart: boolean = PingManager.Instance.IsBeating;
@@ -86,6 +86,7 @@ export class TopBar extends ObservableReactComponent<object> {
onClick={this.navigateToHome}
icon={<FontAwesomeIcon icon={DocListCast(Doc.MySharedDocs.data_dashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'portrait' : 'home'} />}
color={this.color}
+ background={this.backgroundColor}
/>
) : (
<div className="logo-container">
@@ -166,6 +167,7 @@ export class TopBar extends ObservableReactComponent<object> {
tooltip="Open Dashboards"
size={Size.SMALL}
color={this.color}
+ background={this.backgroundColor}
style={{ fontWeight: 700, fontSize: '1rem' }}
onClick={(e: React.MouseEvent) => {
const dashView = Doc.ActiveDashboard && DocumentView.getDocumentView(Doc.ActiveDashboard);
@@ -191,22 +193,27 @@ export class TopBar extends ObservableReactComponent<object> {
type={Type.TERT}
color={SettingsManager.userColor}
background={this.variantColor}
- onClick={() => {
- SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
- }}
+ onClick={() => SharingManager.Instance.open(undefined, Doc.ActiveDashboard)}
/>
) : null}
- <IconButton tooltip="Issue Reporter ⌘I" size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
+ <IconButton tooltip="Issue Reporter ⌘I" size={Size.SMALL} color={this.color} background={this.backgroundColor} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<Flip key={this._flipDocumentation}>
- <IconButton tooltip="Documentation ⌘D" size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+ <IconButton
+ tooltip="Documentation ⌘D"
+ size={Size.SMALL}
+ color={this.color}
+ background={this.backgroundColor}
+ onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')}
+ icon={<FontAwesomeIcon icon="question-circle" />}
+ />
</Flip>
- <IconButton tooltip="Settings ⌘⇧S" size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.openMgr} icon={<FontAwesomeIcon icon="cog" />} />
+ <IconButton tooltip="Settings ⌘⇧S" size={Size.SMALL} color={this.color} background={this.backgroundColor} onClick={SettingsManager.Instance.openMgr} icon={<FontAwesomeIcon icon="cog" />} />
<IconButton
size={Size.SMALL}
onClick={ServerStats.Instance.open}
- type={Type.TERT}
tooltip={'Server is ' + (PingManager.Instance.IsBeating ? '' : 'NOT ') + 'running ' + (upToDate ? DashVersion : 'out of date version:' + DashVersion)}
color={this.happyHeart ? (upToDate ? Colors.LIGHT_BLUE : Colors.YELLOW) : Colors.ERROR_RED}
+ background={PingManager.Instance.IsBeating ? SettingsManager.userBackgroundColor : Colors.MEDIUM_GRAY}
icon={<FontAwesomeIcon icon={this.happyHeart ? 'heart' : 'heart-broken'} />}
/>
{/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.color} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4bf04da5c..e48337651 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -43,10 +43,10 @@ export namespace Field {
* @param doc doc containing key
* @param key field key to display
* @param showComputedValue whether copmuted function should display its value instead of its function
+ * @param schemaCell
* @returns string representation of the field
*/
export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean, schemaCell?: boolean): string {
- const isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const valFunc = (field: FieldType): string => {
const res =
@@ -64,7 +64,9 @@ export namespace Field {
.trim()
.replace(/^new List\((.*)\)$/, '$1');
};
- return !Field.IsField(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield);
+ const notOnTemplate = !key.startsWith('_') || doc[DocLayout] === doc;
+ const isOnDelegate = notOnTemplate && !Doc.IsDataProto(doc) && ((key.startsWith('_') && !Field.IsField(cfield)) || Object.keys(doc).includes(key.replace(/^_/, '')));
+ return (isOnDelegate ? '=' : '') + (!Field.IsField(cfield) ? '' : valFunc(cfield));
}
export function toScriptString(field: FieldType, schemaCell?: boolean) {
switch (typeof field) {
@@ -405,9 +407,9 @@ export class Doc extends RefField {
}
@computed get __DATA__(): Doc {
const self = this[SelfProxy];
- return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+ return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(Cast(self[DocLayout].rootDocument, Doc, null) || self);
}
- @computed get __LAYOUT__(): Doc | undefined {
+ @computed get __LAYOUT__(): Doc {
const self = this[SelfProxy];
const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
if (templateLayoutDoc) {
@@ -420,7 +422,7 @@ export class Doc extends RefField {
}
return Cast(self['layout_' + templateLayoutDoc.title + '(' + renderFieldKey + ')'], Doc, null) || templateLayoutDoc;
}
- return undefined;
+ return self;
}
public async [HandleUpdate](diff: { $set: { [key: string]: FieldType } } | { $unset?: unknown }) {
@@ -685,15 +687,11 @@ export namespace Doc {
}
export function MakeEmbedding(doc: Doc, id?: string) {
- const embedding = (!GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
- const layout = Doc.LayoutField(embedding);
- if (layout instanceof Doc && layout !== embedding && layout === Doc.Layout(embedding)) {
- Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
- }
+ const embedding = (!Doc.IsDataProto(doc) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
embedding.createdFrom = doc;
- embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
- !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
embedding.author = ClientUtils.CurrentUserEmail();
+ embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
+ embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
return embedding;
}
@@ -766,9 +764,8 @@ export namespace Doc {
// prune doc and do nothing
} else if (
!Doc.IsSystem(docAtKey) &&
- (key.includes('layout[') ||
- key.startsWith('layout') || //
- ['embedContainer', 'annotationOn', 'proto'].includes(key) ||
+ (key.startsWith('layout') ||
+ ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
(['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()))
) {
assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
@@ -871,15 +868,15 @@ export namespace Doc {
const expandedLayoutFieldKey = 'layout_' + templateLayoutDoc.title + '(' + templateField + ')';
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
- if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
+ if (templateLayoutDoc.rootDocument instanceof Promise) {
expandedTemplateLayout = undefined;
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
} else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
- if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
+ if (DocCast(templateLayoutDoc.rootDocument)?.[DocData] === targetDoc[DocData]) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
// eslint-disable-next-line no-param-reassign
- templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
+ templateLayoutDoc.rootDocument && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
setTimeout(
@@ -888,7 +885,6 @@ export namespace Doc {
const dataDoc = Doc.GetProto(targetDoc);
newLayoutDoc.rootDocument = targetDoc;
newLayoutDoc.embedContainer = targetDoc;
- newLayoutDoc.resolvedDataDoc = dataDoc;
newLayoutDoc.acl_Guest = SharingPermissions.Edit;
if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as List<Doc>)?.length) {
dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
@@ -912,9 +908,9 @@ export namespace Doc {
console.log('Warning: GetLayoutDataDocPair childDoc not defined');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+ const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
const templateRoot = DocCast(containerDoc?.rootDocument);
- return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
+ return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data };
}
export function FindReferences(infield: Doc | List<Doc>, references: Set<Doc>, system: boolean | undefined) {
@@ -1083,7 +1079,7 @@ export namespace Doc {
export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
- if (target.resolvedDataDoc) {
+ if (target.rootDocument) {
target[targetKey] = new PrefetchProxy(templateDoc);
} else {
titleTarget && (Doc.GetProto(target).title = titleTarget);
@@ -1116,10 +1112,10 @@ export namespace Doc {
Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
}
// get the layout string that the template uses to specify its layout
- const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
+ const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout]));
// change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
- Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
+ templateField[DocLayout].layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
return true;
}
@@ -1154,18 +1150,15 @@ export namespace Doc {
// the document containing the view layout information - will be the Document itself unless the Document has
// a layout field or 'layout' is given.
- export function Layout(doc: Doc, layout?: Doc): Doc {
- const overrideLayout = layout && Cast(doc[layout.title + '(' + StrCast(layout.isTemplateForField, 'data') + ')'], Doc, null);
- return overrideLayout || doc[DocLayout] || doc;
- }
- export function SetLayout(doc: Doc, layout: Doc | string) {
- doc[StrCast(doc.layout_fieldKey, 'layout')] = layout;
+ export function Layout(doc: Doc, template?: Doc): Doc {
+ const expandedTemplate = template && Cast(doc['layout_' + template.title + '(' + StrCast(template.isTemplateForField, 'data') + ')'], Doc, null);
+ return expandedTemplate || doc[DocLayout];
}
export function LayoutField(doc: Doc) {
return doc[StrCast(doc.layout_fieldKey, 'layout')];
}
export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
- const match = StrCast(templateLayoutString || Doc.Layout(doc).layout).match(/fieldKey={'([^']+)'}/);
+ const match = StrCast(templateLayoutString || doc[DocLayout].layout).match(/fieldKey={'([^']+)'}/);
return match?.[1] || ''; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
}
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
@@ -1301,7 +1294,7 @@ export namespace Doc {
highlightedDocs.add(doc);
doc[Highlight] = true;
doc[Animation] = presentationEffect;
- if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
+ if (dataAndDisplayDocs && !doc.rootDocument) {
// if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
highlightedDocs.add(doc[DocData]);
doc[DocData][Highlight] = true;
@@ -1324,17 +1317,7 @@ export namespace Doc {
}
export function getDocTemplate(doc?: Doc) {
- return !doc
- ? undefined
- : doc.isTemplateDoc
- ? doc
- : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
- ? doc.dragFactory
- : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
- ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
- ? Doc.Layout(doc).proto
- : Doc.Layout(doc)
- : undefined;
+ return !doc ? undefined : doc.isTemplateDoc ? doc : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory : doc[DocLayout].isTemplateDoc ? (doc[DocLayout].rootDocument ? doc[DocLayout].proto : doc[DocLayout]) : undefined;
}
export function toggleLockedPosition(doc: Doc) {
@@ -1478,9 +1461,9 @@ export namespace Doc {
const docText = (async (tdoc:Doc) => {
switch (tdoc.type) {
case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text
- case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(Doc.LayoutField(tdoc), '_o') ?? '')
+ case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(tdoc[Doc.LayoutFieldKey(tdoc)], '_o') ?? '')
.then(hrefBase64 => gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'));
- case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)]).Text;
+ case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutFieldKey(tdoc)]);
default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
}}); // prettier-ignore
return docText(doc).then(text => (doc['$' + Doc.LayoutFieldKey(doc) + '_description'] = text));
@@ -1736,7 +1719,7 @@ ScriptingGlobals.add(function idToDoc(id: string): Doc {
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renameEmbedding(doc: Doc) {
- return StrCast(doc.$title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+ return StrCast(doc.title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getProto(doc: Doc) {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index af9cb1180..dc7516f38 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -131,9 +131,13 @@ export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null)
export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) {
return Cast(field, ImageField, defaultVal);
}
-export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) {
+export function ImageCastToNameType(field: FieldResult, defaultVal: ImageField | null = null) {
const href = ImageCast(field, defaultVal)?.url.href;
- return href ? `${href.split('.')[0]}${suffix}.${href.split('.')[1]}` : null;
+ return href ? [href.replace(/.[^.]*$/, ''), href.split('.').lastElement()] : ["", ""];
+}
+export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) {
+ const [name, type] = ImageCastToNameType(field, defaultVal);
+ return name ? `${name}${suffix}.${type}` : null;
}
export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index abbe543e8..205c500a5 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -303,30 +303,23 @@ export function SetPropSetterCb(prop: string, propSetter: ((target: Doc, value:
//
// target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List.
//
-export function setter(target: ListImpl<FieldType> | Doc, inProp: string | symbol | number, value: unknown, receiver: Doc | ListImpl<FieldType>): boolean {
- if (!inProp) {
+export function setter(target: ListImpl<FieldType> | Doc, prop: string | symbol | number, value: unknown, receiver: Doc | ListImpl<FieldType>): boolean {
+ if (!prop) {
console.log('WARNING: trying to set an empty property. This should be fixed. ');
return false;
}
- let prop = inProp;
- const effectiveAcl = inProp === 'constructor' || typeof inProp === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+ const effectiveAcl = prop === 'constructor' || typeof prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
if (typeof prop === 'string' && prop.startsWith('acl_') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value as SharingPermissions))) return true;
- if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('$')) {
- prop = prop.substring(1);
- if (target.__DATA__ instanceof Doc) {
- target.__DATA__[prop] = value as FieldResult;
- return true;
- }
+ if (target instanceof Doc && typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('$')) {
+ target.__DATA__[prop.substring(1)] = value as FieldResult;
+ return true;
}
- if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) {
- if (!prop.startsWith('__')) prop = prop.substring(1);
- if (target.__LAYOUT__ instanceof Doc) {
- target.__LAYOUT__[prop] = value as FieldResult;
- return true;
- }
+ if (target instanceof Doc && typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_') && !prop.startsWith('__')) {
+ target.__LAYOUT__[prop.substring(1)] = value as FieldResult;
+ return true;
}
if (target.__fieldTuples[prop] instanceof ComputedField) {
if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e75ede9df..07428798c 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -117,7 +117,6 @@ export default class FireflyManager extends ApiManager {
generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => {
let body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`;
if (seed) {
- console.log('RECEIVED SEED', seed);
body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}}, "seeds": [${seed}]}`;
}
const fetched = this.getBearerToken().then(response =>
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index c9d5df547..7c55e4a42 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -126,11 +126,8 @@ export default class UploadManager extends ApiManager {
secureHandler: async ({ req, res }) => {
const { sources } = req.body;
if (Array.isArray(sources)) {
- const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
- res.send(results);
- return;
- }
- res.send();
+ res.send(await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))));
+ } else res.send();
},
});
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index a2747257a..ed109d8f7 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -450,14 +450,12 @@ export namespace DashUploadUtils {
* 3) the size of the image, in bytes (4432130)
* 4) the content type of the image, i.e. image/(jpeg | png | ...)
*/
- export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
- const result = await InspectImage(source);
- if (result instanceof Error) {
- return { name: result.name, message: result.message };
- }
- const outputFile = filename || result.filename || '';
- return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false);
- };
+ export const UploadImage = (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> =>
+ InspectImage(source).then(async result =>
+ result instanceof Error
+ ? ({ name: result.name, message: result.message } as Error) //
+ : UploadInspectedImage(result, filename || result.filename || '', prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false)
+ );
type md5 = 'md5';
type falsetype = false;