aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ClientUtils.ts5
-rw-r--r--src/client/Network.ts4
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts2
-rw-r--r--src/client/apis/gpt/GPT.ts6
-rw-r--r--src/client/documents/DocUtils.ts73
-rw-r--r--src/client/documents/DocumentTypes.ts23
-rw-r--r--src/client/documents/Documents.ts164
-rw-r--r--src/client/util/CalendarManager.tsx15
-rw-r--r--src/client/util/CaptureManager.tsx2
-rw-r--r--src/client/util/CurrentUserUtils.ts232
-rw-r--r--src/client/util/DictationManager.ts45
-rw-r--r--src/client/util/DocumentManager.ts21
-rw-r--r--src/client/util/DragManager.ts45
-rw-r--r--src/client/util/DropConverter.ts10
-rw-r--r--src/client/util/GroupManager.tsx14
-rw-r--r--src/client/util/History.ts2
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts14
-rw-r--r--src/client/util/LinkFollower.ts32
-rw-r--r--src/client/util/LinkManager.ts42
-rw-r--r--src/client/util/RTFMarkup.tsx4
-rw-r--r--src/client/util/ReplayMovements.ts6
-rw-r--r--src/client/util/ScriptManager.ts9
-rw-r--r--src/client/util/Scripting.ts8
-rw-r--r--src/client/util/SearchUtil.ts11
-rw-r--r--src/client/util/SettingsManager.tsx32
-rw-r--r--src/client/util/SharingManager.tsx6
-rw-r--r--src/client/util/SnappingManager.ts54
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx4
-rw-r--r--src/client/util/request-image-size.ts38
-rw-r--r--src/client/views/AntimodeMenu.tsx2
-rw-r--r--src/client/views/DashboardView.tsx52
-rw-r--r--src/client/views/DocComponent.tsx94
-rw-r--r--src/client/views/DocViewUtils.ts3
-rw-r--r--src/client/views/DocumentButtonBar.tsx14
-rw-r--r--src/client/views/DocumentDecorations.scss117
-rw-r--r--src/client/views/DocumentDecorations.tsx198
-rw-r--r--src/client/views/EditableView.tsx22
-rw-r--r--src/client/views/FieldsDropdown.tsx4
-rw-r--r--src/client/views/FilterPanel.tsx11
-rw-r--r--src/client/views/GlobalKeyHandler.ts29
-rw-r--r--src/client/views/InkStrokeProperties.ts13
-rw-r--r--src/client/views/InkTranscription.tsx29
-rw-r--r--src/client/views/InkingStroke.tsx12
-rw-r--r--src/client/views/LightboxView.tsx6
-rw-r--r--src/client/views/Main.tsx8
-rw-r--r--src/client/views/MainView.tsx65
-rw-r--r--src/client/views/MarqueeAnnotator.tsx22
-rw-r--r--src/client/views/ObservableReactComponent.tsx27
-rw-r--r--src/client/views/PinFuncs.ts11
-rw-r--r--src/client/views/PropertiesButtons.tsx37
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx8
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx6
-rw-r--r--src/client/views/PropertiesView.tsx72
-rw-r--r--src/client/views/ScriptBox.tsx6
-rw-r--r--src/client/views/SidebarAnnos.scss10
-rw-r--r--src/client/views/SidebarAnnos.tsx41
-rw-r--r--src/client/views/StyleProvider.scss19
-rw-r--r--src/client/views/StyleProvider.tsx73
-rw-r--r--src/client/views/StyleProviderQuiz.tsx91
-rw-r--r--src/client/views/TagsView.scss12
-rw-r--r--src/client/views/TagsView.tsx79
-rw-r--r--src/client/views/TemplateMenu.scss4
-rw-r--r--src/client/views/TemplateMenu.tsx1
-rw-r--r--src/client/views/UndoStack.tsx5
-rw-r--r--src/client/views/animationtimeline/Region.tsx32
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx56
-rw-r--r--src/client/views/animationtimeline/Track.tsx24
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx13
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx9
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss6
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx25
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx39
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx11
-rw-r--r--src/client/views/collections/CollectionMenu.tsx24
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx34
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx20
-rw-r--r--src/client/views/collections/CollectionPileView.tsx1
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx22
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx63
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx40
-rw-r--r--src/client/views/collections/CollectionSubView.tsx64
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx30
-rw-r--r--src/client/views/collections/CollectionView.tsx23
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.tsx60
-rw-r--r--src/client/views/collections/TabDocView.scss12
-rw-r--r--src/client/views/collections/TabDocView.tsx67
-rw-r--r--src/client/views/collections/TreeView.tsx43
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx94
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx143
-rw-r--r--src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx33
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx33
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx9
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx113
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx104
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss12
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx35
-rw-r--r--src/client/views/collections/collectionSchema/SchemaCellField.tsx69
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx99
-rw-r--r--src/client/views/global/globalCssVariables.module.scss2
-rw-r--r--src/client/views/global/globalScripts.ts97
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx5
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx4
-rw-r--r--src/client/views/newlightbox/ExploreView/ExploreView.tsx4
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx9
-rw-r--r--src/client/views/nodes/AudioBox.tsx4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx9
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx139
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx48
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx6
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx472
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx33
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx16
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx17
-rw-r--r--src/client/views/nodes/DiagramBox.tsx22
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx116
-rw-r--r--src/client/views/nodes/DocumentView.scss10
-rw-r--r--src/client/views/nodes/DocumentView.tsx289
-rw-r--r--src/client/views/nodes/EquationBox.tsx1
-rw-r--r--src/client/views/nodes/FieldView.tsx26
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx47
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx11
-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.scss39
-rw-r--r--src/client/views/nodes/ImageBox.tsx653
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx68
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx38
-rw-r--r--src/client/views/nodes/LabelBox.tsx10
-rw-r--r--src/client/views/nodes/LinkBox.tsx7
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx12
-rw-r--r--src/client/views/nodes/MapBox/AnimationUtility.ts2
-rw-r--r--src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx45
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss8
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx178
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx5
-rw-r--r--src/client/views/nodes/PDFBox.scss6
-rw-r--r--src/client/views/nodes/PDFBox.tsx28
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx3
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx4
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx13
-rw-r--r--src/client/views/nodes/VideoBox.tsx48
-rw-r--r--src/client/views/nodes/WebBox.tsx22
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx25
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx26
-rw-r--r--src/client/views/nodes/chatbot/tools/GetDocsTool.ts5
-rw-r--r--src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts18
-rw-r--r--src/client/views/nodes/formattedText/DailyJournal.tsx280
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss13
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx81
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx589
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx24
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts209
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx26
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts83
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx51
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts31
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts6
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditor.tsx9
-rw-r--r--src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts0
-rw-r--r--src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss24
-rw-r--r--src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx109
-rw-r--r--src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss21
-rw-r--r--src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx81
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx2
-rw-r--r--src/client/views/nodes/scrapbook/EmbeddedDocView.tsx52
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx143
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookContent.tsx23
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlot.scss85
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlot.tsx28
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts25
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx207
-rw-r--r--src/client/views/nodes/trails/PresSlideBox.scss (renamed from src/client/views/nodes/trails/PresElementBox.scss)0
-rw-r--r--src/client/views/nodes/trails/PresSlideBox.tsx (renamed from src/client/views/nodes/trails/PresElementBox.tsx)45
-rw-r--r--src/client/views/nodes/trails/index.ts2
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx77
-rw-r--r--src/client/views/pdf/Annotation.tsx7
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss20
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx200
-rw-r--r--src/client/views/pdf/PDFViewer.scss4
-rw-r--r--src/client/views/pdf/PDFViewer.tsx84
-rw-r--r--src/client/views/search/FaceRecognitionHandler.tsx35
-rw-r--r--src/client/views/search/SearchBox.tsx31
-rw-r--r--src/client/views/smartdraw/DrawingFillHandler.tsx109
-rw-r--r--src/client/views/smartdraw/SmartDrawHandler.tsx22
-rw-r--r--src/client/views/smartdraw/StickerPalette.tsx53
-rw-r--r--src/client/views/topbar/TopBar.tsx10
-rw-r--r--src/debug/Repl.tsx6
-rw-r--r--src/fields/DateField.ts4
-rw-r--r--src/fields/Doc.ts593
-rw-r--r--src/fields/List.ts6
-rw-r--r--src/fields/Proxy.ts64
-rw-r--r--src/fields/RichTextField.ts31
-rw-r--r--src/fields/RichTextUtils.ts3
-rw-r--r--src/fields/Schema.ts21
-rw-r--r--src/fields/ScriptField.ts15
-rw-r--r--src/fields/Types.ts104
-rw-r--r--src/fields/URLField.ts3
-rw-r--r--src/fields/util.ts33
-rw-r--r--src/server/ApiManagers/FireflyManager.ts224
-rw-r--r--src/server/ApiManagers/FlashcardManager.ts161
-rw-r--r--src/server/ApiManagers/UploadManager.ts7
-rw-r--r--src/server/DashSession/DashSessionAgent.ts4
-rw-r--r--src/server/DashUploadUtils.ts21
-rw-r--r--src/server/index.ts2
-rw-r--r--src/typings/index.d.ts1
219 files changed, 5952 insertions, 4653 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index e1f490c1a..03ff13924 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -476,7 +476,10 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement |
export function addStyleSheet() {
const style = document.createElement('style');
const sheets = document.head.appendChild(style);
- return sheets.sheet;
+ return sheets;
+}
+export function removeStyleSheet(sheet?: HTMLStyleElement) {
+ sheet && document.head.removeChild(sheet);
}
export function addStyleSheetRule(sheet: CSSStyleSheet | null, selector: string, css: string | { [key: string]: string }, selectorPrefix = '.') {
const propText =
diff --git a/src/client/Network.ts b/src/client/Network.ts
index a2ecf1bea..b11dcb379 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -21,10 +21,10 @@ export namespace Networking {
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
- }).then(async response => {
+ }).then(response => {
if (response.ok) return response.json() as object;
- return await response.text().then(text => ({ error: '' + response.status + ':' + response.statusText + '-' + text }));
+ return response.text().then(text => ({ error: '' + response.status + ':' + response.statusText + '-' + text }));
});
}
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 4b86a8341..15fd6313a 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -311,7 +311,7 @@ export namespace GooglePhotos {
sources
.filter(source => ImageCast(Doc.GetProto(source).data))
.forEach(async source => {
- const data = ImageCast(Doc.GetProto(source).data);
+ const data = ImageCast(Doc.GetProto(source).data)!;
const url = data.url.href;
const target = Doc.MakeEmbedding(source);
const description = parseDescription(target, descriptionKey);
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 b216551d5..df14dce5a 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -8,7 +8,6 @@ import * as JSZipUtils from '../../JSZipUtils';
import { decycle } from '../../decycler/decycler';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, FieldResult, FieldType, LinkedTo, Opt, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkData, InkDataFieldName, InkField } from '../../fields/InkField';
import { List, ListFieldName } from '../../fields/List';
@@ -56,13 +55,13 @@ export namespace DocUtils {
const matchLink = (val: string, anchor: Doc) => {
const linkedToExp = (val ?? '').split('=');
if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
- return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+ return DocCast(anchor[linkedToExp[0]]) && Field.toScriptString(DocCast(anchor[linkedToExp[0]])!) === linkedToExp[1];
};
// prettier-ignore
return (value === Doc.FilterNone && !allLinks.length) ||
(value === Doc.FilterAny && !!allLinks.length) ||
- (allLinks.some(link => matchLink(value as string, DocCast(link.link_anchor_1)) ||
- matchLink(value as string, DocCast(link.link_anchor_2)) ));
+ (allLinks.some(link => (DocCast(link.link_anchor_1) && matchLink(value as string, DocCast(link.link_anchor_1)!)) ||
+ (DocCast(link.link_anchor_2) && matchLink(value as string, DocCast(link.link_anchor_2)!)) ));
}
if (typeof value === 'string') {
value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
@@ -267,7 +266,7 @@ export namespace DocUtils {
Object.keys(funcs)
.filter(key => !key.endsWith('-setter'))
.forEach(key => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
const func = funcs[key];
if (ScriptCast(cfield)?.script.originalScript !== func) {
const setFunc = Cast(funcs[key + '-setter'], 'string', null);
@@ -284,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));
@@ -309,6 +308,7 @@ export namespace DocUtils {
*/
export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
+
if (type.indexOf('image') !== -1) {
ctor = Docs.Create.ImageDocument;
if (!options._width) options._width = 300;
@@ -376,17 +376,18 @@ export namespace DocUtils {
const documentList: ContextMenuProps[] = foo
.filter(btnDoc => !btnDoc.hidden)
- .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .map(btnDoc => DocCast(btnDoc?.dragFactory))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+ .map(doc => doc!)
.map(dragDoc => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
event: undoable(() => {
- const newDoc = DocUtils.copyDragFactory(dragDoc);
+ const newDoc = (dragDoc.isTemplateDoc ? DocUtils.delegateDragFactory : DocUtils.copyDragFactory)(dragDoc);
if (newDoc) {
- newDoc.author = ClientUtils.CurrentUserEmail();
+ newDoc._author = ClientUtils.CurrentUserEmail();
newDoc.x = x;
newDoc.y = y;
- newDoc[DocData].backgroundColor = Doc.UserDoc().textBackgroundColor;
+ newDoc.$backgroundColor = Doc.UserDoc().textBackgroundColor;
DocumentView.SetSelectOnLoad(newDoc);
if (pivotField) {
newDoc[pivotField] = pivotValue;
@@ -438,6 +439,7 @@ export namespace DocUtils {
.filter(btnDoc => !btnDoc.hidden)
.map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
+ .map(doc => doc!)
.map(dragDoc => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
event: undoable(() => {
@@ -495,11 +497,11 @@ export namespace DocUtils {
.map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
.filter(d => d.isTemplateDoc);
// bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
- // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
+ // first try to find a template that matches the specific document type (<typeName><TemplateName>). otherwise, fallback to a general match on <templateName>
!docLayoutTemplate &&
allTemplates.forEach(tempDoc => {
const templateType = StrCast(doc[templateName + '_fieldKey'] || doc.type);
- StrCast(tempDoc.title) === templateName + '_' + templateType && (docLayoutTemplate = tempDoc);
+ StrCast(tempDoc.title) === templateName + (templateType[0].toUpperCase() + templateType.slice(1)) && (docLayoutTemplate = tempDoc);
});
!docLayoutTemplate &&
allTemplates.forEach(tempDoc => {
@@ -523,18 +525,13 @@ export namespace DocUtils {
Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
}
} else {
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
- fieldTemplate = Docs.Create.TextDocument('', options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
- }
+ const fieldTemplate = (() => {
+ if (doc.data instanceof RichTextField || typeof doc.data === 'string') return Docs.Create.TextDocument('', options);
+ if (doc.data instanceof PdfField) return Docs.Create.PdfDocument('http://www.msn.com', options);
+ if (doc.data instanceof VideoField) return Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
+ if (doc.data instanceof AudioField) return Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
+ if (doc.data instanceof ImageField) return Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
+ })();
const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
@@ -579,7 +576,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]',
}),
@@ -691,6 +688,7 @@ export namespace DocUtils {
}
generatedDocuments.push(doc);
}
+ return doc;
}
export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
@@ -714,7 +712,7 @@ export namespace DocUtils {
nativeWidth: 40,
nativeHeight: 40,
})
- : Docs.Create.TextDocument('', {
+ : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument : Docs.Create.TextDocument)('', {
annotationOn,
backgroundColor,
x,
@@ -725,12 +723,11 @@ 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),
@@ -744,7 +741,7 @@ export namespace DocUtils {
if (defaultTextTemplate) {
tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
- tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
+ tbox.$proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
}
return tbox;
}
@@ -841,7 +838,7 @@ export namespace DocUtils {
export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
const generatedDocuments: Doc[] = [];
// Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid.
- Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
+ return Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
const {
source: { newFilename, mimetype },
result,
@@ -851,7 +848,9 @@ export namespace DocUtils {
overwriteDoc.loadingError = result.message;
Doc.removeCurrentlyLoading(overwriteDoc);
}
- } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
+ return undefined;
+ }
+ return newFilename && mimetype ? processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc) : undefined;
});
}
@@ -870,13 +869,13 @@ export namespace DocUtils {
const ndoc = Doc.MakeDelegateWithProto(dragFactory);
if (ndoc && dragFactory.dragFactory_count !== undefined) {
dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
- Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
+ Doc.GetProto(ndoc).title = ndoc.title;
}
return ndoc;
}
- export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
- const { clone, map, linkMap } = await Doc.MakeClone(doc);
+ export function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+ const { clone, map, linkMap } = Doc.MakeClone(doc);
const proms = new Set<string>();
function replacer(key: string, value: { url: string; [key: string]: unknown }) {
if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 00f5dedd6..cef44e999 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -31,13 +31,12 @@ export enum DocumentType {
DATAVIZ = 'dataviz',
ANNOPALETTE = 'annopalette',
LOADING = 'loading',
- SIMULATION = 'simulation', // physics simulation
MESSAGE = 'message', // chat message
// special purpose wrappers that either take no data or are compositions of lower level types
LINK = 'link',
PRES = 'presentation',
- PRESELEMENT = 'preselement',
+ PRESSLIDE = 'presslide',
COMPARISON = 'comparison',
PUSHPIN = 'pushpin',
MAPROUTE = 'maproute',
@@ -45,28 +44,34 @@ export enum DocumentType {
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
+ SCRAPBOOK = 'scrapbook',
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 19ddbdd50..bb6f5d637 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() {
@@ -35,7 +34,7 @@ export enum FInfoFieldType {
enumeration = 'enum',
date = 'date',
list = 'list',
- rtf = 'rich text',
+ rtf = 'richtext',
map = 'map',
}
export class FInfo {
@@ -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);
@@ -216,8 +218,7 @@ 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)');
+ rootDocument?: DOCt = new DocInfo('document that stores the data for compound template documents.');
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);
@@ -282,9 +283,8 @@ 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_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
_layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button');
_layout_flashcardType?: STRt = new StrInfo('flashcard style to render in ComparisonBox. currently just "flashcard".');
_layout_showTitle?: string; // field name to display in header (:hover is an optional suffix)
@@ -308,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']);
@@ -367,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);
@@ -525,6 +526,16 @@ export class DocumentOptions {
ai?: string; // to mark items as ai generated
ai_firefly_seed?: number;
ai_firefly_prompt?: string;
+
+ /**
+ * JSON‐stringified slot configuration for ScrapbookBox
+ */
+ scrapbookConfig?: string;
+
+ /**
+ * The list of embedded Doc instances in each Scrapbook slot
+ */
+ scrapbookContents?: List<Doc>;
}
export const DocOptions = new DocumentOptions();
@@ -574,19 +585,6 @@ export namespace Docs {
options: { acl: '' },
},
],
-
- // AARAV ADD //
- [
- DocumentType.JOURNAL,
- {
- layout: { view: EmptyBox, dataField: 'text' },
- options: {
- title: 'Daily Journal',
- acl_Guest: SharingPermissions.View,
- },
- },
- ],
- // AARAV ADD //
]);
const suffix = 'Proto';
@@ -677,7 +675,7 @@ export namespace Docs {
// whatever options pertain to this specific prototype
const options: DocumentOptions = {
isSystem: true,
- _layout_fieldKey: 'layout',
+ layout_fieldKey: 'layout',
title,
type,
isBaseProto: true,
@@ -687,10 +685,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);
@@ -723,7 +722,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;
@@ -838,7 +837,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,
@@ -918,25 +917,58 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
+ export function ScrapbookDocument(items: Doc[] = [], options: DocumentOptions = {}, fieldKey: string = 'items') {
+ return InstanceFromProto(
+ Prototypes.get(DocumentType.SCRAPBOOK),
+ new List<Doc>(items),
+ {
+ title:
+ options.title ??
+ new Date().toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ }),
+ ...options,
+ },
+ undefined,
+ fieldKey
+ );
+ }
+
// AARAV ADD //
export function DailyJournalDocument(text: string | RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
- const styles = {
- bold: true, // Make the journal date bold
- color: 'blue', // Set the journal date color to blue
- fontSize: 18, // Set the font size to 18px for the whole text
- };
+ // const getFormattedDate = () => {
+ // const date = new Date().toLocaleDateString(undefined, {
+ // weekday: 'long',
+ // year: 'numeric',
+ // month: 'long',
+ // day: 'numeric',
+ // });
+ // return date;
+ // };
+
+ // const getDailyText = () => {
+ // const placeholderText = 'Start writing here...';
+ // const dateText = `${getFormattedDate()}`;
+
+ // return RichTextField.textToRtfFormat(
+ // [
+ // { text: 'Journal Entry:', styles: { bold: true, color: 'black', fontSize: 20 } },
+ // { text: dateText, styles: { italic: true, color: 'gray', fontSize: 15 } },
+ // { text: placeholderText, styles: { fontSize: 14, color: 'gray' } },
+ // ],
+ // undefined,
+ // placeholderText.length
+ // );
+ // };
return InstanceFromProto(
Prototypes.get(DocumentType.JOURNAL),
- typeof text === 'string' ? RichTextField.textToRtf(text, undefined, styles, undefined) : text,
+ '',
{
- title: new Date().toLocaleDateString(undefined, {
- weekday: 'long',
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- }),
+ title: '',
...options,
},
undefined,
@@ -966,26 +998,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;
}
@@ -1060,10 +1088,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 });
}
@@ -1132,8 +1156,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
}
- export function PresElementBoxDocument() {
- return Prototypes.get(DocumentType.PRESELEMENT);
+ export function PresSlideDocument() {
+ return Prototypes.get(DocumentType.PRESSLIDE);
}
export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
@@ -1141,7 +1165,7 @@ export namespace Docs {
}
export function AnnoPaletteDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyStickers]), { ...(options || {}) });
+ return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([...(Doc.MyStickers ? [Doc.MyStickers] : [])]), { ...(options || {}) });
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
@@ -1151,9 +1175,5 @@ export namespace Docs {
export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
return InstanceFromProto(proto, undefined, options);
}
-
- export function SimulationDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.SIMULATION), undefined, { ...(options || {}) });
- }
}
}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index d28b3a2c9..b50e39c02 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -8,7 +8,6 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import { Doc, DocListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { StrCast } from '../../fields/Types';
import { Docs } from '../documents/Documents';
import { MainViewModal } from '../views/MainViewModal';
@@ -51,8 +50,6 @@ export class CalendarManager extends ObservableReactComponent<object> {
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
-
@observable private creationType: CreationType = 'new-calendar';
@observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars?.data);
@@ -97,7 +94,6 @@ export class CalendarManager extends ObservableReactComponent<object> {
}),
500
);
- this.layoutDocAcls = false;
});
constructor(props: object) {
@@ -122,9 +118,8 @@ export class CalendarManager extends ObservableReactComponent<object> {
// TODO: Make undoable
private addToCalendar = () => {
const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
- const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
+ const targetDoc = docs[0];
- console.log(targetDoc);
if (targetDoc) {
let calendar: Doc;
if (this.creationType === 'new-calendar') {
@@ -167,7 +162,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
console.log('my calendars: ', Doc.MyCalendars);
if (this.creationType === 'new-calendar') {
- Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
+ Doc.MyCalendars && Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
}
}
};
@@ -234,14 +229,14 @@ export class CalendarManager extends ObservableReactComponent<object> {
@computed
get calendarInterface() {
const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
- const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+ const targetDoc = docs[0];
return (
<div
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>
@@ -324,7 +319,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
<div className="date-range-picker-container">
<div>Select a date range: </div>
<Provider theme={defaultTheme}>
- <DateRangePicker aria-label="Select a date range" value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} />
+ <DateRangePicker aria-label="Select a date range" value={this.selectedDateRange} onChange={v => v && this.setSelectedDateRange(v)} />
</Provider>
</div>
{this.createButtonActive && (
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 47f31612f..80af78898 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -13,7 +13,7 @@ import './CaptureManager.scss';
export class CaptureManager extends React.Component<object> {
// eslint-disable-next-line no-use-before-define
public static Instance: CaptureManager;
- static _settingsStyle = addStyleSheet();
+ static _settingsStyle = addStyleSheet().sheet;
@observable _document: Opt<Doc> = undefined;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 44def115c..22084d519 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -3,7 +3,6 @@ import { reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
import { ClientUtils, OmitKeys } from "../../ClientUtils";
import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
-import { DocData } from "../../fields/DocSymbols";
import { InkEraserTool, InkInkTool, InkProperty, InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
@@ -16,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";
@@ -60,6 +59,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;
@@ -111,7 +111,7 @@ export class CurrentUserUtils {
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
- return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
+ return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script),allOpts);
});
const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true};
@@ -149,11 +149,13 @@ export class CurrentUserUtils {
{ 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"}];
+
+ const metanote = DocCast(doc.emptyMetaNote);
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;
return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
- }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
+ }), ...(metanote ? [metanote]:[]), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
@@ -211,7 +213,7 @@ export class CurrentUserUtils {
const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
const makeIconTemplate = (name: DocumentType | string | undefined, templateField: string, opts:DocumentOptions) => {
- const title = "icon" + (name ? "_" + name : "");
+ const title = "icon" + (name ? name[0].toUpperCase()+name.slice(1) : "");
const curIcon = DocCast(templateIconsDoc[title]);
const creator = (() => { switch (opts.iconTemplate) {
case DocumentType.IMG : return imageBox;
@@ -249,7 +251,6 @@ export class CurrentUserUtils {
title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc,
backgroundColor?: string, openFactoryAsDelegate?:boolean, openFactoryLocation?:string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
}[] {
- const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List<string>(["isSystem"]) });
const json = {
doc: {
type: "doc",
@@ -272,21 +273,23 @@ export class CurrentUserUtils {
// "<div style={'height:100%'}>" +
// " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._header_pointerEvents||`none`}' fontSize='{this._header_fontSize}px' height='{this._header_height}px' background='{this._header_color}' />" +
- // " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._header_height)*scale}px' height='calc({100/scale}% - {this._header_height}px)'/>" +
+ // " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{this._header_height}px' height='calc(100% - {this._header_height}px)'/>" +
// "</div>";
- const headerBtnHgt = 10;
- const headerTemplate = (opts:DocumentOptions) =>
- MakeTemplate(Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Header Template",
- layout:`<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>
- <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._header_height||0}px)' />
- <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._header_fontSize||9}px' height='{(this._header_height||0)}px' backgroundColor='{this._header_color || "lightGray"}' />
- <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._header_height=Math.min(Math.max(0,this._height-30),this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${headerBtnHgt}:0)’} > Metadata</HTMLdiv>
+ const metadataBtnHght = 10;
+ const metaNoteTemplate = (opts:DocumentOptions) =>
+ MakeTemplate(Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "MetaNote",
+ layout:`<HTMLdiv transformOrigin='top left' width='100%' height='100%'>
+ <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${metadataBtnHght}px - {this._header_height||0}px)' />
+ <FormattedTextBox {...props} noSidebar='true' dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._header_fontSize||9}px' height='{(this._header_height||0)}px' backgroundColor='{this._header_color || "lightGray"}' />
+ <HTMLdiv fontSize='${metadataBtnHght - 1}px' height='${metadataBtnHght}px' backgroundColor='yellow'
+ onClick={‘(this._header_height=(this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${metadataBtnHght}:0)’} > Metadata
+ </HTMLdiv>
</HTMLdiv>`
- }, "header"));
+ }, "metaNote"));
const slideView = (opts:DocumentOptions) =>
MakeTemplate(Docs.Create.MultirowDocument(
[
- Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
+ Docs.Create.MulticolumnDocument([], { title: "hero", _xMargin: 10, _height: 200, isSystem: true }),
Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, text_fontFamily: StrCast(Doc.UserDoc().fontFamily), text_fontSize: StrCast(Doc.UserDoc().fontSize) })
], {...opts, title: "Slide View Template"}));
const plotlyApi = () => {
@@ -323,9 +326,9 @@ export class CurrentUserUtils {
type: 'scatter'
}`);
const slide = Docs.Create.TextDocument("", opts);
- slide[DocData].text = rtfield;
- slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
- slide[DocData]._type_collection = CollectionViewType.Freeform;
+ slide.$text = rtfield;
+ slide.$layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
+ slide.$type_collection = CollectionViewType.Freeform;
slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
@@ -374,9 +377,9 @@ pie title Minerals in my tap water
"Potassium" : 50
"Magnesium" : 10.01`);
const slide = Docs.Create.TextDocument("", opts);
- slide[DocData].text = rtfield;
- slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
- slide[DocData]._type_collection = CollectionViewType.Freeform;
+ slide.$text = rtfield;
+ slide.$layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
+ slide.$_type_collection = CollectionViewType.Freeform;
slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
@@ -392,7 +395,6 @@ pie title Minerals in my tap water
{key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }},
{key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 50, _height: 50, nativeWidth: 40, nativeHeight: 40, _xMargin: 10, _yMargin: 10}},
{key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
- {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }},
{key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }},
{key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }},
{key: "Comparison", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _width: 300, _height: 300 }},
@@ -406,9 +408,11 @@ pie title Minerals in my tap water
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("", opts), opts: { _width: 300, _height: 300, }},
// AARAV ADD //
- {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts:{ _width: 300, _height: 300}},
+ {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300, }},
+ {key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
+ //{key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
{key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 500, _height: 500, _layout_fitWidth: true, }},
- {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+ {key: "MetaNote", creator: metaNoteTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
{key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
{key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
@@ -421,41 +425,46 @@ pie title Minerals in my tap water
{key: "Plotly", creator: plotlyView, opts: { _width: 300, _height: 300, }},
];
+ const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List<string>(["isSystem"]) });
emptyThings.forEach(
thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
-
+ const metanote = DocCast(Doc.UserDoc().emptyMetaNote);
+ if (metanote) {
+ metanote.title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name
+ }
+
return [
- { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)},
- { toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
- { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
- { toolTip: "Tap or drag to create a mermaid node", title: "Mermaids", icon: "rocket", dragFactory: doc.emptyMermaids as Doc, clickFactory: DocCast(doc.emptyMermaids)},
- { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)},
- { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
- { toolTip: "Tap or drag to create an image", title: "Image", icon: "image", dragFactory: doc.emptyImage as Doc, clickFactory: DocCast(doc.emptyImage)},
- { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
- { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)},
- { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
- { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)},
- { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay},
- { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
- { toolTip: "Tap or drag to create a chat assistant", title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)},
- { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
- { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
- { toolTip: "Tap or drag to create a journal entry", title: "Journal", icon: "note", dragFactory: doc.emptyDailyJournal as Doc,clickFactory:DocCast(doc.emptyDataJournal), },
- { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc, clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
- { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
+ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)},
+ { toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
+ { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
+ { toolTip: "Tap or drag to create a mermaid node", title: "Mermaids", icon: "rocket", dragFactory: doc.emptyMermaids as Doc, clickFactory: DocCast(doc.emptyMermaids)},
+ { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)},
+ { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
+ { toolTip: "Tap or drag to create an image", title: "Image", icon: "image", dragFactory: doc.emptyImage as Doc, clickFactory: DocCast(doc.emptyImage)},
+ { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
+ { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)},
+ { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
+ { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)},
+ { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay},
+ { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
+ { toolTip: "Tap or drag to create a chat assistant", title: "Chat Assist", icon: "book", dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)},
+ { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
+ { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
+ { toolTip: "Tap or drag to create a scrapbook template", title: "Scrapbook", icon: "palette", dragFactory: doc.emptyScrapbook as Doc,clickFactory:DocCast(doc.emptyScrapbook), },
+ { toolTip: "Tap or drag to create a journal entry", title: "Journal", icon: "book", dragFactory:doc.emptyDailyJournal as Doc,clickFactory: DocCast(doc.emptyDataJournal), },
+ { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon:"person-chalkboard",dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc, clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data note", title: "MetaNote", icon: "window-maximize", dragFactory: doc.emptyMetaNote as Doc, clickFactory: DocCast(doc.emptyMetaNote), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
+ { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
].map(tuple => (
{ openFactoryLocation: OpenWhere.addRight,
scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)',
onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'},
funcs: tuple.funcs,
- ...tuple, }))
+ ...tuple, }));
}
/// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
@@ -483,22 +492,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??Doc.UserDoc(), 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'])});
}
@@ -548,7 +559,6 @@ pie title Minerals in my tap water
const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
- // doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.", _layout_dontCenter: "y",
@@ -604,7 +614,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};
@@ -739,12 +749,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[] {
@@ -762,14 +772,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()'}},
@@ -781,7 +791,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_);}' }},
@@ -789,7 +799,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_);}'}},
@@ -827,36 +837,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
];
}
@@ -870,7 +877,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,
@@ -902,7 +909,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
@@ -975,7 +982,10 @@ pie title Minerals in my tap water
};
DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
- if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List<Doc>();
+ const sharedDocs = DocCast(doc.mySharedDocs);
+ if (sharedDocs) {
+ if (!Doc.GetProto(sharedDocs).data_dashboards) Doc.GetProto(sharedDocs).data_dashboards = new List<Doc>();
+ }
}
/// Import option on the left side button panel
@@ -1000,9 +1010,9 @@ pie title Minerals in my tap water
static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
const doc = docIn;
DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
- reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
+ reaction(() => DateCast(DocCast(doc.globalGroupDatabase)?.data_modificationDate),
async () => {
- const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
+ const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase)?.data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || [];
SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
}, { fireImmediately: true });
@@ -1058,11 +1068,13 @@ pie title Minerals in my tap water
SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API. This also triggers the LinkManager to be created
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs);
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
+ if (Doc.MyFilesystem) {
+ Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+ Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+ Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
+ }
- Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org");
+ DocCast(Doc.UserDoc().emptyWebpage) && (Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)!).data = new WebField("https://www.wikipedia.org"));
DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
setInterval(UPDATE_SERVER_CACHE, 120000);
@@ -1142,7 +1154,7 @@ pie title Minerals in my tap water
const file = input.files?.[0];
if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
const doc = await Doc.importDocument(file);
- const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+ const list = Cast(Doc.MyImports?.data, listSpec(Doc), null);
doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
const disposer = OverlayView.ShowSpinner();
@@ -1150,7 +1162,7 @@ pie title Minerals in my tap water
if (results.length !== input.files?.length) {
alert("Error uploading files - possibly due to unsupported file types");
}
- const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+ const list = Cast(Doc.MyImports?.data, listSpec(Doc), null);
list?.splice(0, 0, ...results);
disposer();
} else {
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 897366757..dcef4a4fe 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -2,7 +2,6 @@
import * as interpreter from 'words-to-numbers';
import { ClientUtils } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
@@ -339,13 +338,12 @@ export namespace DictationManager {
{
action: (target: DocumentView) => {
const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
- const proto = newBox[DocData];
const prompt = 'Press alt + r to start dictating here...';
const head = 3;
const anchor = head + prompt.length;
const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
- proto.data = new RichTextField(proseMirrorState, prompt);
- proto.backgroundColor = '#eeffff';
+ newBox.$data = new RichTextField(proseMirrorState, prompt);
+ newBox.$backgroundColor = '#eeffff';
target.props.addDocTab(newBox, OpenWhere.addRight);
},
},
@@ -358,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.LayoutDataKey(target.Document), created);
}
- created && Doc.AddDocToList(dataDoc, fieldKey, created);
}
},
restrictTo: [DocumentType.COL],
@@ -390,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 },
@@ -417,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/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e33449782..3bae2881e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -2,7 +2,6 @@ import { Howl } from 'howler';
import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
import { Animation, DocData } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
@@ -108,16 +107,16 @@ export class DocumentManager {
});
// gets all views
- public getDocumentViewsById(id: string) {
+ public getAllDocumentViews(doc: Doc) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
- if (view.Document[Id] === id) {
+ if (view.Document === doc) {
toReturn.push(view);
}
});
if (toReturn.length === 0) {
DocumentManager.Instance.DocumentViews.forEach(view => {
- if (view.Document[DocData]?.[Id] === id) {
+ if (view.Document[DocData] === doc) {
toReturn.push(view);
}
});
@@ -125,10 +124,6 @@ export class DocumentManager {
return toReturn;
}
- public getAllDocumentViews(doc: Doc) {
- return this.getDocumentViewsById(doc[Id]);
- }
-
public getDocumentView(target: Doc | undefined, preferredCollection?: DocumentView): DocumentView | undefined {
const docViewArray = DocumentManager.Instance.DocumentViews;
const passes = !target ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined];
@@ -178,17 +173,17 @@ export class DocumentManager {
while (
containerDocContext.length &&
DocCast(containerDocContext[0]?.embedContainer) &&
- DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking &&
+ DocCast(containerDocContext[0].embedContainer)!._type_collection !== CollectionViewType.Docking &&
(includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
) {
- containerDocContext = [Cast(containerDocContext[0].embedContainer, Doc, null), ...containerDocContext];
+ containerDocContext = [DocCast(containerDocContext[0].embedContainer)!, ...containerDocContext];
}
return containerDocContext;
}
static _howl: Howl;
static playAudioAnno(doc: Doc) {
- const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
+ const anno = Cast(doc[Doc.LayoutDataKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
this._howl?.stop();
if (anno instanceof AudioField) {
@@ -253,7 +248,7 @@ export class DocumentManager {
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
const options = optionsIn;
- Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
+ Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
let activatedTab = false;
@@ -277,7 +272,7 @@ export class DocumentManager {
}));
if (options.openLocation?.includes(OpenWhere.lightbox)) {
// even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
- const target = DocCast(targetDoc.annotationOn, targetDoc);
+ const target = DocCast(targetDoc.annotationOn, targetDoc)!;
const compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView;
if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) {
await new Promise<void>(waitres => {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2a7859f09..a66e6998b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -234,29 +234,27 @@ export namespace DragManager {
dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
- const finishDrag = async (e: DragCompleteEvent) => {
+ const finishDrag = (e: DragCompleteEvent) => {
const { docDragData } = e;
setTimeout(() => dragData.dragEnding?.());
onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
- docDragData.droppedDocuments = (
- await Promise.all(
- dragData.draggedDocuments.map(async d =>
- !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
- ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result as Doc)
- : docDragData.dropAction === dropActionType.embed
- ? Doc.BestEmbedding(d)
- : docDragData.dropAction === dropActionType.add
- ? d
- : docDragData.dropAction === dropActionType.proto
- ? d[DocData]
- : docDragData.dropAction === dropActionType.copy
- ? (await Doc.MakeClone(d)).clone
- : d
- )
+ docDragData.droppedDocuments = dragData.draggedDocuments
+ .map(d =>
+ !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
+ ? addAudioTag(ScriptCast(d.onDragStart)!.script.run({ this: d }).result as Doc)
+ : docDragData.dropAction === dropActionType.embed
+ ? Doc.BestEmbedding(d)
+ : docDragData.dropAction === dropActionType.add
+ ? d
+ : docDragData.dropAction === dropActionType.proto
+ ? d[DocData]
+ : docDragData.dropAction === dropActionType.copy
+ ? Doc.MakeClone(d).clone
+ : d
)
- ).filter(d => d);
+ .filter(d => d);
![dropActionType.same, dropActionType.proto].includes(StrCast(docDragData.dropAction) as dropActionType) &&
docDragData.droppedDocuments
// .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
@@ -280,11 +278,12 @@ export namespace DragManager {
export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
+ const bdData = bd[DocData];
params.forEach(p => {
- Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc));
+ Object.keys(vars).indexOf(p) !== -1 && (bdData[p] = new PrefetchProxy(vars[p] as Doc));
}); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
- bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
+ bd.$onClick_paramFieldKeys = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
@@ -363,7 +362,7 @@ export namespace DragManager {
};
}
- async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+ function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
const dropArgs = {
cancelable: true, // allows preventDefault() to be called to cancel the drop
bubbles: true,
@@ -379,7 +378,7 @@ export namespace DragManager {
};
target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
- await finishDrag?.(complete);
+ finishDrag?.(complete);
UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
options?.dragComplete?.(complete);
endDrag?.();
@@ -565,11 +564,11 @@ export namespace DragManager {
const targClassName = e.target instanceof HTMLElement && typeof e.target.className === 'string' ? e.target.className : '';
if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(targClassName) && docDragData.draggedDocuments.length === 1) {
if (!startWindowDragTimer) {
- startWindowDragTimer = setTimeout(async () => {
+ startWindowDragTimer = setTimeout(() => {
startWindowDragTimer = undefined;
docDragData.dropAction = docDragData.userDropAction || dropActionType.same;
AbortDrag();
- await finishDrag?.(new DragCompleteEvent(true, docDragData));
+ finishDrag?.(new DragCompleteEvent(true, docDragData));
DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => {
if (!aborted && (docDragData?.dropAction === dropActionType.move || docDragData?.dropAction === dropActionType.same)) {
docDragData.removeDocument?.(docDragData?.draggedDocuments[0]);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b5d29be4c..b6b111930 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,9 +1,9 @@
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';
-import { StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs, DocumentOptions } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
@@ -23,7 +23,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
*/
function makeTemplate(doc: Doc, first: boolean = true): boolean {
const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
- if (layoutDoc.layout instanceof Doc) {
+ if (DocCast(layoutDoc.layout)) {
return true; // its already a template
}
const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)?.[0];
@@ -68,7 +68,7 @@ export function MakeTemplate(doc: Doc) {
* Makes a draggable button or image that will create a template doc Instance
*/
export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) {
- const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+ const layoutDoc = doc;
if (layoutDoc.type !== DocumentType.FONTICON) {
!layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
}
@@ -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/History.ts b/src/client/util/History.ts
index 9728e3177..0df0ec337 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -94,7 +94,7 @@ export namespace HistoryUtil {
}
if (Array.isArray(value)) {
} else if (parser === true || parser === 'json') {
- value = JSON.parse(value);
+ value = value === 'undefined' ? undefined : JSON.parse(value);
} else if (parser === 'none') {
} else {
value = parser(value);
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 43807397f..eae3460b3 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,6 +1,5 @@
import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
@@ -16,13 +15,12 @@ export namespace ImageUtils {
export const AssignImgInfo = (document: Doc, data?: Upload.InspectionResults) => {
if (data) {
data.nativeWidth && (document._height = (NumCast(document._width) * data.nativeHeight) / data.nativeWidth);
- const proto = document[DocData];
- const field = Doc.LayoutFieldKey(document);
- proto[`${field}_nativeWidth`] = data.nativeWidth;
- proto[`${field}_nativeHeight`] = data.nativeHeight;
- proto[`${field}_path`] = data.source;
- proto[`${field}_exif`] = JSON.stringify(data.exifData.data);
- proto[`${field}_contentSize`] = data.contentSize ? data.contentSize : undefined;
+ const field = '$' + Doc.LayoutDataKey(document);
+ document[`${field}_nativeWidth`] = data.nativeWidth;
+ document[`${field}_nativeHeight`] = data.nativeHeight;
+ document[`${field}_path`] = data.source;
+ document[`${field}_exif`] = JSON.stringify(data.exifData.data);
+ document[`${field}_contentSize`] = data.contentSize ? data.contentSize : undefined;
}
return document;
};
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 0e67dcfaa..6081c3fae 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -43,13 +43,13 @@ export class LinkFollower {
};
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
- const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
+ const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)!);
+ const isAnchor = (source?: Doc, anchor?: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(DocCast(anchor?.annotationOn), source);
const linkDocs = link ? [link] : Doc.Links(sourceDoc);
- const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
- const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
- const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
- const backLinkWithoutTargetView = backLinks.find(l => !getView(DocCast(l.link_anchor_1)));
+ const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_1))); // link docs where 'sourceDoc' is link_anchor_1
+ const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_2))); // link docs where 'sourceDoc' is link_anchor_2
+ const fwdLinkWithoutTargetView = fwdLinks.find(l => !DocCast(l.link_anchor_2) || !getView(DocCast(l.link_anchor_2)!));
+ const backLinkWithoutTargetView = backLinks.find(l => !DocCast(l.link_anchor_1) || !getView(DocCast(l.link_anchor_1)!));
const linkWithoutTargetDoc = traverseBacklink === undefined ? (fwdLinkWithoutTargetView ?? backLinkWithoutTargetView) : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
@@ -60,17 +60,17 @@ export class LinkFollower {
return false;
}
followLinks.forEach(async linkDoc => {
- const target = (
+ const target = DocCast(
sourceDoc === linkDoc.link_anchor_1
? linkDoc.link_anchor_2
: sourceDoc === linkDoc.link_anchor_2
? linkDoc.link_anchor_1
- : Doc.AreProtosEqual(sourceDoc, linkDoc.link_anchor_1 as Doc) || Doc.AreProtosEqual((linkDoc.link_anchor_1 as Doc).annotationOn as Doc, sourceDoc)
+ : Doc.AreProtosEqual(sourceDoc, DocCast(linkDoc.link_anchor_1)) || Doc.AreProtosEqual(DocCast(DocCast(linkDoc.link_anchor_1)?.annotationOn), sourceDoc)
? linkDoc.link_anchor_2
: linkDoc.link_anchor_1
- ) as Doc;
- const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+ );
if (target) {
+ const srcAnchor = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
const doFollow = (canToggle?: boolean) => {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
const options: FocusViewOptions = {
@@ -102,14 +102,16 @@ export class LinkFollower {
if (srcAnchor.followLinkLocation === OpenWhere.inParent) {
const sourceDocParent = DocCast(sourceDoc.embedContainer);
if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) {
- Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target);
+ Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutDataKey(target.embedContainer), target);
movedTarget = true;
}
- if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) {
- Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
- movedTarget = true;
+ if (sourceDocParent) {
+ if (!DocListCast(sourceDocParent[Doc.LayoutDataKey(sourceDocParent)]).includes(target)) {
+ Doc.AddDocToList(sourceDocParent, Doc.LayoutDataKey(sourceDocParent), target);
+ movedTarget = true;
+ }
+ Doc.SetContainer(target, sourceDocParent);
}
- Doc.SetContainer(target, sourceDocParent);
}
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d04d41968..d8e0c4cbe 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -136,7 +136,7 @@ export class LinkManager {
true
);
FieldLoader.ServerLoadStatus.message = 'links';
- this.addLinkDB(Doc.LinkDBDoc());
+ Doc.LinkDBDoc() && this.addLinkDB(Doc.LinkDBDoc()!);
}
public createlink_relationshipLists = () => {
@@ -148,16 +148,21 @@ export class LinkManager {
public addLink(linkDoc: Doc, checkExists = false) {
Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
- if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
- Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
- // eslint-disable-next-line no-use-before-define
- setTimeout(UPDATE_SERVER_CACHE, 100);
+ if (Doc.LinkDBDoc()) {
+ if (!checkExists || !DocListCast(Doc.LinkDBDoc()!.data).includes(linkDoc)) {
+ Doc.AddDocToList(Doc.LinkDBDoc()!, 'data', linkDoc);
+ // eslint-disable-next-line no-use-before-define
+ setTimeout(UPDATE_SERVER_CACHE, 100);
+ }
}
}
public deleteLink(linkDoc: Doc) {
- const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
- linkDoc[DocData].link_anchor_1 = linkDoc[DocData].link_anchor_2 = undefined;
- return ret;
+ if (Doc.LinkDBDoc()) {
+ const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc()!, 'data', linkDoc);
+ linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined;
+ return ret;
+ }
+ return false;
}
public deleteAllLinksOnAnchor(anchor: Doc) {
LinkManager.Instance.relatedLinker(anchor).forEach(LinkManager.Instance.deleteLink);
@@ -178,8 +183,8 @@ export class LinkManager {
}
const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as '0' | '1' | '2'));
- const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
- const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']);
+ const anchorRoot = DocCast(anchor.rootDocument, anchor)!; // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
+ const annos = DocListCast(anchorRoot[Doc.LayoutDataKey(anchor) + '_annotations']);
return Array.from(
annos.reduce((set, anno) => {
if (!processed.includes(anno)) {
@@ -212,7 +217,8 @@ export class LinkManager {
}
// finds the opposite anchor of a given anchor in a link
- public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
+ public static getOppositeAnchor(linkDoc: Doc | undefined, anchor: Doc | undefined): Doc | undefined {
+ if (!linkDoc || !anchor) return undefined;
const id = LinkManager.anchorIndex(linkDoc, anchor);
const a1 = DocCast(linkDoc.link_anchor_1);
const a2 = DocCast(linkDoc.link_anchor_2);
@@ -242,11 +248,13 @@ export function UPDATE_SERVER_CACHE() {
.filter(doc => doc instanceof Doc)
.map(doc => doc as Doc);
const references = new Set<Doc>(prototypes);
+ DocCast(Doc.UserDoc().myLinkDatabase) && references.add(DocCast(Doc.UserDoc().myLinkDatabase)!); // prevent crawling through link database here -- see below
Doc.FindReferences(Doc.UserDoc(), references, undefined);
- DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
- if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
- Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
- Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+
+ DocListCast(DocCast(Doc.UserDoc().myLinkDatabase)?.data).forEach(link => {
+ if (DocCast(link.link_anchor_1) && !references.has(DocCast(link.link_anchor_1)!) && DocCast(link.link_anchor_2) && !references.has(DocCast(link.link_anchor_2)!)) {
+ DocCast(Doc.UserDoc().myLinkDatabase) && Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase)!, 'data', link);
+ Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
}
});
LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
@@ -257,10 +265,10 @@ export function UPDATE_SERVER_CACHE() {
cacheDocumentIds = newCacheUpdate;
// print out cached docs
- //Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
+ Doc.MyDockedBtns?.linearView_IsOpen && console.log('Set cached docs = ');
const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
- //Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+ Doc.MyDockedBtns?.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
body: {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index a01b64eda..23ef9bba0 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -115,11 +115,11 @@ export class RTFMarkup extends React.Component<object> {
{` display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`[@fieldname:value] `}</b>
+ <b style={{ fontSize: 'larger' }}>{`@fieldname:value `}</b>
{` assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`[@fieldname:=expression] `}</b>
+ <b style={{ fontSize: 'larger' }}>{`@fieldname:=expression `}</b>
{` assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`}
</p>
</div>
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index 62a09a8bc..4f0423342 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -108,9 +108,11 @@ export class ReplayMovements {
movements.forEach((movement, i) => {
if (typeof movement.doc === 'string') {
- movements[i].doc = IdToDoc(movement.doc);
- if (!movements[i].doc) {
+ const doc = IdToDoc(movement.doc);
+ if (!doc) {
console.log('ERROR: tracked doc not found');
+ } else {
+ movements[i].doc = doc;
}
}
});
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 9158f6c0b..8c7f88bf6 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -1,7 +1,6 @@
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { Docs } from '../documents/Documents';
import { ScriptingGlobals } from './ScriptingGlobals';
@@ -10,7 +9,6 @@ export class ScriptManager {
// eslint-disable-next-line no-use-before-define
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
- // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
private constructor() {
@@ -58,7 +56,7 @@ export class ScriptManager {
public static addScriptToGlobals(scriptDoc: Doc): void {
ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name));
- const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
+ const params = StrListCast(scriptDoc['data-params']);
const paramNames = params.reduce((o: string, p: string) => {
let out = o;
if (params.indexOf(p) === params.length - 1) {
@@ -69,7 +67,6 @@ export class ScriptManager {
return out;
}, '' as string);
- // eslint-disable-next-line no-new-func
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c6d98496a..2fa6e3059 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -51,9 +51,11 @@ 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) {
+ console.log('Script Compile Failed: ' + script + ' ', errors);
return { compiled: false, errors };
}
@@ -228,10 +230,8 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
}
paramNames.push(...Object.keys(params).filter(p => p !== 'this' && !Object.keys(capturedVariables).includes(p)));
- const paramList = paramNames.map(key => {
- const val = typeof params[key] === 'string' && params[key].length && !'"\'`'.includes(params[key][0]) ? `"${params[key]}"` : params[key];
- return `${key}: ${val}`;
- });
+ const paramList = paramNames.map(key => `${key}: ${params[key] === Doc.name ? 'any' : params[key]}`);
+
for (const key in capturedVariables) {
if (key !== 'this') {
const val = capturedVariables[key];
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 733eae5f4..fc3bb99ab 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -9,11 +9,11 @@ export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
- const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+ const blockedTypes = [DocumentType.PRESSLIDE, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = matchKeyNames
? []
: Object.entries(DocOptions)
- .filter(([, info]: [string, FInfo]) => !info?.searchable())
+ .filter(([, info]: [string, FieldType | FInfo | undefined]) => (info instanceof FInfo ? !info.searchable() : true))
.map(([key]) => key);
const exact = queryIn.startsWith('=');
@@ -21,9 +21,8 @@ export namespace SearchUtil {
const results = new ObservableMap<Doc, string[]>();
if (collectionDoc) {
- const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
- // eslint-disable-next-line @typescript-eslint/ban-types
- const docIDs: String[] = [];
+ const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]);
+ const docIDs: string[] = [];
SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
const dtype = StrCast(doc.type) as DocumentType;
if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
@@ -77,7 +76,7 @@ export namespace SearchUtil {
// eslint-disable-next-line no-loop-func
docs.filter(d => d && !visited.includes(d)).forEach(d => {
visited.push(d);
- const fieldKey = Doc.LayoutFieldKey(d);
+ const fieldKey = Doc.LayoutDataKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
data && newarray.push(...DocListCast(data));
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 6ea242fc3..9e79fd870 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -30,7 +30,7 @@ export enum ColorScheme {
export class SettingsManager extends React.Component<object> {
// eslint-disable-next-line no-use-before-define
public static Instance: SettingsManager;
- static _settingsStyle = addStyleSheet();
+ static _settingsStyle = addStyleSheet().sheet;
@observable private _passwordResultText = '';
@observable private _playgroundMode = false;
@@ -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"
@@ -572,7 +578,7 @@ export class SettingsManager extends React.Component<object> {
<div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
{ClientUtils.CurrentUserEmail()}
</div>
- <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
+ <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} background={SettingsManager.userColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
</div>
</div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 3a248400b..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">
@@ -633,7 +633,7 @@ export class SharingManager extends React.Component<object> {
private focusOn = (contents: string) => {
const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
- const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc];
+ const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.Document) : [this.targetDoc];
return (
<span
className="focus-span"
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/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 92f877859..80653779e 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable react/require-default-props */
-/* eslint-disable prefer-destructuring */
/* eslint-disable no-use-before-define */
import * as React from 'react';
import ReactMarkdown from 'react-markdown';
@@ -299,7 +297,7 @@ export function IssueView({ issue }: IssueViewProps) {
</div>
</div>
)}
- <ReactMarkdown className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
+ <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{issueBody}
</ReactMarkdown>
</div>
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 32ab23618..798390c7d 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -33,27 +33,25 @@ export function requestImageSize(url: string): Promise<ISizeCalculationResult> {
res.on('data', chunk => {
buffer = Buffer.concat([buffer, chunk]);
- });
-
- res.on('error', reject);
-
- res.on('end', () => {
- try {
- size = imageSize(buffer);
- if (size) {
- resolve(size);
- req.abort();
+ })
+ .on('error', reject)
+ .on('end', () => {
+ try {
+ size = imageSize(buffer);
+ if (size) {
+ resolve(size);
+ req.abort();
+ }
+ } catch (err) {
+ /* empty */
+ console.log('Error: ', err);
+ }
+ if (!size) {
+ reject(new Error('Image has no size'));
+ return;
}
- } catch (err) {
- /* empty */
- console.log('Error: ', err);
- }
- if (!size) {
- reject(new Error('Image has no size'));
- return;
- }
- resolve(size);
- });
+ resolve(size);
+ });
});
req.on('error', reject);
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/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 7f0118ed3..3ceb23ffd 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
import { FaPlus } from 'react-icons/fa';
import { ClientUtils } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
-import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
+import { AclPrivate, DocAcl } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
@@ -113,12 +113,12 @@ export class DashboardView extends ObservableReactComponent<object> {
getDashboards = (whichGroup: DashboardGroup) => {
if (whichGroup === DashboardGroup.MyDashboards) {
- return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail());
+ return DocListCast(Doc.MyDashboards?.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail());
}
- return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
+ return DocListCast(Doc.MySharedDocs?.data_dashboards).filter(doc => doc.dockingConfig);
};
- isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
+ isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs?.viewed).includes(dashboard);
@undoBatch
createNewDashboard = (name: string, background?: string) => {
@@ -155,7 +155,7 @@ export class DashboardView extends ObservableReactComponent<object> {
@action
openNewDashboardModal = () => {
this.openModal = true;
- this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`);
+ this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards?.data).length + 1}`);
};
_downX: number = 0;
@@ -191,7 +191,7 @@ export class DashboardView extends ObservableReactComponent<object> {
<Button
text={'Shared Dashboards (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
- color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
+ color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs?.viewed).includes(dash)) ? 'green' : color}
align="flex-start"
onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}
fillWidth
@@ -226,7 +226,7 @@ export class DashboardView extends ObservableReactComponent<object> {
color={color}
val={StrCast(dashboard.title)}
setVal={val => {
- dashboard[DocData].title = val;
+ dashboard.$title = val;
}}
/>
{this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div />}
@@ -275,7 +275,7 @@ export class DashboardView extends ObservableReactComponent<object> {
}
public static openSharedDashboard = (dashboard: Doc) => {
- Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
+ Doc.MySharedDocs && Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
DashboardView.openDashboard(Doc.BestEmbedding(dashboard));
};
@@ -283,8 +283,8 @@ export class DashboardView extends ObservableReactComponent<object> {
/// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
public static openDashboard = (doc: Doc | undefined, fromHistory = false) => {
if (!doc) return false;
- Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
- Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc);
+ Doc.MyDashboards && Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
+ Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc);
// this has the side-effect of setting the main container since we're assigning the active/guest dashboard
Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc);
@@ -316,11 +316,11 @@ export class DashboardView extends ObservableReactComponent<object> {
};
public static removeDashboard = (dashboard: Doc) => {
- const dashboards = DocListCast(Doc.MyDashboards.data).filter(dash => dash !== dashboard);
+ const dashboards = DocListCast(Doc.MyDashboards?.data).filter(dash => dash !== dashboard);
undoable(() => {
if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.lastElement());
- Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
- Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true);
+ Doc.MyDashboards && Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
+ Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true);
if (!dashboards.length) Doc.ActivePage = 'home';
}, 'remove dashboard')();
};
@@ -403,13 +403,13 @@ export class DashboardView extends ObservableReactComponent<object> {
},
],
};
- if (dashboard.dockingConfig && dashboard.dockingConfig !== dashboard[DocData].dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
- else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
+ const dockingOnLayout = dashboard._dockingConfig && dashboard._dockingConfig !== dashboard.$dockingConfig;
+ dashboard[`${dockingOnLayout ? '_' : '$'}dockingConfig`] = JSON.stringify(reset);
return reset;
};
public static createNewDashboard = (id?: string, name?: string, background?: string) => {
- const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1;
+ const dashboardCount = DocListCast(Doc.MyDashboards?.data).length + 1;
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
@@ -425,16 +425,16 @@ export class DashboardView extends ObservableReactComponent<object> {
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
- Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
- dashboardDoc.pane_count = 1;
- freeformDoc.embedContainer = dashboardDoc;
- dashboardDoc.myOverlayDocs = new List<Doc>();
- dashboardDoc[DocData].myPublishedDocs = new List<Doc>();
- dashboardDoc[DocData].myTagCollections = new List<Doc>();
- dashboardDoc[DocData].myUniqueFaces = new List<Doc>();
- dashboardDoc[DocData].myTrails = DashboardView.SetupDashboardTrails();
- dashboardDoc[DocData].myCalendars = DashboardView.SetupDashboardCalendars();
+ Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
+ Doc.MyDashboards && Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
+ freeformDoc._embedContainer = dashboardDoc;
+ dashboardDoc.$myPaneCount = 1;
+ dashboardDoc.$myOverlayDocs = new List<Doc>();
+ dashboardDoc.$myPublishedDocs = new List<Doc>();
+ dashboardDoc.$myTagCollections = new List<Doc>();
+ dashboardDoc.$myUniqueFaces = new List<Doc>();
+ dashboardDoc.$myTrails = DashboardView.SetupDashboardTrails();
+ dashboardDoc.$myCalendars = DashboardView.SetupDashboardCalendars();
// open this new dashboard
Doc.ActiveDashboard = dashboardDoc;
Doc.ActivePage = 'dashboard';
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e351e2dec..1ecd82dbc 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -3,9 +3,9 @@ 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 { toList } from '../../fields/Types';
+import { DocCast, toList } from '../../fields/Types';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -19,6 +19,7 @@ import { FieldViewProps } from './nodes/FieldView';
* */
export interface DocComponentProps {
Document: Doc;
+ TemplateDataDocument?: Doc;
LayoutTemplate?: () => Opt<Doc>;
LayoutTemplateString?: string;
}
@@ -30,23 +31,44 @@ export function DocComponent<P extends DocComponentProps>() {
}
/**
- * This is the document being rendered. In the case of a compound template, it
- * may not be the actual document rendered and it also may not be the 'real' root document.
- * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+ * 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 Document() {
return this._props.Document;
}
/**
- * This is the document being rendered. It may be a template so it may or may no inherit from the data doc.
+ * This is the "root" Doc being rendered. In the case of a compound template Doc,
+ * 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.
+ */
+ 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
+ * case of a compound template, this will be the expanded template Doc
+ * that represents the component of the compound Doc being rendered.
+ * This may or may not inherit from the data doc.
*/
@computed get layoutDoc() {
- return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
+ return this._props.LayoutTemplateString ? this.Document : Doc.LayoutDoc(this.Document, this._props.LayoutTemplate?.());
}
/**
- * This is the unique data repository for a dcoument that stores the intrinsic document data
+ * This is the unique data repository for a document that stores the intrinsic document data.
*/
@computed get dataDoc() {
return this.Document[DocData];
@@ -75,18 +97,34 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
}
/**
- * This is the document being rendered. In the case of a compound template, it
- * may not be the actual document rendered and it also may not be the 'real' root document.
- * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+ * 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.
*/
get Document() {
return this._props.Document;
}
+
+ /**
+ * This is the "root" Doc being rendered. In the case of a compound template Doc,
+ * 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 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.
+ * This is the document being rendered by the React component. In the
+ * case of a compound template, this will be the expanded template Doc
+ * that represents the component of the compound Doc being rendered.
+ * This may or may not inherit from the data doc.
*/
@computed get layoutDoc() {
- return Doc.Layout(this.Document);
+ return this.Document[DocLayout];
}
/**
@@ -133,25 +171,40 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
}
/**
- * This is the document being rendered. In the case of a compound template, it
- * may not be the actual document rendered and it also may not be the 'real' root document.
- * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+ * 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.
*/
- @computed get Document() {
+ get Document() {
return this._props.Document;
}
+
+ /**
+ * This is the "root" Doc being rendered. In the case of a compound template Doc,
+ * 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 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.Document);
+ return this.Document[DocLayout];
}
/**
* This is the unique data repository for a dcoument that stores the intrinsic document data
*/
@computed get dataDoc() {
- return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ?
+ (this._props.TemplateDataDocument ?? this.Document[DocData]) :
+ this.Document[DocData]; // prettier-ignore
}
/**
@@ -225,8 +278,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
added.forEach(adoc => {
adoc._dragOnlyWithinContainer = undefined;
- if (annotationKey ?? this._annotationKeySuffix()) adoc[DocData].annotationOn = this.Document;
- else adoc[DocData].annotationOn = undefined;
+ adoc.$annotationOn = (annotationKey ?? this._annotationKeySuffix()) ? this.Document : undefined;
Doc.SetContainer(adoc, this.Document);
inheritParentAcls(targetDataDoc, adoc, true);
});
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..bc669fc4e 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -303,7 +303,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
DocumentView.Selected().forEach(dv => {
dv.layoutDoc._layout_showTags = !showing;
if (e.shiftKey)
- DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+ DocListCast(dv.Document[Doc.LayoutDataKey(dv.Document) + '_annotations']).forEach(doc => {
if (doc.face) doc.hidden = showing;
});
});
@@ -358,7 +358,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
view =>
view &&
DictationManager.recordAudioAnnotation(
- view.dataDoc,
+ view.Document,
view.LayoutFieldKey,
stopFunc => {
this._stopFunc = stopFunc;
@@ -479,13 +479,13 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
const doc = rootView?.Document;
if (doc) {
const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
- const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
+ const trail = DocCast(anchor.presentationTrail) ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : undefined);
if (trail !== anchor.presentationTrail) {
- DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
+ trail && DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
anchor.presentationTrail = trail;
}
Doc.ActivePresentation = trail;
- this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
+ trail && this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
}
e.stopPropagation();
};
@@ -507,12 +507,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
)}
{DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
- <div className="documentButtonBar-button">{this.templateButton}</div>
+ {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>}
{!DocumentView.Selected().some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
<div className="documentButtonBar-button">{this.pinButton}</div>
<div className="documentButtonBar-button">{this.recordButton}</div>
<div className="documentButtonBar-button">{this.calendarButton}</div>
- <div className="documentButtonBar-button">{this.aiEditorButton}</div>
+ {this.view0?.HasAIEditor ? <div className="documentButtonBar-button">{this.aiEditorButton}</div> : null}
<div className="documentButtonBar-button">{this.keywordButton}</div>
{!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
<div className="documentButtonBar-button">{this.menuButton}</div>
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 54e050f9f..ab665e984 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;
}
@@ -127,7 +127,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
} else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
DocumentView.SelectedDocs().forEach(doc => {
- doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
+ doc.$onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
});
}
const titleFieldKey = this._titleControlString.substring(1);
@@ -240,7 +240,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
} else {
// if Doc is in the sticker palette, remove the flag indicating that it's saved
const dragFactory = DocCast(iconView.Document.dragFactory);
- if (dragFactory && DocCast(dragFactory.cloneOf).savedAsSticker) DocCast(dragFactory.cloneOf).savedAsSticker = undefined;
+ if (dragFactory && DocCast(dragFactory.cloneOf)?.savedAsSticker) DocCast(dragFactory.cloneOf)!.savedAsSticker = undefined;
// if this is a face Annotation doc, then just hide it.
if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true;
@@ -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 });
@@ -445,6 +445,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@action
onPointerDown = (e: React.PointerEvent): void => {
SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+ DocumentView.Selected()
+ .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
+ .forEach(dv => {
+ dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width);
+ dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height);
+ });
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
const id = (this._resizeHdlId = e.currentTarget.className);
@@ -473,6 +479,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
};
+
+ // Modify the onPointerMove method to handle shift+click during resize
onPointerMove = (e: PointerEvent): boolean => {
const first = DocumentView.Selected()[0];
const effectiveAcl = GetEffectiveAcl(first.Document);
@@ -482,26 +490,92 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
const thisPt = // do snapping of drag point
- fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft')
+ fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft') && e.ctrlKey
? DragManager.snapDragAspect(this.projectDragToAspect(e, first, fixedAspect), fixedAspect)
: DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
const { scale, refPt } = this.getResizeVals(thisPt, dragHdl);
- !this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
- this._interactionLock = true;
- this._snapPt = thisPt;
- e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
- const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect);
- const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
- DocumentView.Selected().forEach(docView =>
- this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
- await new Promise<void>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
- }); // prettier-ignore
+ !this._interactionLock &&
+ runInAction(() => {
+ // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
+ this._interactionLock = true;
+ this._snapPt = thisPt;
+
+ const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : [];
+ const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected();
+
+ // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI)
+ e.shiftKey && outpainted.forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey }));
+
+ // Special handling for not outpainted Docs when ctrl-resizing (setup native dimesions for modification)
+ e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions());
+
+ const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect);
+ const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y };
+ notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey }));
+
+ new Promise<void>(res => setTimeout(() => res((this._interactionLock = undefined))));
+ });
return false;
};
+ resizeViewForOutpainting = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; shiftKey: boolean }) => {
+ const doc = docView.Document;
+
+ if (doc.isGroup) {
+ DocListCast(doc.data)
+ .map(member => DocumentView.getDocumentView(member, docView)!)
+ .forEach(member => this.resizeViewForOutpainting(member, refPt, scale, opts));
+ doc.xPadding = NumCast(doc.xPadding) * scale.x;
+ doc.yPadding = NumCast(doc.yPadding) * scale.y;
+ return;
+ }
+
+ // Calculate new boundary dimensions
+ const originalWidth = NumCast(doc._width);
+ const originalHeight = NumCast(doc._height);
+ const newWidth = Math.max(NumCast(doc._width_min, 25), originalWidth * scale.x);
+ const newHeight = Math.max(NumCast(doc._height_min, 10), originalHeight * scale.y);
+
+ // Apply new dimensions
+ doc._width = newWidth;
+ doc._height = newHeight;
+
+ const refCent = docView.screenToViewTransform().transformPoint(refPt[0], refPt[1]);
+ const { deltaX, deltaY } = this.realignRefPt(doc, refCent, originalWidth, originalHeight);
+ doc.x = NumCast(doc.x) + deltaX;
+ doc.y = NumCast(doc.y) + deltaY;
+ doc._layout_modificationDate = new DateField();
+ };
+
+ @action
+ onPointerUp = () => {
+ SnappingManager.SetIsResizing(undefined);
+ SnappingManager.clearSnapLines();
+
+ this._resizeHdlId = '';
+ this._resizeUndo?.end();
+
+ // detect layout_autoHeight gesture and apply
+ DocumentView.Selected().forEach(view => {
+ NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
+ });
+ // need to change points for resize, or else rotation/control points will fail.
+ this._inkDragDocs
+ .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
+ .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,
+ }))); // prettier-ignore
+ Doc.SetNativeWidth(doc, undefined);
+ Doc.SetNativeHeight(doc, undefined);
+ });
+ };
+
//
// determines how much to resize, and determines the resize reference point
//
@@ -529,7 +603,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
//
// resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
//
- resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => {
+ resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => {
const doc = docView.Document;
if (doc.isGroup) {
DocListCast(doc.data)
@@ -542,25 +616,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
- const modifyNativeDim =
- (opts.ctrlKey && doc._layout_nativeDimEditable) || // e.g., PDF or web page
- (doc._layout_reflowHorizontal && opts.dragHdl !== 'bottom' && opts.dragHdl !== 'top') || // eg rtf or some web pages
- (doc._layout_reflowVertical && (opts.dragHdl === 'bottom' || opts.dragHdl === 'top' || opts.ctrlKey)); // eg rtf, web, pdf
- if (nwidth && nheight && !modifyNativeDim) {
- // eg., dragging right resizer on PDF -- enforce native dimensions because not expliclty overridden with ctrl or bottom resize drag
+ const cornerReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && doc._layout_reflowVertical && ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'].includes(opts.dragHdl);
+ const horizontalReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && ['left', 'right'].includes(opts.dragHdl); // eg rtf or some web pages
+ const verticalReflow = !opts.freezeNativeDims && doc._layout_reflowVertical && ['bottom', 'top'].includes(opts.dragHdl); // eg rtf, web, pdf
+ // preserve aspect ratio if Doc has a native ize and drag won't cause reflow (eg, ctrl-dragging a Doc's corner doesn't allow reflow, or dragging right side of a PDF which isn't horizontally reflowable)
+ if (nwidth && nheight && !cornerReflow && !horizontalReflow && !verticalReflow) {
scale.x === 1 ? (scale.x = scale.y) : (scale.y = scale.x);
}
- if (['right', 'left'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
+ if ((horizontalReflow || cornerReflow) && Doc.NativeWidth(doc)) {
const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth;
- doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
+ doc._nativeWidth = scale.x * Doc.NativeWidth(doc);
if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth));
if (doc._layout_reflowVertical && !NumCast(doc.nativeHeight)) {
doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
}
}
- if (['bottom', 'top'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
- const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && (!doc.layout_reflowVertical || opts.ctrlKey);
+ if ((verticalReflow || cornerReflow) && Doc.NativeHeight(doc)) {
+ const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && !doc.layout_reflowVertical;
doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight));
}
@@ -606,31 +679,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
};
- @action
- onPointerUp = (): void => {
- SnappingManager.SetIsResizing(undefined);
- SnappingManager.clearSnapLines();
- this._resizeHdlId = '';
- this._resizeUndo?.end();
-
- // detect layout_autoHeight gesture and apply
- DocumentView.Selected().forEach(view => {
- NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
- });
- // need to change points for resize, or else rotation/control points will fail.
- this._inkDragDocs
- .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
- .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
- doc[DocData].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,
- }))); // prettier-ignore
- Doc.SetNativeWidth(doc, undefined);
- Doc.SetNativeHeight(doc, undefined);
- });
- };
-
@computed get selectionTitle(): string {
if (DocumentView.Selected().length === 1) {
const selected = DocumentView.Selected()[0];
@@ -719,7 +767,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 +821,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 +838,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 +855,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 +866,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 +882,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 +898,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 +911,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/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 407031b40..e7ab6a180 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -18,7 +18,7 @@ import './FilterPanel.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
interface fieldsDropdownProps {
- Document: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
+ Doc: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
selectFunc: (value: string) => void;
menuClose?: () => void;
placeholder?: string | (() => string);
@@ -36,7 +36,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
@computed get allDescendantDocs() {
const allDocs = new Set<Doc>();
- SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc));
+ SearchUtil.foreachRecursiveDoc([this._props.Doc], (depth, doc) => allDocs.add(doc));
return Array.from(allDocs);
}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 99738052d..848a77004 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -9,7 +9,6 @@ import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
@@ -89,7 +88,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
key={icon.toString()}
onClick={undoable(e => {
e.stopPropagation;
- hotKey[DocData].icon = icon.toString();
+ hotKey.$icon = icon.toString();
}, '')}
className="icon-panel-button">
<FontAwesomeIcon icon={icon} color={SnappingManager.userColor} />
@@ -183,7 +182,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
return targetView?.ComponentView?.annotationKey || (targetView?.ComponentView?.fieldKey ?? 'data');
}
@computed get targetDocChildren() {
- return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])];
+ return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutDataKey(this.Document) + '_sidebar'])];
}
@computed get rangeFilters() {
@@ -237,7 +236,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
(facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
- const fieldKey = Doc.LayoutFieldKey(t);
+ const fieldKey = Doc.LayoutDataKey(t);
const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
@@ -380,7 +379,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div className="filterBox-treeView">
<div className="filterBox-select">
<div style={{ width: '100%' }}>
- <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
+ <FieldsDropdown Doc={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
</div>
{/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
{/* <div className="filterBox-select-bool">
@@ -444,7 +443,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div>
<div className="filterBox-select">
<div style={{ width: '100%' }}>
- <FieldsDropdown Document={this.Document} selectFunc={this._props.addHotKey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
+ <FieldsDropdown Doc={this.Document} selectFunc={this._props.addHotKey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
</div>
</div>
</div>
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2d342d1b1..948beec74 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';
@@ -109,20 +108,6 @@ export class KeyManager {
preventDefault: false,
};
switch (keyname) {
- case 'u':
- if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- const ungroupings = DocumentView.Selected();
- undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
- DocumentView.DeselectAll();
- }
- break;
- case 'g':
- if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- const selected = DocumentView.Selected();
- const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView);
- cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping');
- }
- break;
case ' ':
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
@@ -254,17 +239,17 @@ export class KeyManager {
break;
case 'i':
{
- const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+ const importBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImports);
if (importBtn) {
- MainView.Instance.selectMenu(importBtn);
+ MainView.Instance.selectLeftSidebarButton(importBtn);
}
}
break;
case 's':
{
- const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+ const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyTrails);
if (trailsBtn) {
- MainView.Instance.selectMenu(trailsBtn);
+ MainView.Instance.selectLeftSidebarButton(trailsBtn);
}
}
break;
@@ -272,9 +257,9 @@ export class KeyManager {
if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) {
DocumentView.Selected()[0].ComponentView?.search?.('', false, false);
} else {
- const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
+ 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/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 358274f0e..41f38c008 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -3,7 +3,6 @@ import * as fitCurve from 'fit-curve';
import * as _ from 'lodash';
import { action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { Doc, NumListCast, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { InkData, InkField, InkTool } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
@@ -100,7 +99,7 @@ export class InkStrokeProperties {
controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y })));
// Updating the indices of the control points whose handle tangency has been broken.
- doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control)));
+ doc.brokenInkIndices = new List(NumListCast(doc.brokenInkIndices).map(control => (control > i ? control + 4 : control)));
runInAction(() => {
this._currentPoint = -1;
});
@@ -253,7 +252,7 @@ export class InkStrokeProperties {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const order = controlIndex % 4;
const closed = InkingStroke.IsClosed(ink);
- const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
+ const brokenIndices = NumListCast(inkView.Document.brokenInkIndices);
if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
const cptBefore = ink[controlIndex];
const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
@@ -342,7 +341,7 @@ export class InkStrokeProperties {
*/
snapControl = (inkView: DocumentView, controlIndex: number) => {
const inkDoc = inkView.Document;
- const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
+ const ink = Cast(inkDoc[Doc.LayoutDataKey(inkDoc)], InkField)?.inkData;
if (ink) {
const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
@@ -406,9 +405,9 @@ export class InkStrokeProperties {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const doc = view.Document;
const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []);
- const ind = brokenIndices.findIndex(value => value === controlIndex);
+ const ind = brokenIndices?.findIndex(value => value === controlIndex) ?? -1;
if (ind !== -1) {
- brokenIndices.splice(ind, 1);
+ brokenIndices!.splice(ind, 1);
const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
const angleDifference = InkStrokeProperties.angleChange(handleB, oppositeHandleA, controlPoint);
@@ -509,7 +508,7 @@ export class InkStrokeProperties {
const inkStroke = inkView?.ComponentView as InkingStroke;
const polylinePoints = this.sampleBezier(inkStroke?.inkScaledData().inkData ?? [])?.map(pt => [pt.x, pt.y]);
if (polylinePoints) {
- inkDoc[DocData].stroke = new InkField(
+ inkDoc.$stroke = new InkField(
fitCurve.default(polylinePoints, tolerance)
.reduce((cpts, bez) =>
({n: cpts.push(...bez.map(cpt => ({X:cpt[0], Y:cpt[1]}))), cpts}).cpts,
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index e800e0ae3..2e6b477e9 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -48,7 +48,7 @@ export class InkTranscription extends React.Component {
const options = {
configuration: {
server: {
- scheme: 'https',
+ scheme: 'https' as iink.TScheme,
host: 'cloud.myscript.com',
applicationKey: 'c0901093-5ac5-4454-8e64-0def0f13f2ca',
hmacKey: 'f6465cca-1856-4492-a6a4-e2395841be2f',
@@ -56,13 +56,14 @@ export class InkTranscription extends React.Component {
},
recognition: {
type: 'TEXT',
+ lang: 'en_US',
+ text: {
+ mimeTypes: ['application/vnd.myscript.jiix'] as 'application/vnd.myscript.jiix'[],
+ },
},
},
};
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const editor = new iink.Editor(r, options as any);
-
- await editor.initialize();
+ await iink.Editor.load(r, 'INKV2', options);
this._textRegister = r;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -78,7 +79,7 @@ export class InkTranscription extends React.Component {
const options = {
configuration: {
server: {
- scheme: 'https',
+ scheme: 'https' as iink.TScheme,
host: 'cloud.myscript.com',
applicationKey: 'c0901093-5ac5-4454-8e64-0def0f13f2ca',
hmacKey: 'f6465cca-1856-4492-a6a4-e2395841be2f',
@@ -86,14 +87,14 @@ export class InkTranscription extends React.Component {
},
recognition: {
type: 'TEXT',
+ lang: 'en_US',
+ text: {
+ mimeTypes: ['application/vnd.myscript.jiix'] as 'application/vnd.myscript.jiix'[],
+ },
},
},
};
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const editor = new iink.Editor(r, options as any);
-
- await editor.initialize();
- this.iinkEditor = editor;
+ this.iinkEditor = await iink.Editor.load(r, 'INKV2', options);
this._textRegister = r;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
@@ -117,9 +118,9 @@ export class InkTranscription extends React.Component {
const times: number[] = [];
validInks
- .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
+ .filter(i => Cast(i[Doc.LayoutDataKey(i)], InkField))
.forEach(i => {
- const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
+ const d = Cast(i[Doc.LayoutDataKey(i)], InkField, null);
const inkStroke = DocumentView.getDocumentView(i)?.ComponentView as InkingStroke;
strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
times.push(DateCast(i.author_date).getDate().getTime());
@@ -381,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/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f555808ef..0136f6abe 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -26,8 +26,8 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
-import { InkData, InkField } from '../../fields/InkField';
-import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { InkData } from '../../fields/InkField';
+import { BoolCast, InkCast, NumCast, RTFCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
import { Gestures } from '../../pen-gestures/GestureTypes';
import { Docs } from '../documents/Documents';
@@ -35,6 +35,7 @@ import { DocumentType } from '../documents/DocumentTypes';
import { InteractionUtils } from '../util/InteractionUtils';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { ContextMenu } from './ContextMenu';
import { ViewBoxAnnotatableComponent } from './DocComponent';
import { Colors } from './global/globalEnums';
@@ -42,14 +43,13 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
import { InkStrokeProperties } from './InkStrokeProperties';
import { InkTangentHandles } from './InkTangentHandles';
+import { InkTranscription } from './InkTranscription';
+import { DocumentView } from './nodes/DocumentView';
import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox, FormattedTextBoxProps } from './nodes/formattedText/FormattedTextBox';
import { PinDocView, PinProps } from './PinFuncs';
import { StyleProp } from './StyleProp';
import { ViewBoxInterface } from './ViewBoxInterface';
-import { InkTranscription } from './InkTranscription';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { DocumentView } from './nodes/DocumentView';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@@ -245,7 +245,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
* factor for converting between ink and screen space.
*/
inkScaledData = () => {
- const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null))?.inkData ?? [];
+ const inkData = InkCast(this.dataDoc[this.fieldKey], InkCast(this.layoutDoc[this.fieldKey]) ?? null)?.inkData ?? [];
const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 5698da785..0eb21b943 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -136,7 +136,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
return this.SetLightboxDoc(
doc,
undefined,
- [...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
+ [...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
layoutTemplate
);
};
@@ -187,7 +187,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
saved: this._savedState,
});
if (this._docTarget) {
- const fieldKey = Doc.LayoutFieldKey(this._docTarget);
+ const fieldKey = Doc.LayoutDataKey(this._docTarget);
const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])];
const links = Doc.Links(this._docTarget)
.map(link => Doc.getOppositeAnchor(link, this._docTarget!)!)
@@ -317,7 +317,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
</GestureOverlay>
</div>
- {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
+ {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Doc={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
{this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)}
{this.renderNavBtn(
this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e5347e692..b884eb8c8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -45,7 +45,6 @@ import { LoadingBox } from './nodes/LoadingBox';
import { MapBox } from './nodes/MapBox/MapBox';
import { MapPushpinBox } from './nodes/MapBox/MapPushpinBox';
import { PDFBox } from './nodes/PDFBox';
-import { PhysicsSimulationBox } from './nodes/PhysicsBox/PhysicsSimulationBox';
import { RecordingBox } from './nodes/RecordingBox';
import { ScreenshotBox } from './nodes/ScreenshotBox';
import { ScriptingBox } from './nodes/ScriptingBox';
@@ -62,10 +61,11 @@ import { FootnoteView } from './nodes/formattedText/FootnoteView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { SummaryView } from './nodes/formattedText/SummaryView';
import { ImportElementBox } from './nodes/importBox/ImportElementBox';
-import { PresBox, PresElementBox } from './nodes/trails';
+import { PresBox, PresSlideBox } from './nodes/trails';
import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
import { SearchBox } from './search/SearchBox';
import { StickerPalette } from './smartdraw/StickerPalette';
+import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox';
dotenv.config();
@@ -135,8 +135,9 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
VideoBox,
AudioBox,
RecordingBox,
+ ScrapbookBox,
PresBox,
- PresElementBox,
+ PresSlideBox,
SearchBox,
ImageLabelBox,
FaceCollectionBox,
@@ -154,7 +155,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
CalendarBox,
ComparisonBox,
LoadingBox,
- PhysicsSimulationBox,
SchemaRowBox,
ImportElementBox,
MapPushpinBox,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ef8d0c197..ad6bb09c7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -11,7 +11,6 @@ import '@dash/components/src/global/globalCssVariables.scss';
import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { DocCast, StrCast, toList } from '../../fields/Types';
import { DocServer } from '../DocServer';
@@ -94,7 +93,7 @@ export class MainView extends ObservableReactComponent<object> {
@observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
@observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
@observable private _panelContent: string = 'none';
- @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel;
+ @observable private _sidebarContent?: Doc = Doc.MyLeftSidebarPanel;
@observable private _leftMenuFlyoutWidth: number = 0;
@computed get _hideUI() {
return SnappingManager.HideUI || (this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking);
@@ -174,7 +173,7 @@ export class MainView extends ObservableReactComponent<object> {
views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur()
);
reaction(
- () => Doc.MyDockedBtns.linearView_IsOpen,
+ () => Doc.MyDockedBtns?.linearView_IsOpen,
open => SnappingManager.SetPrintToConsole(!!open)
);
const scriptTag = document.createElement('script');
@@ -200,7 +199,7 @@ export class MainView extends ObservableReactComponent<object> {
ele.outerHTML = '';
}, 1000);
}
- this._sidebarContent.proto = undefined;
+ this._sidebarContent && (this._sidebarContent.proto = undefined);
if (!MainView.Live) {
DocServer.setLivePlaygroundFields([
'dataTransition',
@@ -530,6 +529,7 @@ export class MainView extends ObservableReactComponent<object> {
fa.faColumns,
fa.faChevronCircleUp,
fa.faUpload,
+ fa.faBorderStyle,
fa.faBorderAll,
fa.faBraille,
fa.faPersonChalkboard,
@@ -636,8 +636,10 @@ export class MainView extends ObservableReactComponent<object> {
openPresentation = (pres: Doc) => {
if (pres.type === DocumentType.PRES) {
CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName);
- Doc.MyTrails && (Doc.ActivePresentation = pres);
- Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+ if (Doc.MyTrails) {
+ Doc.ActivePresentation = pres;
+ Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+ }
this.closeFlyout();
}
};
@@ -645,16 +647,16 @@ export class MainView extends ObservableReactComponent<object> {
@action
createNewFolder = async () => {
const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
- Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
+ Doc.MyFilesystem && Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
};
waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
- addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
- removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
+ addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+ removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
@computed get headerBarDocView() {
- return (
+ return !this.headerBarDoc ? null : (
<div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
<DocumentView
key="headerBarDoc"
@@ -792,16 +794,16 @@ export class MainView extends ObservableReactComponent<object> {
<div key="flyout" className="mainView-libraryFlyout-out">
{this.docButtons}
</div>
- ) : (
+ ) : !this._sidebarContent ? null : (
<div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }}>
<div className="mainView-contentArea">
<DocumentView
- Document={DocCast(this._sidebarContent.proto, this._sidebarContent)}
+ Document={DocCast(this._sidebarContent?.proto, this._sidebarContent)!}
addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={DocumentView.PinDoc}
containerViewPath={returnEmptyDocViewList}
- styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
+ styleProvider={this._sidebarContent?.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
removeDocument={returnFalse}
ScreenToLocalTransform={this.mainContainerXf}
PanelWidth={this.leftMenuFlyoutWidth}
@@ -821,7 +823,7 @@ export class MainView extends ObservableReactComponent<object> {
}
@computed get leftMenuPanel() {
- return (
+ return !Doc.MyLeftSidebarMenu ? null : (
<div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: DocumentView.LightboxDoc() ? 'none' : undefined }}>
<DocumentView
Document={Doc.MyLeftSidebarMenu}
@@ -848,8 +850,8 @@ export class MainView extends ObservableReactComponent<object> {
}
@action
- selectMenu = (button: Doc) => {
- const title = StrCast(button[DocData].title);
+ selectLeftSidebarButton = (button: Doc) => {
+ const title = StrCast(button.$title);
const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
this.closeFlyout();
if (willOpen) {
@@ -860,7 +862,9 @@ export class MainView extends ObservableReactComponent<object> {
case 'Help':
break;
default:
- this.expandFlyout(button);
+ this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
+ this._sidebarContent && (this._sidebarContent.proto = DocCast(button.target));
+ SnappingManager.SetLastPressedBtn(button[Id]);
}
}
return true;
@@ -873,8 +877,10 @@ export class MainView extends ObservableReactComponent<object> {
*/
addHotKey = (hotKey: string) => {
const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter);
- const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
- Doc.AddToFilterHotKeys(menuDoc);
+ if (filterIcons) {
+ const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
+ Doc.AddToFilterHotKeys(menuDoc);
+ }
};
@computed get mainInnerContent() {
@@ -936,29 +942,16 @@ 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';
- this._sidebarContent.proto = undefined;
+ this._sidebarContent && (this._sidebarContent.proto = undefined);
this._leftMenuFlyoutWidth = 0;
});
- remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+ remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && !!Doc.MyDockedBtns && Doc.RemoveDocFromList(Doc.MyDockedBtns!, 'data', doc), true);
moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
- addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
+ addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !!Doc.MyDockedBtns && Doc.AddDocToList(Doc.MyDockedBtns!, 'data', doc), true);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
@@ -1162,7 +1155,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/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 3f4200dce..e4811a902 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -97,7 +97,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
presentation_zoomText: true,
title: '>' + this.props.Document.title,
});
- const textRegionAnnoProto = textRegionAnno[DocData];
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
@@ -118,15 +117,15 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
})
);
- textRegionAnnoProto.y = Math.max(minY, 0);
- textRegionAnnoProto.x = Math.max(minX, 0);
- textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
- textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
- textRegionAnnoProto.backgroundColor = color;
+ textRegionAnno.$y = Math.max(minY, 0);
+ textRegionAnno.$x = Math.max(minX, 0);
+ textRegionAnno.$height = Math.max(maxY, 0) - Math.max(minY, 0);
+ textRegionAnno.$width = Math.max(maxX, 0) - Math.max(minX, 0);
+ textRegionAnno.$backgroundColor = color;
// mainAnnoDocProto.text = this._selectionText;
- textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects);
- textRegionAnnoProto.opacity = 0;
- textRegionAnnoProto.layout_unrendered = true;
+ textRegionAnno.$text_inlineAnnotations = new List<string>(annoRects);
+ textRegionAnno.$opacity = 0;
+ textRegionAnno.$layout_unrendered = true;
savedAnnoMap.clear();
return textRegionAnno;
};
@@ -226,9 +225,8 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: dragEx => {
if (!dragEx.aborted && dragEx.linkDocument) {
- const linkDocData = dragEx.linkDocument[DocData];
- linkDocData.link_relationship = 'cropped image';
- linkDocData.title = 'crop: ' + this.props.Document.title;
+ dragEx.linkDocument.$link_relationship = 'cropped image';
+ dragEx.linkDocument.$title = 'crop: ' + this.props.Document.title;
}
},
});
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index bb7a07f0e..2290516dc 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -15,6 +15,33 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj
this._props = props;
makeObservable(this);
}
+ __passiveWheel: HTMLElement | null = null;
+ _isContentActive: () => boolean | undefined = () => false;
+
+ /**
+ * default method to stop wheel events from bubbling up to parent components.
+ * @param e
+ */
+ onPassiveWheel = (e: WheelEvent) => {
+ if (this._isContentActive?.()) e.stopPropagation();
+ };
+
+ /**
+ * This fixes the problem where a component uses wheel events to scroll, but is nested inside another component that
+ * can also scroll. In that case, the wheel event will bubble up to the parent component and cause it to scroll in addition.
+ * This is based on the native HTML5 behavior where wheel events are passive by default, meaning that they do not prevent the default action of scrolling.
+ * This method should be called from a ref={} property on or above the component that uses wheel events to scroll.
+ * @param ele HTMLELement containing the component that will scroll
+ * @param isContentActive function determining if the component is active and should handle the wheel event.
+ * @param onPassiveWheel an optional function to call to handle the wheel event (and block its propagation. If omitted, the event won't propagate.
+ */
+ fixWheelEvents = (ele: HTMLElement | null, isContentActive: () => boolean | undefined, onPassiveWheel?: (e: WheelEvent) => void) => {
+ this._isContentActive = isContentActive;
+ this.__passiveWheel?.removeEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel);
+ this.__passiveWheel = ele;
+ ele?.addEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel, { passive: false });
+ };
+
componentDidUpdate(prevProps: Readonly<T>): void {
Object.keys(prevProps)
.filter(pkey => (prevProps as {[key:string]: unknown})[pkey] !== (this.props as {[key:string]: unknown})[pkey])
diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts
index ab02c2d07..805dd9a49 100644
--- a/src/client/views/PinFuncs.ts
+++ b/src/client/views/PinFuncs.ts
@@ -1,5 +1,4 @@
import { Doc, DocListCast, Field } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Copy, Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { ObjectField } from '../../fields/ObjectField';
@@ -71,14 +70,14 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
pinProps.pinData.dataview ||
pinProps.pinData.poslayoutview ||
pinProps?.activeFrame !== undefined;
- const fkey = Doc.LayoutFieldKey(targetDoc);
+ const fkey = Doc.LayoutDataKey(targetDoc);
if (pinProps.pinData.dataview) {
pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
pinDoc.config_data = Field.Copy(targetDoc[fkey]);
}
if (pinProps.pinData.dataannos) {
- const fieldKey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fieldKey + '_annotations']).filter(doc => !doc.layout_unrendered));
+ const fieldKey = '$' + Doc.LayoutDataKey(targetDoc) + +'_annotations';
+ pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[fieldKey]).filter(doc => !doc.layout_unrendered));
}
if (pinProps.pinData.inkable) {
pinDoc.config_fillColor = targetDoc.fillColor;
@@ -88,7 +87,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
}
if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
if (pinProps.pinData.clippable) {
- const fieldKey = Doc.LayoutFieldKey(targetDoc);
+ const fieldKey = Doc.LayoutDataKey(targetDoc);
pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth'];
}
if (pinProps.pinData.datarange) {
@@ -128,7 +127,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
}
if (pinProps.pinData.temporal) {
pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
- const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
+ const duration = NumCast(pinDoc[`${Doc.LayoutDataKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
}
}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 606fb17ed..28566ba1d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-unused-class-component-methods */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from '@dash/components';
import { action, computed, observable } from 'mobx';
@@ -14,7 +13,6 @@ import { RxWidth } from 'react-icons/rx';
import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
import { TfiBarChart } from 'react-icons/tfi';
import { Doc, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
@@ -120,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'
);
@@ -133,9 +131,9 @@ export class PropertiesButtons extends React.Component {
on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
() => <MdTouchApp />,
(dv, doc) => {
- const on = !!doc.onPaint;
- doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
- doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
+ const on = !!doc.$onPaint;
+ doc.$onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
+ doc.$layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
}
);
}
@@ -295,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';
@@ -387,18 +385,19 @@ export class PropertiesButtons extends React.Component {
const followLoc = this.selectedDoc._followLinkLocation;
const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
- let active = false;
- // prettier-ignore
- switch (value[0]) {
- case 'linkInPlace': active = linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView(); break;
- case 'linkOnRight': active = linkButton && followLoc === OpenWhere.addRight; break;
- case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
- case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
- case 'nothing': active = !linkButton && this.selectedDoc.onClick === undefined;break;
- default:
- }
+ const active = () => {
+ // prettier-ignore
+ switch (value[0]) {
+ case 'linkInPlace': return linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView(); break;
+ case 'linkOnRight': return linkButton && followLoc === OpenWhere.addRight; break;
+ case 'enterPortal': return linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
+ case 'toggleDetail':return ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
+ case 'nothing': return !linkButton && this.selectedDoc.onClick === undefined;break;
+ default: return false;
+ }
+ };
return (
- <div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
+ <div className="list-item" key={`${value}`} style={{ backgroundColor: active() ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
{value[1]}
</div>
);
@@ -449,7 +448,7 @@ export class PropertiesButtons extends React.Component {
};
render() {
- const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
+ const layoutField = this.selectedDoc?.[Doc.LayoutDataKey(this.selectedDoc)];
const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox;
const isInk = this.selectedDoc?.layout_isSvg;
const isImage = layoutField instanceof ImageField;
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index e30d14eae..5a00e6a89 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,18 +1,16 @@
-/* eslint-disable react/require-default-props */
-/* eslint-disable react/no-unused-prop-types */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
-import { Cast } from '../../fields/Types';
+import { DocCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { LinkManager } from '../util/LinkManager';
import { SettingsManager } from '../util/SettingsManager';
import './PropertiesDocBacklinksSelector.scss';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere } from './nodes/OpenWhere';
import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
type PropertiesDocBacklinksSelectorProps = {
Document: Doc;
@@ -26,7 +24,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
getOnClick = action((link: Doc) => {
const linkAnchor = this.props.Document;
const other = Doc.getOppositeAnchor(link, linkAnchor);
- const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
+ const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? DocCast(other.annotationOn) : other;
LinkManager.Instance.currentLink = link;
if (otherdoc) {
otherdoc.hidden = false;
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index f494ff16a..1aa956e16 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
-import { Cast, StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
import { ObservableReactComponent } from './ObservableReactComponent';
import './PropertiesDocContextSelector.scss';
import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -26,10 +26,10 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
@computed get _docs() {
if (!this._props.DocView) return [];
- const target = this._props.DocView._props.Document;
+ const target = this._props.DocView.Document;
const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
const embeddings = Doc.GetEmbeddings(target);
- const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
+ const containerProtos = embeddings.filter(embedding => DocCast(embedding.embedContainer)).reduce((set, embedding) => set.add(DocCast(embedding.embedContainer)!), new Set<Doc>());
const containerSets = Array.from(containerProtos.keys()).map(container => (Doc.GetEmbeddings(container).length ? Doc.GetEmbeddings(container) : [container]));
const containers = containerSets.reduce((p, set) => {
set.map(s => p.add(s));
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 11adf7435..acf6f928a 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';
@@ -161,7 +161,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
CollectionViewType.Card,
CollectionViewType.Carousel,
CollectionViewType.Grid,
- ].includes(this.selectedDoc?.type_collection as CollectionViewType);
+ ].includes(this.selectedDoc?.type_collection as CollectionViewType) || this.selectedDoc.$type === DocumentType.SCRAPBOOK; // prettier-ignore
}
rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
@@ -181,7 +181,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@action
docHeight = () => {
const layoutDoc = this.selectedLayoutDoc;
- if (layoutDoc && this.dataDoc) {
+ if (layoutDoc) {
return Math.max(
70,
Math.min(
@@ -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 => {
@@ -282,7 +282,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const tags = StrListCast(doc.tags);
if (!tags.includes(value)) {
tags.push(value);
- doc[DocData].tags = tags.length ? new List<string>(tags) : undefined;
+ doc.$tags = tags.length ? new List<string>(tags) : undefined;
}
return true;
}
@@ -308,11 +308,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get contextCount() {
- if (this.selectedDocumentView) {
- const target = this.selectedDocumentView.Document;
- return Doc.GetEmbeddings(target).length - 1;
- }
- return 0;
+ return this.selectedDocumentView ? Doc.GetEmbeddings(this.selectedDocumentView.Document).length - 1 : 0;
}
@computed get links() {
@@ -334,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 (
@@ -498,7 +494,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const individualTableEntries = [];
const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
- const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData];
+ const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.dataDoc;
// adds each user to usersAdded
SharingManager.Instance.users.forEach(eachUser => {
let userOnDoc = true;
@@ -529,7 +525,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
let permission;
if (this.layoutDocAcls) {
if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name;
- else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[userKey]);
+ else if (DocCast(target.embedContainer)) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer)!)[userKey]);
else permission = StrCast(Doc.GetProto(target)?.[userKey]);
} else permission = StrCast(target[userKey]);
individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
@@ -550,7 +546,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
if (this.layoutDocAcls) {
if (target[DocAcl][groupKey]) {
permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
- } else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[groupKey]);
+ } else if (DocCast(target.embedContainer)) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer)!)[groupKey]);
else permission = StrCast(Doc.GetProto(target)?.[groupKey]);
} else permission = StrCast(target[groupKey]);
groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false));
@@ -777,7 +773,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
switch (field) {
case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
- case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
+ case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.$stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
case 'wid': {
const oldWidth = NumCast(selDoc._width);
const oldHeight = NumCast(selDoc._height);
@@ -825,11 +821,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
getField(key: string) {
- return Field.toString(this.selectedDoc?.[DocData][key] as FieldType);
+ return Field.toString(this.selectedDoc?.['$' + key] as FieldType);
}
@computed get selectedStrokes() {
- return this.containsInkDoc ? DocListCast(this.selectedDoc[DocData].data) : DocumentView.SelectedSchemaDoc() ? [DocumentView.SelectedSchemaDoc()!] : DocumentView.SelectedDocs().filter(doc => doc.layout_isSvg);
+ return this.containsInkDoc ? DocListCast(this.selectedDoc.$data) : DocumentView.SelectedSchemaDoc() ? [DocumentView.SelectedSchemaDoc()!] : DocumentView.SelectedDocs().filter(doc => doc.layout_isSvg);
}
@computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore
set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore
@@ -839,10 +835,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
set shapeWid(value) { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore
@computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore
set shapeHgt(value) { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore
- @computed get strokeThk(){ return NumCast(this.selectedStrokes.lastElement()?.[DocData].stroke_width); } // prettier-ignore
+ @computed get strokeThk(){ return NumCast(this.selectedStrokes.lastElement()?.$stroke_width); } // prettier-ignore
set strokeThk(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_width = Math.round(value * 100) / 100;
+ doc.$stroke_width = Math.round(value * 100) / 100;
});
}
@@ -881,28 +877,26 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
private _lastDash: string = '2';
- @computed get colorFil() { return StrCast(this.selectedStrokes.lastElement()?.[DocData].fillColor); } // prettier-ignore
+ @computed get colorFil() { return StrCast(this.selectedStrokes.lastElement()?.$fillColor); } // prettier-ignore
set colorFil(value) {
this.selectedStrokes.forEach(doc => {
const inkStroke = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
const { inkData } = inkStroke.inkScaledData();
if (InkingStroke.IsClosed(inkData)) {
- doc[DocData].fillColor = value || undefined;
+ doc.$fillColor = value || undefined;
}
});
}
- @computed get colorStk() { return StrCast(this.selectedStrokes.lastElement()?.[DocData].color); } // prettier-ignore
+ @computed get colorStk() { return StrCast(this.selectedStrokes.lastElement()?.$color); } // prettier-ignore
set colorStk(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].color = value || undefined;
+ doc.$color = value || undefined;
});
}
@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[DocData].color = value || undefined); } // prettier-ignore
+ set borderColor(value) { this.selectedDoc && (this.selectedDoc.$color = value || undefined); } // prettier-ignore
colorButton(value: string, type: string, setter: () => void) {
return (
@@ -1034,41 +1028,41 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
set dashdStk(value) {
value && (this._lastDash = value as string);
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_dash = value ? this._lastDash : undefined;
+ doc.$stroke_dash = value ? this._lastDash : undefined;
});
}
@computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore
set widthStk(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_width = Number(value);
+ doc.$stroke_width = Number(value);
});
}
@computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore
set markScal(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_markerScale = Number(value);
+ doc.$stroke_markerScale = Number(value);
});
}
@computed get refStrength() { return Number(this.getField('drawing_refStrength') || '50'); } // prettier-ignore
set refStrength(value) {
- this.selectedDoc[DocData].drawing_refStrength = Number(value);
+ this.selectedDoc.$drawing_refStrength = Number(value);
}
@computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '5'); } // prettier-ignore
set smoothAmt(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_smoothAmount = Number(value);
+ doc.$stroke_smoothAmount = Number(value);
});
}
@computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore
set markHead(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_startMarker = value;
+ doc.$stroke_startMarker = value;
});
}
@computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore
set markTail(value) {
this.selectedStrokes.forEach(doc => {
- doc[DocData].stroke_endMarker = value;
+ doc.$stroke_endMarker = value;
});
}
@@ -1356,7 +1350,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
* of ink strokes in the properties menu.
*/
containsInk = (selectedDoc: Doc) => {
- const childDocs: Doc[] = DocListCast(selectedDoc[DocData].data);
+ const childDocs: Doc[] = DocListCast(selectedDoc.$data);
for (let i = 0; i < childDocs.length; i++) {
if (DocumentView.getDocumentView(childDocs[i])?.layoutDoc?.layout_isSvg) {
return true;
@@ -1454,7 +1448,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@undoBatch
setDescripValue = action((value: string) => {
if (this.selectedLink) {
- this.selectedLink[DocData].link_description = value;
+ this.selectedLink.$link_description = value;
}
});
@@ -1927,7 +1921,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType)
? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType)
- : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
+ : PresBox.Instance.activeItem
+ ? PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type
+ : undefined;
return (
<div className="propertiesView" style={{ width: this._props.width }}>
<div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index d05b0a6b6..52c0227d8 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
import { DragManager } from '../util/DragManager';
@@ -101,7 +100,6 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
);
}
// let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
- // eslint-disable-next-line react/sort-comp
public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }, defaultScript?: ScriptField) {
let overlayDisposer: () => void = emptyFunction;
const script = ScriptCast(doc[fieldKey]) || defaultScript;
@@ -119,7 +117,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
onCancel={overlayDisposer}
onSave={(text, onError) => {
if (!text) {
- doc[DocData][fieldKey] = undefined;
+ doc['$' + fieldKey] = undefined;
} else {
const compScript = CompileScript(text, {
params: { this: Doc.name, ...contextParams },
@@ -142,7 +140,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
div.innerHTML = 'button';
params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, () => {}, clientX, clientY);
- doc[DocData][fieldKey] = new ScriptField(compScript);
+ doc['$' + fieldKey] = new ScriptField(compScript);
overlayDisposer();
}
}}
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 816fc8ed3..573c28ccf 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,10 +1,9 @@
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
+import { ClientUtils, returnFalse, returnOne, returnZero } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
@@ -23,7 +22,7 @@ import { FieldViewProps } from './nodes/FieldView';
interface ExtraProps {
fieldKey: string;
- Document: Doc;
+ Doc: Doc;
layoutDoc: Doc;
dataDoc: Doc;
// usePanelWidth: boolean;
@@ -46,7 +45,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
_stackRef = React.createRef<CollectionStackingView>();
@computed get allMetadata() {
const keys = new Map<string, FieldResult<FieldType>>();
- DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
+ DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc =>
SearchUtil.documentKeys(doc)
.filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
.map(key => keys.set(key, doc[key]))
@@ -55,7 +54,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
}
@computed get allHashtags() {
const keys = new Set<string>();
- DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
+ DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
return Array.from(keys.keys())
.filter(key => key[0])
.filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase()))
@@ -63,7 +62,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
}
@computed get allUsers() {
const keys = new Set<string>();
- DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
+ DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
return Array.from(keys.keys()).sort();
}
@@ -73,7 +72,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
.join(' ');
const target = Docs.Create.TextDocument(startup, {
title: '-note-',
- annotationOn: this._props.Document,
+ annotationOn: this._props.Doc,
_width: 200,
_height: 50,
_layout_fitWidth: true,
@@ -88,9 +87,9 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
.filter(data => data.split(':')[0])
.filter(data => !filterExlusions?.includes(data.split(':')[0]))
.map(data => {
- const key = data.split(':')[0];
+ const key = '$' + data.split(':')[0];
const val = Field.Copy(this.allMetadata.get(key));
- target[DocData][key] = val;
+ target[key] = val;
return {
type: 'dashField',
attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
@@ -98,7 +97,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
};
});
- if (!anchor.text) anchor[DocData].text = '-selection-';
+ if (!anchor.text) anchor.$text = '-selection-';
const textLines: { type: string; attrs: object; content?: unknown[] }[] = [
{
type: 'paragraph',
@@ -133,7 +132,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
};
if (taggedContent.length) textLines.push(metadatatext);
if (textLines.length) {
- target[DocData].text = new RichTextField(
+ target.$text = new RichTextField(
JSON.stringify({
doc: {
type: 'doc',
@@ -149,7 +148,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
return target;
};
makeDocUnfiltered = (doc: Doc) => {
- if (DocListCast(this._props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ if (DocListCast(this._props.Doc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
if (this.childFilters()) {
// if any child filters exist, get rid of them
this._props.layoutDoc._childFilters = new List<string>();
@@ -190,7 +189,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
const renderTag = (tag: string) => {
const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
return (
- <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
{tag}
</div>
);
@@ -198,7 +197,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
const renderMeta = (tag: string) => {
const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
return (
- <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
{tag}
</div>
);
@@ -206,7 +205,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
const renderUsers = (user: string) => {
const active = this.childFilters().includes(`author:${user}:check`);
return (
- <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}>
+ <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, 'author', user, 'check', true, undefined, e.shiftKey)}>
{user}
</div>
);
@@ -214,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.Document.type !== DocumentType.RTF && StrCast(this._props.Document._layout_showTitle) === 'title' ? 15 : 0,
- right: 0,
- background: this._props.styleProvider?.(this._props.Document, this._props, StyleProp.WidgetColor) as string,
- width: `100%`,
- height: '100%',
+ top: this._props.Doc.type !== DocumentType.RTF && StrCast(this._props.Doc._layout_showTitle) === 'title' ? 15 : 0,
+ background: this._props.styleProvider?.(this._props.Doc, this._props, StyleProp.WidgetColor) as string,
}}>
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
@@ -229,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}
@@ -257,7 +253,6 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
renderDepth={this._props.renderDepth + 1}
type_collection={CollectionViewType.Stacking}
fieldKey={this.sidebarKey}
- pointerEvents={returnAll}
/>
</div>
</div>
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 b04b1ae65..9110e198f 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.
@@ -131,7 +129,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.TreeViewIcon: {
- const img = ImageCast(doc?.icon ?? doc?.[doc ? Doc.LayoutFieldKey(doc) : ""]);
+ const img = ImageCast(doc?.icon ?? doc?.[doc ? Doc.LayoutDataKey(doc) : ""]);
if (img) {
const ext = extname(img.url.href);
const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
@@ -166,7 +164,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
return undefined;
case StyleProp.DocContents: return undefined;
case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
- case StyleProp.Opacity: return componentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+ case StyleProp.Opacity: return componentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null) ?? null);
case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], isCaption ? lightOrDark(backgroundCol()) : StrCast(Doc.UserDoc().fontColor, color()));
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
@@ -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 (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
+ const dataKey = doc ? Doc.LayoutDataKey(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,
+ DocCast(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.PRESSLIDE: 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/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx
index db9ab831a..fe76bce28 100644
--- a/src/client/views/StyleProviderQuiz.tsx
+++ b/src/client/views/StyleProviderQuiz.tsx
@@ -1,24 +1,25 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import axios from 'axios';
+import { runInAction } from 'mobx';
import * as React from 'react';
import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
-import { emptyFunction } from '../../Utils';
+import { emptyFunction, unimplementedFunction } from '../../Utils';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { NumCast, StrCast } from '../../fields/Types';
-import { GPTCallType, gptAPICall, gptImageLabel } from '../apis/gpt/GPT';
+import { Networking } from '../Network';
+import { GPTCallType, gptAPICall } from '../apis/gpt/GPT';
import { Docs } from '../documents/Documents';
import { ContextMenu } from './ContextMenu';
import { ContextMenuProps } from './ContextMenuItem';
import { StyleProp } from './StyleProp';
-import { AnchorMenu } from './pdf/AnchorMenu';
+import './StyleProviderQuiz.scss';
import { DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { ImageBox } from './nodes/ImageBox';
import { ImageUtility } from './nodes/imageEditor/imageEditorUtils/ImageHandler';
-import './StyleProviderQuiz.scss';
+import { AnchorMenu } from './pdf/AnchorMenu';
export namespace styleProviderQuiz {
enum quizMode {
@@ -45,8 +46,8 @@ export namespace styleProviderQuiz {
* @param boxes
* @param texts
*/
- async function createBoxes(img: ImageBox, boxes: [[[number, number]]], texts: [string]) {
- img.Document._quizBoxes = new List<Doc>([]);
+ async function createBoxes(img: ImageBox, boxes: number[][][], texts: string[]) {
+ img.Document.quizBoxes = new List<Doc>([]);
for (let i = 0; i < boxes.length; i++) {
const coords = boxes[i] ? boxes[i] : [];
const width = coords[1][0] - coords[0][0];
@@ -66,8 +67,8 @@ export namespace styleProviderQuiz {
newCol.zIndex = 1000;
newCol.forceActive = true;
newCol.quiz = text;
- newCol[DocData][Doc.LayoutFieldKey(newCol) + '_transform'] = 'none';
- Doc.AddDocToList(img.Document, '_quizBoxes', newCol);
+ newCol['$' + Doc.LayoutDataKey(newCol) + '_transform'] = 'none';
+ Doc.AddDocToList(img.Document, 'quizBoxes', newCol);
img.addDocument(newCol);
// img._loading = false;
}
@@ -83,23 +84,15 @@ export namespace styleProviderQuiz {
imgBox.Document._quizMode = quiz;
const quizBoxes = DocListCast(imgBox.Document.quizBoxes);
if (!quizBoxes.length) {
- imgBox.Loading = true;
+ runInAction(() => (imgBox.Loading = true));
- const img = {
- file: i ? i : imgBox.paths[0],
- drag: i ? 'drag' : 'full',
- smart: quiz,
- };
- const response = await axios.post('http://localhost:105/labels/', img, {
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- if (response.data['boxes'].length != 0) {
- createBoxes(imgBox, response.data['boxes'], response.data['text']);
- } else {
- imgBox.Loading = false;
+ const response = (await Networking.PostToServer('/labels', { file: i ? i : imgBox.paths[0], drag: i ? 'drag' : 'full', smart: quiz })) as { result: string };
+ const replacedResponse = response.result.replace(/ '/g, '"').replace(/',/g, '",').replace(/\{'/g, '{"').replace(/':/g, '":').replace(/'\]/g, '"]').replace(/\['/g, '["');
+ const parsedResponse = JSON.parse(replacedResponse) as { boxes: number[][][]; text: string[] };
+ if (parsedResponse.boxes.length != 0) {
+ createBoxes(imgBox, parsedResponse.boxes, parsedResponse.text);
}
+ runInAction(() => (imgBox.Loading = false));
} else quizBoxes.forEach(box => (box.hidden = false));
}
@@ -117,20 +110,21 @@ export namespace styleProviderQuiz {
const blob = await ImageUtility.canvasToBlob(canvas);
return selectUrlToBase64(blob);
}
- /**
- * Create flashcards from an image.
- */
- async function getImageDesc(img: ImageBox) {
- img.Loading = true;
- try {
- const hrefBase64 = await createCanvas(img);
- const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: ');
- AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y));
- } catch (error) {
- console.log('Error', error);
- }
- img.Loading = false;
- }
+
+ // /**
+ // * Create flashcards from an image.
+ // */
+ // async function makeFlashcardsForImage(img: ImageBox) {
+ // img.Loading = true;
+ // try {
+ // const hrefBase64 = await createCanvas(img);
+ // const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: ');
+ // AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y));
+ // } catch (error) {
+ // console.log('Error', error);
+ // }
+ // img.Loading = false;
+ // }
/**
* Calls the createCanvas and pushInfo methods to convert the
@@ -230,8 +224,8 @@ export namespace styleProviderQuiz {
*/
function extractHexAndSentences(inputString: string) {
// Regular expression to match a hexadecimal number at the beginning followed by a period and sentences
- const regex = /^#([0-9A-Fa-f]+)\.\s*(.+)$/s;
- const match = inputString.match(regex);
+ const regex = /^#([0-9A-Fa-f]+)\.\s*(.+)$/;
+ const match = inputString.replace('\n', ' ').match(regex);
if (match) {
const hexNumber = match[1];
@@ -254,7 +248,7 @@ export namespace styleProviderQuiz {
function check(img: ImageBox) {
//this._loading = true;
imgQuizBoxes(img).forEach(async doc => {
- const input = StrCast(doc[DocData].title);
+ const input = StrCast(doc.$title);
if (imgQuizMode(img) == quizMode.SMART && input) {
const questionText = 'Question: What was labeled in this image?';
const rubricText = ' Rubric: ' + StrCast(doc.quiz);
@@ -271,7 +265,9 @@ export namespace styleProviderQuiz {
doc.backgroundColor = '#' + hexSent.hexNumber;
} else {
const match = compareWords(input, StrCast(doc.quiz).trim());
- doc.backgroundColor = match ? '#11c249' : '#eb2d2d';
+ if (input) {
+ doc.backgroundColor = match ? '#11c249' : '#eb2d2d';
+ }
}
});
//this._loading = false;
@@ -279,8 +275,8 @@ export namespace styleProviderQuiz {
function redo(img: ImageBox) {
imgQuizBoxes(img).forEach(doc => {
- doc[DocData].title = '';
- doc.backgroundColor = '#e4e4e4';
+ doc.$title = '';
+ doc.$backgroundColor = '#e4e4e4';
});
}
@@ -289,7 +285,7 @@ export namespace styleProviderQuiz {
*/
function exitQuizMode(img: ImageBox) {
img.Document._quizMode = quizMode.NONE;
- DocListCast(img.Document._quizBoxes).forEach(box => {
+ DocListCast(img.Document.quizBoxes).forEach(box => {
box.hidden = true;
});
}
@@ -388,10 +384,7 @@ export namespace styleProviderQuiz {
}
break;
case StyleProp.AnchorMenuItems:
- if (imgBox) {
- AnchorMenu.Instance.gptFlashcards = () => getImageDesc(imgBox);
- AnchorMenu.Instance.makeLabels = () => makeLabels(props?.DocumentView?.().ComponentView as ImageBox);
- }
+ AnchorMenu.Instance.makeLabels = imgBox ? () => makeLabels(props?.DocumentView?.().ComponentView as ImageBox) : unimplementedFunction;
}
return undefined;
}
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 93d6fb684..40d4f108d 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,8 @@ import { DocumentView } from './nodes/DocumentView';
import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
import { IconTagBox } from './nodes/IconTagBox';
import { Id } from '../../fields/FieldSymbols';
+import { StyleProp } from './StyleProp';
+import { Docs } from '../documents/Documents';
/**
* The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -72,7 +74,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
const newTagCol = new Doc();
newTagCol.title = tag;
newTagCol.collections = new List<Doc>();
- newTagCol[DocData].docs = new List<Doc>();
+ newTagCol.$docs = new List<Doc>();
Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myTagCollections', newTagCol);
return newTagCol;
@@ -82,7 +84,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
* @param tag tag string
* @returns An array of documents that contain the tag.
*/
- public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs);
+ public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.$docs);
public static docHasTag = (doc: Doc, tag: string) => StrListCast(doc?.tags).includes(tag);
/**
@@ -95,11 +97,11 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
// If the document is of type COLLECTION, make it a smart collection, otherwise, add the tag to the document.
if (doc.type === DocumentType.COL && !doc.annotationOn) {
- Doc.AddDocToList(tagCollection[DocData], 'collections', doc);
+ Doc.AddDocToList(tagCollection, 'collections', doc);
// Iterate through the tag Doc collections and add a copy of the document to each collection
- for (const cdoc of DocListCast(tagCollection[DocData].docs)) {
- if (!DocListCast(doc[DocData].data).find(d => Doc.AreProtosEqual(d, cdoc))) {
+ for (const cdoc of DocListCast(tagCollection.$docs)) {
+ if (!DocListCast(doc.$data).find(d => Doc.AreProtosEqual(d, cdoc))) {
const newEmbedding = Doc.MakeEmbedding(cdoc);
Doc.AddDocToList(doc[DocData], 'data', newEmbedding);
Doc.SetContainer(newEmbedding, doc);
@@ -111,7 +113,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
// Iterate through the tag document's collections and add a copy of the document to each collection
for (const collection of DocListCast(tagCollection.collections)) {
- if (!DocListCast(collection[DocData].data).find(d => Doc.AreProtosEqual(d, doc))) {
+ if (!DocListCast(collection.$data).find(d => Doc.AreProtosEqual(d, doc))) {
const newEmbedding = Doc.MakeEmbedding(doc);
Doc.AddDocToList(collection[DocData], 'data', newEmbedding);
Doc.SetContainer(newEmbedding, collection);
@@ -119,8 +121,8 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
}
}
- if (!doc[DocData].tags) doc[DocData].tags = new List<string>();
- const tagList = doc[DocData].tags as List<string>;
+ if (!doc.$tags) doc.$tags = new List<string>();
+ const tagList = doc.$tags as List<string>;
if (!tagList.includes(tag)) tagList.push(tag);
};
@@ -131,22 +133,22 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
* @param tagDoc doc that collections the Docs with the tag
*/
public static removeTagFromDoc = (doc: Doc, tag: string, tagDoc?: Doc) => {
- if (doc[DocData].tags) {
+ if (doc.$tags) {
if (doc.type === DocumentType.COL) {
tagDoc && Doc.RemoveDocFromList(tagDoc[DocData], 'collections', doc);
for (const cur_doc of TagItem.allDocsWithTag(tag)) {
- doc[DocData].data = new List<Doc>(DocListCast(doc[DocData].data).filter(d => !Doc.AreProtosEqual(cur_doc, d)));
+ doc.$data = new List<Doc>(DocListCast(doc.$data).filter(d => !Doc.AreProtosEqual(cur_doc, d)));
}
} else {
tagDoc && Doc.RemoveDocFromList(tagDoc[DocData], 'docs', doc);
for (const collection of DocListCast(tagDoc?.collections)) {
- collection[DocData].data = new List<Doc>(DocListCast(collection[DocData].data).filter(d => !Doc.AreProtosEqual(doc, d)));
+ collection.$data = new List<Doc>(DocListCast(collection.$data).filter(d => !Doc.AreProtosEqual(doc, d)));
}
}
}
- doc[DocData].tags = new List<string>(StrListCast(doc[DocData].tags).filter(label => label !== tag));
+ doc.$tags = new List<string>(StrListCast(doc.$tags).filter(label => label !== tag));
};
private _ref: React.RefObject<HTMLDivElement>;
@@ -170,19 +172,19 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
const newEmbeddings = TagItem.allDocsWithTag(this._props.tag).map(doc => Doc.MakeEmbedding(doc));
// Create a new collection and set up configurations.
+ const emptyCol = DocCast(Doc.UserDoc().emptyCollection);
const newCollection = ((doc: Doc) => {
- const docData = doc[DocData];
- docData.data = new List<Doc>(newEmbeddings);
- docData.title = this._props.tag;
- docData.tags = new List<string>([this._props.tag]);
- docData.freeform_fitContentsToBox = true;
+ doc.$data = new List<Doc>(newEmbeddings);
+ doc.$title = this._props.tag;
+ doc.$tags = new List<string>([this._props.tag]);
+ doc.$freeform_fitContentsToBox = true;
doc._freeform_panX = doc._freeform_panY = 0;
doc._width = 900;
doc._height = 900;
doc.layout_fitWidth = true;
doc._layout_showTags = true;
return doc;
- })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+ })(emptyCol ? Doc.MakeCopy(emptyCol, true) : Docs.Create.FreeformDocument([], {}));
newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection));
// Add the collection to the tag document's list of associated smart collections.
@@ -255,6 +257,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
interface TagViewProps {
Views: DocumentView[];
+ background: string;
}
/**
@@ -266,11 +269,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();
@@ -296,7 +298,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));
}
/**
@@ -306,7 +310,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);
+ }
};
/**
@@ -337,7 +344,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
const tagsList = new Set<string>(StrListCast(this.View.dataDoc.tags));
const chatTagsList = new Set<string>(StrListCast(this.View.dataDoc.tags_chat));
const facesList = new Set<string>(
- DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations'])
+ DocListCast(this.View.dataDoc[Doc.LayoutDataKey(this.View.Document) + '_annotations'])
.concat(this.View.Document)
.filter(d => d.face)
.map(doc => StrCast(DocCast(doc.face)?.title))
@@ -347,10 +354,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,
}}>
@@ -358,18 +374,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/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 8879fc20d..049945517 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -40,12 +40,16 @@
height: 100%;
width: 100%;
max-height: 250px;
+ overflow: auto;
.templateToggle,
.chromeToggle {
text-align: left;
color: black;
}
+ .collectionTreeView {
+ height: fit-content !important;
+ }
input {
margin-right: 10px;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 680c8ed0e..1266a11c1 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -80,7 +80,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
const addedTypes = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
const templateMenu: Array<JSX.Element> = [];
templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
- // eslint-disable-next-line no-return-assign
addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
this._addedKeys &&
Array.from(this._addedKeys)
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 32b97b31a..067020a62 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -7,9 +7,8 @@ import { SettingsManager } from '../util/SettingsManager';
import { UndoManager } from '../util/UndoManager';
import './UndoStack.scss';
-interface UndoStackProps {}
@observer
-export class UndoStack extends React.Component<UndoStackProps> {
+export class UndoStack extends React.Component<object> {
render() {
const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
@@ -34,7 +33,6 @@ export class UndoStack extends React.Component<UndoStackProps> {
{Array.from(UndoManager.undoStackNames).map((name, i) => (
<div
className="undoStack-resultContainer"
- // eslint-disable-next-line react/no-array-index-key
key={i}
onClick={() => {
const size = UndoManager.undoStackNames.length;
@@ -48,7 +46,6 @@ export class UndoStack extends React.Component<UndoStackProps> {
.map((name, i) => (
<div
className="undoStack-resultContainer"
- // eslint-disable-next-line react/no-array-index-key
key={i}
onClick={() => {
for (let n = 0; n <= i; n++) UndoManager.Redo();
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 99163f6c6..40fcb2bf6 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -12,6 +12,18 @@ import './Region.scss';
import './Timeline.scss';
import { TimelineMenu } from './TimelineMenu';
+export const RegionDataSchema = createSchema({
+ position: defaultSpec('number', 0),
+ duration: defaultSpec('number', 0),
+ keyframes: listSpec(Doc),
+ fadeIn: defaultSpec('number', 0),
+ fadeOut: defaultSpec('number', 0),
+ functions: listSpec(Doc),
+ hasData: defaultSpec('boolean', false),
+});
+export type RegionData = makeInterface<[typeof RegionDataSchema]>;
+export const RegionData = makeInterface(RegionDataSchema);
+
/**
* Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
*/
@@ -108,18 +120,6 @@ export namespace RegionHelpers {
};
}
-export const RegionDataSchema = createSchema({
- position: defaultSpec('number', 0),
- duration: defaultSpec('number', 0),
- keyframes: listSpec(Doc),
- fadeIn: defaultSpec('number', 0),
- fadeOut: defaultSpec('number', 0),
- functions: listSpec(Doc),
- hasData: defaultSpec('boolean', false),
-});
-export type RegionData = makeInterface<[typeof RegionDataSchema]>;
-export const RegionData = makeInterface(RegionDataSchema);
-
interface IProps {
animatedDoc: Doc;
RegionData: Doc;
@@ -184,7 +184,7 @@ export class Region extends ObservableReactComponent<IProps> {
return RegionHelpers.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
}
- constructor(props: any) {
+ constructor(props: IProps) {
super(props);
makeObservable(this);
}
@@ -220,9 +220,7 @@ export class Region extends ObservableReactComponent<IProps> {
}, 200);
this._doubleClickEnabled = true;
document.addEventListener('pointermove', this.onBarPointerMove);
- document.addEventListener('pointerup', (e: PointerEvent) => {
- document.removeEventListener('pointermove', this.onBarPointerMove);
- });
+ document.addEventListener('pointerup', () => document.removeEventListener('pointermove', this.onBarPointerMove));
}
};
@@ -426,6 +424,7 @@ export class Region extends ObservableReactComponent<IProps> {
.map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) }))
.forEach(({ pos, dur }) => {
if (pos !== this.regiondata.position) {
+ // eslint-disable-next-line no-param-reassign
val += this.regiondata.position;
if (val < 0 || (val > pos && val < pos + dur)) {
cannotMove = true;
@@ -483,7 +482,6 @@ export class Region extends ObservableReactComponent<IProps> {
* this probably needs biggest change, since everyone expected all keyframes to have a circle (and draggable)
*/
drawKeyframes = () => {
- const keyframeDivs: JSX.Element[] = [];
return DocListCast(this.regiondata.keyframes).map(kf => {
return (
<>
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 15683ebf2..814e9a7a0 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -46,7 +46,7 @@ import { Id } from '../../../fields/FieldSymbols';
*/
@observer
-export class Timeline extends ObservableReactComponent<FieldViewProps> {
+export class Timeline extends ObservableReactComponent<FieldViewProps & { Doc: Doc }> {
// readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
@@ -57,7 +57,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
private DEFAULT_CONTAINER_HEIGHT: number = 330;
private MIN_CONTAINER_HEIGHT: number = 205;
- constructor(props: FieldViewProps) {
+ constructor(props: FieldViewProps & { Doc: Doc }) {
super(props);
makeObservable(this);
}
@@ -90,11 +90,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
*/
@computed
private get children(): Doc[] {
- const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as unknown as DocumentType);
+ const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Doc.type) as unknown as DocumentType);
if (annotatedDoc) {
- return DocListCast(this._props.Document[Doc.LayoutFieldKey(this._props.Document) + '_annotations']);
+ return DocListCast(this._props.Doc[Doc.LayoutDataKey(this._props.Doc) + '_annotations']);
}
- return DocListCast(this._props.Document[this._props.fieldKey]);
+ return DocListCast(this._props.Doc[this._props.fieldKey]);
}
/// //////lifecycle functions////////////
@@ -104,21 +104,21 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
- if (!this._props.Document.AnimationLength) {
+ if (!this._props.Doc.AnimationLength) {
// if animation length did not exist
- this._props.Document.AnimationLength = this._time; // set it to default time
+ this._props.Doc.AnimationLength = this._time; // set it to default time
} else {
- this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
+ this._time = NumCast(this._props.Doc.AnimationLength); // else, set time to animationlength stored from before
}
this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
- this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
+ this._props.Doc.isATOn = !this._props.Doc.isATOn; // turns the boolean on, saying AT (animation timeline) is on
this.toggleHandle();
}
componentWillUnmount() {
- this._props.Document.AnimationLength = this._time; // save animation length
+ this._props.Doc.AnimationLength = this._time; // save animation length
}
/// //////////////////////////////////////////////
@@ -224,7 +224,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
*/
@action
onPanDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, e => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
+ setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, movEv => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + movEv.clientX - this._trackbox.current!.getBoundingClientRect().left));
};
/**
@@ -241,7 +241,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
this._visibleStart -= e.movementX;
this._totalLength -= e.movementX;
this._time -= RegionHelpers.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
- this._props.Document.AnimationLength = this._time;
+ this._props.Doc.AnimationLength = this._time;
}
return false;
};
@@ -259,8 +259,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
setupMoveUpEvents(
this,
e,
- action(e => {
- const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
+ action(movEv => {
+ const offset = movEv.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
return false;
}),
@@ -358,7 +358,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
const size = 40 * scale; // 50 is default
const iconSize = 25;
const width: number = this._props.PanelWidth();
- const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
+ const modeType = this._props.Doc.isATOn ? 'Author' : 'Play';
// decides if information should be omitted because the timeline is very small
// if its less than 950 pixels then it's going to be overlapping
@@ -397,7 +397,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
tickIncrement={this._tickIncrement}
time={this._time}
parent={this}
- isAuthoring={BoolCast(this._props.Document.isATOn)}
+ isAuthoring={BoolCast(this._props.Doc.isATOn)}
currentBarX={this._currentBarX}
totalLength={this._totalLength}
visibleLength={this._visibleLength}
@@ -418,10 +418,10 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
</div>
<div className="time-box overview-tool" style={{ display: 'flex' }}>
{this.timeIndicator(lengthString, totalTime)}
- <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this._props.Document)}>
+ <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this._props.Doc)}>
<FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
</div>
- <div className="resetView-tool" style={{ display: this._props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this._props.Document)}>
+ <div className="resetView-tool" style={{ display: this._props.Doc.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this._props.Doc)}>
<FontAwesomeIcon icon="expand-arrows-alt" size="lg" />
</div>
</div>
@@ -431,17 +431,17 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
};
timeIndicator(lengthString: string, totalTime: number) {
- if (this._props.Document.isATOn) {
- return <div key="time-text" className="animation-text" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', display: this._props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
+ if (this._props.Doc.isATOn) {
+ return <div key="time-text" className="animation-text" style={{ visibility: this._props.Doc.isATOn ? 'visible' : 'hidden', display: this._props.Doc.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
} else {
const ctime = `Current: ${this.getCurrentTime()}`;
const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
<div style={{ flexDirection: 'column' }}>
- <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Doc.isATOn ? 'block' : 'none' }}>
{ctime}
</div>
- <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Doc.isATOn ? 'block' : 'none' }}>
{ttime}
</div>
</div>
@@ -467,8 +467,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
const roundToggleContainer = this._roundToggleContainerRef.current!;
const timelineContainer = this._timelineContainer.current!;
- this._props.Document.isATOn = !this._props.Document.isATOn;
- if (!BoolCast(this._props.Document.isATOn)) {
+ this._props.Doc.isATOn = !this._props.Doc.isATOn;
+ if (!BoolCast(this._props.Doc.isATOn)) {
// turning on playmode...
roundToggle.style.transform = 'translate(0px, 0px)';
roundToggle.style.animationName = 'turnoff';
@@ -543,7 +543,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
// change visible and total width
return (
<div style={{ visibility: 'visible' }}>
- <div key="timeline_wrapper" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
+ <div key="timeline_wrapper" style={{ visibility: this._props.Doc.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
<div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
<div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
{this.drawTicks()}
@@ -551,7 +551,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
<div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} />
</div>
<div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
- {[...this.children, this._props.Document].map(doc => (
+ {[...this.children, this._props.Doc].map(doc => (
<Track
key={doc[Id]}
ref={ref => this.mapOfTracks.push(ref)}
@@ -563,7 +563,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
time={this._time}
tickSpacing={this._tickSpacing}
tickIncrement={this._tickIncrement}
- collection={this._props.Document}
+ collection={this._props.Doc}
timelineVisible={true}
/>
))}
@@ -571,7 +571,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
</div>
<div className="currentTime">Current: {this.getCurrentTime()}</div>
<div key="timeline_title" className="title-container" ref={this._titleContainer}>
- {[...this.children, this._props.Document].map(doc => (
+ {[...this.children, this._props.Doc].map(doc => (
<div key={doc[Id]} style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
<p>{StrCast(doc.title)}</p>
</div>
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 1e4ed74be..dea7b6aae 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,4 +1,4 @@
-import { action, computed, intercept, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { action, computed, intercept, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
@@ -6,7 +6,7 @@ import { Copy } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../fields/Types';
import { Transform } from '../../util/Transform';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { Region, RegionData, RegionHelpers } from './Region';
@@ -29,16 +29,16 @@ interface IProps {
@observer
export class Track extends ObservableReactComponent<IProps> {
@observable private _inner = React.createRef<HTMLDivElement>();
- @observable private _currentBarXReaction: any = undefined;
- @observable private _timelineVisibleReaction: any = undefined;
- @observable private _autoKfReaction: any = undefined;
+ @observable private _currentBarXReaction: IReactionDisposer | undefined = undefined;
+ @observable private _timelineVisibleReaction: IReactionDisposer | undefined = undefined;
+ @observable private _autoKfReaction: IReactionDisposer | undefined = undefined;
@observable private _newKeyframe: boolean = false;
private readonly MAX_TITLE_HEIGHT = 75;
@observable private _trackHeight = 0;
private primitiveWhitelist = ['x', 'y', '_freeform_panX', '_freeform_panY', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
private objectWhitelist = ['data'];
- constructor(props: any) {
+ constructor(props: IProps) {
super(props);
makeObservable(this);
}
@@ -101,11 +101,11 @@ export class Track extends ObservableReactComponent<IProps> {
}
const keyframes = Cast(this.saveStateRegion.keyframes, listSpec(Doc)) as List<Doc>;
const kfIndex = keyframes.indexOf(this.saveStateKf);
- const kf = keyframes[kfIndex] as Doc; //index in the keyframe
+ const kf = DocCast(keyframes[kfIndex]); //index in the keyframe
if (this._newKeyframe) {
- DocListCast(this.saveStateRegion.keyframes).forEach((kf, index) => {
- this.copyDocDataToKeyFrame(kf);
- kf.opacity = index === 0 || index === 3 ? 0.1 : 1;
+ DocListCast(this.saveStateRegion.keyframes).forEach((keyF, index) => {
+ this.copyDocDataToKeyFrame(keyF);
+ keyF.opacity = index === 0 || index === 3 ? 0.1 : 1;
});
this._newKeyframe = false;
}
@@ -144,7 +144,7 @@ export class Track extends ObservableReactComponent<IProps> {
() => {
return [...this.primitiveWhitelist.map(key => this._props.animatedDoc[key]), ...objects];
},
- (changed, reaction) => {
+ (/* changed, reaction */) => {
//check for region
const region = this.findRegion(this.time);
if (region !== undefined) {
@@ -374,7 +374,7 @@ export class Track extends ObservableReactComponent<IProps> {
@action
copyDocDataToKeyFrame = (doc: Doc) => {
- var somethingChanged = false;
+ let somethingChanged = false;
this.primitiveWhitelist.map(key => {
const originalVal = this._props.animatedDoc[key];
somethingChanged = somethingChanged || originalVal !== doc[key];
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 756b37f99..d5edc3e0b 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';
@@ -39,7 +40,6 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
export class CollectionCardView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [key: string]: IReactionDisposer } = {};
- private _oldWheel: HTMLElement | null = null;
private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center)
private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!;
private _draggerRef = React.createRef<HTMLDivElement>();
@@ -55,13 +55,7 @@ export class CollectionCardView extends CollectionSubView() {
}
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
this._dropDisposer?.();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
- }
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(ele, this._props.isContentActive);
};
@computed get cardWidth() {
return NumCast(this.layoutDoc._cardWidth, 50);
@@ -486,7 +480,6 @@ export class CollectionCardView extends CollectionSubView() {
);
});
}
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
docViewProps = (): DocumentViewProps => ({
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 9c8ef5519..755e31925 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -25,7 +25,6 @@ const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = requi
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
- private _oldWheel: HTMLElement | null = null;
constructor(props: SubCollectionViewProps) {
super(props);
@@ -44,10 +43,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(ele, this._props.isContentActive);
};
@computed get scrollSpeed() {
@@ -101,7 +97,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
const docs = DocListCast(this.Document[this.fieldKey]);
if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) {
- const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor));
+ const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)!);
options.didMove = newIndex !== this.layoutDoc._carousel_index;
options.didMove && (this.layoutDoc._carousel_index = newIndex);
}
@@ -221,7 +217,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
return this.panelWidth() * (1 - index);
}
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout;
answered = (correct: boolean) => (!correct || !this.curDoc() || NumCast(this.layoutDoc._carousel_index) === this.carouselItems.length - 1) && this.changeSlide(1);
docViewProps = () => ({
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..ac1981012 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -21,7 +21,6 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
export class CollectionCarouselView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
- _oldWheel: HTMLElement | null = null;
_fadeTimer: NodeJS.Timeout | undefined;
@observable _last_index = this.carouselIndex;
@observable _last_opacity = 1;
@@ -43,10 +42,7 @@ export class CollectionCarouselView extends CollectionSubView() {
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(ele, this._props.isContentActive);
};
@computed get captionMarginX(){ return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
@@ -77,7 +73,8 @@ export class CollectionCarouselView extends CollectionSubView() {
focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
const docs = DocListCast(this.Document[this.fieldKey]);
if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) {
- const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor));
+ const annoOn = DocCast(anchor.annotationOn, anchor);
+ const newIndex = NumCast(anchor.config_carousel_index, (annoOn && docs.getIndex(annoOn)) ?? 0);
options.didMove = newIndex !== this.layoutDoc._carousel_index;
options.didMove && (this.layoutDoc._carousel_index = newIndex);
}
@@ -114,13 +111,14 @@ export class CollectionCarouselView extends CollectionSubView() {
: this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
? 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 +134,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 +179,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 +247,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/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e51bc18ef..13c3eb72f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom/client';
import ResizeObserver from 'resize-observer-polyfill';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
-import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -60,7 +60,7 @@ export class CollectionDockingView extends CollectionSubView() {
public get HasFullScreen() {
return this._goldenLayout._maximisedItem !== null;
}
- static _highlightStyleSheet = addStyleSheet();
+ static _highlightStyleSheet = addStyleSheet().sheet;
constructor(props: SubCollectionViewProps) {
super(props);
@@ -443,7 +443,7 @@ export class CollectionDockingView extends CollectionSubView() {
window.addEventListener('mouseup', this.onPointerUp);
if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) {
const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
- if (className.includes('lm_maximise')) {
+ if (className.includes('lm_maximise') || className.includes('lm_close_tab')) {
// this._flush = UndoManager.StartBatch('tab maximize');
} else {
const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement);
@@ -473,15 +473,15 @@ export class CollectionDockingView extends CollectionSubView() {
}
return undefined;
}
- public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
+ public static TakeSnapshot(doc: Doc | undefined, clone = false) {
if (!doc) return undefined;
let json = StrCast(doc.dockingConfig);
if (clone) {
- const cloned = await Doc.MakeClone(doc);
+ const cloned = Doc.MakeClone(doc);
Array.from(cloned.map.entries()).forEach(entry => {
json = json.replace(entry[0], entry[1][Id]);
});
- cloned.clone[DocData].dockingConfig = json;
+ cloned.clone.$dockingConfig = json;
return DashboardView.openDashboard(cloned.clone);
}
const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
@@ -495,7 +495,7 @@ export class CollectionDockingView extends CollectionSubView() {
const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab);
const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
if (newtabdocs.length) {
- newtab[DocData].data = new List<Doc>(newtabdocs);
+ newtab.$data = new List<Doc>(newtabdocs);
newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
}
json = json.replace(origtab[Id], newtab[Id]);
@@ -503,9 +503,8 @@ export class CollectionDockingView extends CollectionSubView() {
});
const dashboardDoc = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
- dashboardDoc.pane_count = 1;
- dashboardDoc.myOverlayDocs = new List<Doc>();
- dashboardDoc.myPublishedDocs = new List<Doc>();
+ dashboardDoc.$myOverlayDocs = new List<Doc>();
+ dashboardDoc.$myPublishedDocs = new List<Doc>();
DashboardView.SetupDashboardTrails();
DashboardView.SetupDashboardCalendars(); // Zaul TODO: needed?
@@ -525,11 +524,11 @@ export class CollectionDockingView extends CollectionSubView() {
this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
const dashDoc = tab.DashDoc;
if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) {
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
+ Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
// if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined;
if (!dashDoc.embedContainer) {
- Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
+ Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
Doc.RemoveEmbedding(dashDoc, dashDoc);
}
}
@@ -555,15 +554,15 @@ export class CollectionDockingView extends CollectionSubView() {
stack.header?.element.on('mousedown', (e: MouseEvent) => {
const dashboard = Doc.ActiveDashboard;
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
- dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
+ dashboard.$myPaneCount = NumCast(dashboard.$myPaneCount) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this._props.PanelWidth(),
_height: this._props.PanelHeight(),
_freeform_backgroundGrid: true,
_layout_fitWidth: true,
- title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
+ title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`,
});
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+ Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.Document, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
@@ -572,15 +571,15 @@ export class CollectionDockingView extends CollectionSubView() {
const addNewDoc = undoable(() => {
const dashboard = Doc.ActiveDashboard;
if (dashboard) {
- dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
+ dashboard.$myPaneCount = NumCast(dashboard.$myPaneCount) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
_width: this._props.PanelWidth(),
_height: this._props.PanelHeight(),
_layout_fitWidth: true,
_freeform_backgroundGrid: true,
- title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
+ title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`,
});
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+ Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.dataDoc, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
@@ -669,7 +668,5 @@ ScriptingGlobals.add(
);
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(async function snapshotDashboard() {
- const batch = UndoManager.StartBatch('snapshot');
- await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
- batch.end();
+ undoable(() => CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard), 'snapshot dashboard');
}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 996626575..89ccf5a0f 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -5,7 +5,6 @@ import * as React from 'react';
import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, numberRange } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { Docs } from '../../documents/Documents';
@@ -23,7 +22,7 @@ import './CollectionStackingView.scss';
interface CMVFieldRowProps {
rows: () => number;
headings: () => object[];
- Document: Doc;
+ Doc: Doc;
chromeHidden?: boolean;
heading: string;
headingObject: SchemaHeaderField | undefined;
@@ -74,7 +73,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer?.();
- if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
+ if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Doc);
else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
this._ele = ele;
};
@@ -164,7 +163,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const { pivotField } = this._props;
const newDoc = Docs.Create.TextDocument(value, { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
DocumentView.SetSelectOnLoad(newDoc);
- pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
+ pivotField && (newDoc['$' + pivotField] = this.getValue(this._props.heading));
const docs = this._props.parent.childDocList;
return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
};
@@ -190,7 +189,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
};
headerMove = (e: PointerEvent) => {
- const embedding = Doc.MakeEmbedding(this._props.Document);
+ const embedding = Doc.MakeEmbedding(this._props.Doc);
const key = this._props.pivotField;
let value = this.getValue(this.heading);
value = typeof value === 'string' ? `"${value}"` : value;
@@ -290,7 +289,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const key = this._props.pivotField;
const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
- return this._props.Document.miniHeaders ? (
+ return this._props.Doc.miniHeaders ? (
<div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
) : !this._props.headingObject ? null : (
<div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index de999c91a..7f835938b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable react/no-unused-class-component-methods */
-/* eslint-disable react/sort-comp */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from '@dash/components';
@@ -9,7 +7,6 @@ import * as React from 'react';
import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
@@ -26,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;
@@ -48,7 +46,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
makeObservable(this);
CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
- this.Pinned = Cast(Doc.UserDoc().menuCollections_pinned, 'boolean', true);
+ this.Pinned = BoolCast(Doc.UserDoc().menuCollections_pinned, true);
this.jumpTo(300, 300);
}
@@ -121,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}
@@ -130,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}
@@ -141,26 +139,22 @@ 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;
}
}
interface CollectionViewMenuProps {
- // eslint-disable-next-line react/no-unused-prop-types
type: CollectionViewType;
- // eslint-disable-next-line react/no-unused-prop-types
fieldKey: string;
docView: DocumentView;
}
@@ -185,7 +179,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text;
try {
formatStr && JSON.parse(formatStr);
- } catch (e) {
+ } catch {
formatStr = '';
}
if (source.length === 1 && formatStr) {
@@ -213,7 +207,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
title: 'set content',
script: 'getProto(this.target).data = copyField(this.source);',
immediate: undoable((source: Doc[]) => {
- this.target[DocData].data = new List<Doc>(source);
+ this.target.$data = new List<Doc>(source);
}, ''),
initialize: emptyFunction,
};
@@ -275,7 +269,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
initialize: (button: Doc) => {
const activeDash = Doc.ActiveDashboard;
if (activeDash) {
- button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as ObjectField) : undefined;
+ button.target_childFilters = (Doc.MySearcher?._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher?._childFilters || activeDash._childFilters) as ObjectField) : undefined;
button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs) : undefined;
}
},
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index c499bd288..7f639a11e 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -32,6 +32,7 @@ import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn
import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { Property } from 'csstype';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
/**
* CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1)
@@ -257,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}
@@ -270,7 +269,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
containerViewPath={this.childContainerViewPath}
fitWidth={this._props.childLayoutFitWidth}
isContentActive={emptyFunction}
- onKey={this.onKeyDown}
+ onKey={this.onKey}
// TODO: change this from a prop to a parameter passed into a function
dontHideOnDrag
isDocumentActive={this.isContentActive}
@@ -322,11 +321,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
};
- // how to get the height of a document. Nothing special here.
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
- const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+ const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
+ const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
@@ -437,11 +435,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
};
@undoBatch
- onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
- if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
+ onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => {
+ if ((e.ctrlKey || textBox.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
e.stopPropagation?.();
- const newDoc = Doc.MakeCopy(fieldProps.Document, true);
- newDoc[DocData].text = undefined;
+ const newDoc = Doc.MakeCopy(textBox.Document, true);
+ newDoc.$text = undefined;
DocumentView.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
@@ -543,8 +541,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
- Document={this.Document}
- TemplateDataDocument={this._props.TemplateDataDocument}
+ Doc={this.Document}
+ TemplateDataDoc={this._props.TemplateDataDocument}
resizeColumns={this.resizeColumns}
renderChildren={this.children}
numGroupColumns={this.numGroupColumns}
@@ -567,7 +565,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@undoBatch
remColumn = (value: SchemaHeaderField) => {
- const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+ const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
if (value) {
const index = colHdrData.indexOf(value);
index !== -1 && colHdrData.splice(index, 1);
@@ -586,7 +584,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
return undefined;
});
- const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+ const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
const newColWidth = 1 / (this.numGroupColumns + 1);
columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth));
value && this.resizeColumns(columnHeaders);
@@ -643,7 +641,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth);
const headers = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
- headers.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]());
+ headers?.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]());
};
// renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view
@@ -701,7 +699,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
{this.renderedSections}
<div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
<FieldsDropdown
- Document={this.Document}
+ Doc={this.Document}
selectFunc={undoable(fieldKey => {
this.layoutDoc._pivotField = fieldKey;
this.removeEmptyColumns();
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 40b3f9ef2..461689a70 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -21,8 +21,8 @@ import './CollectionNoteTakingView.scss';
import { DocumentView } from '../nodes/DocumentView';
interface CSVFieldColumnProps {
- Document: Doc;
- TemplateDataDocument: Opt<Doc>;
+ Doc: Doc;
+ TemplateDataDoc: Opt<Doc>;
backgroundColor?: () => string | undefined;
docList: Doc[];
heading: string;
@@ -65,7 +65,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
// columnWidth returns the width of a column in absolute pixels
@computed get columnWidth() {
- if (this._props.Document._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1);
+ if (this._props.Doc._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1);
if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`;
const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color);
return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%';
@@ -81,7 +81,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc);
else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1);
this._ele = ele;
};
@@ -155,9 +155,9 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
// all docs are added to the column directly to the left.
@undoBatch
deleteColumn = () => {
- const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+ const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), [])!);
if (this._props.headingObject) {
- // this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined));
+ // this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined));
colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
this._props.resizeColumns(colHdrData);
}
@@ -184,11 +184,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
);
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
- Doc.GetProto(this._props.Document)[name] = '';
+ Doc.GetProto(this._props.Doc)[name] = '';
const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
- if (this._props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+ if (this._props.Doc.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
}
this._props.addDocument?.(created);
}
@@ -267,7 +267,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
style={{
width: this.columnWidth,
background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit',
- marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0,
+ marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Doc.xMargin) : 0,
}}>
<div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}>
{this.innards}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index eea128803..5a85c8ee3 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -67,7 +67,6 @@ export class CollectionPileView extends CollectionSubView() {
return (
<div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
<CollectionFreeFormView
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
layoutEngine={this.layoutEngine}
addDocument={this.addPileDoc}
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index 2600c0f57..4736070c3 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -103,7 +103,7 @@ export class CollectionPivotView extends CollectionSubView() {
{this.contents}
<div style={{ right: 0, top: 0, position: 'absolute' }}>
<FieldsDropdown
- Document={this.Document}
+ Doc={this.Document}
selectFunc={fieldKey => {
this.layoutDoc._pivotField = fieldKey;
}}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index c3047e5fb..4e7e19548 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -5,7 +5,6 @@ import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils';
import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -187,13 +186,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@computed get rangeClick() {
// prettier-ignore
return ScriptField.MakeFunction('stackedTimeline.clickAnchor(this, clientX)',
- { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */ }
- )!;
+ { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable
}
@computed get rangePlay() {
// prettier-ignore
return ScriptField.MakeFunction('stackedTimeline.playOnClick(this, clientX)',
- { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */})!;
+ { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable
}
rangeClickScript = () => this.rangeClick;
rangePlayScript = () => this.rangePlay;
@@ -268,13 +266,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action(() => {
+ action(movEv => {
if (!wasSelecting) {
this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
wasSelecting = true;
this._timelineWrapper && (this._timelineWrapper.style.cursor = 'ew-resize');
}
- this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+ this._markerEnd = this.toTimeline(movEv.clientX - rect.x, rect.width);
return false;
}),
action((upEvent, movement, isClick) => {
@@ -433,11 +431,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
_isTimelineLabel: true,
layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
});
- anchor[DocData][startTag] = anchorStartTime;
- anchor[DocData][endTag] = anchorEndTime;
+ anchor['$' + startTag] = anchorStartTime;
+ anchor['$' + endTag] = anchorEndTime;
if (addAsAnnotation) {
if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
- Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
+ Cast(dataDoc[fieldKey], listSpec(Doc), [])!.push(anchor);
} else {
dataDoc[fieldKey] = new List<Doc>([anchor]);
}
@@ -805,9 +803,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
};
resetTitle = () => {
- this._props.mark[DocData].title = ComputedField.MakeFunction(
- `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
- );
+ this._props.mark.$title = ComputedField.MakeFunction(`["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`);
};
// context menu
contextMenuItems = () => {
@@ -844,7 +840,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
styleProvider={this._props.styleProvider}
renderDepth={this._props.renderDepth + 1}
LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutString('data')}
+ LayoutTemplateString={LabelBox.LayoutString('title')}
isDocumentActive={this._props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6bbd43b1b..be570564b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -5,7 +5,6 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, DivHeight, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -35,6 +34,7 @@ import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { computedFn } from 'mobx-utils';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
export type collectionStackingViewProps = {
sortFunc?: (a: Doc, b: Doc) => number;
@@ -55,7 +55,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
_docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = [];
// Doesn't look like this field is being used anywhere. Obsolete?
_columnStart: number = 0;
- _oldWheel: HTMLElement | null = null;
@observable _refList: HTMLElement[] = [];
// map of node headers to their heights. Used in Masonry
@@ -112,7 +111,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() {
@@ -310,17 +309,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this._props.styleProvider?.(doc, props, property);
};
@undoBatch
- onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => {
if (['Enter'].includes(e.key) && e.ctrlKey) {
e.stopPropagation?.();
- const layoutFieldKey = StrCast(fieldProps.fieldKey);
- const newDoc = Doc.MakeCopy(fieldProps.Document, true);
- const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
- newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
- newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
+ const layoutFieldKey = StrCast(textBox.fieldKey);
+ const newDoc = Doc.MakeCopy(textBox.Document, true);
+ const dataField = textBox.Document[Doc.LayoutDataKey(newDoc)];
+ newDoc['$' + Doc.LayoutDataKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+ if (layoutFieldKey !== 'layout' && textBox.Document[layoutFieldKey] instanceof Doc) {
+ newDoc[layoutFieldKey] = textBox.Document[layoutFieldKey];
}
- newDoc[DocData].text = undefined;
+ newDoc.$text = undefined;
DocumentView.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
@@ -340,10 +339,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
@observable docRefs = new ObservableMap<Doc, DocumentView>();
- childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
+ childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null);
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
- getDisplayDoc(doc: Doc, trans: () => string, count: number) {
+ getDisplayDoc = (doc: Doc, trans: () => string, count: number) => {
const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) });
return count > this._renderCount ? null : (
@@ -359,7 +358,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
containerViewPath={this.childContainerViewPath}
fitWidth={this.childFitWidth}
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
- onKey={this.onKeyDown}
+ onKey={this.onKey}
DataTransition={trans}
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
@@ -384,6 +383,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
searchFilterDocs={this.searchFilterDocs}
xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+ rejectDrop={this._props.childRejectDrop}
addDocument={this._props.addDocument}
moveDocument={this._props.moveDocument}
removeDocument={this._props.removeDocument}
@@ -393,7 +393,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
pinToPres={this._props.pinToPres}
/>
);
- }
+ };
getDocTransform = computedFn((doc: Doc) => () => {
// these must be referenced for document decorations to update when the text box container is scrolled
@@ -404,11 +404,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
return new Transform(-translateX + (dref?.centeringX || 0) * scale,
-translateY + (dref?.centeringY || 0) * scale, 1)
- .scale(1 / scale); // prettier-ignore
+ .scale(1 / (scale||1)); // prettier-ignore
});
getDocWidth = computedFn((d?: Doc) => () => {
if (!d) return 0;
- const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
+ const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
const maxWidth = this.columnWidth / this.numGroupColumns;
if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
return Math.min(NumCast(d._width), maxWidth);
@@ -418,8 +418,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
getDocTransition = computedFn((d?: Doc) => () => StrCast(d?.dataTransition));
getDocHeight = computedFn((d?: Doc) => () => {
if (!d || d.hidden) return 0;
- const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+ const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
+ const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
@@ -575,8 +575,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
- Document={this.Document}
- TemplateDataDocument={this._props.TemplateDataDocument}
+ Doc={this.Document}
+ TemplateDataDoc={this._props.TemplateDataDocument}
renderChildren={this.children}
columnWidth={this.columnWidth}
numGroupColumns={this.numGroupColumns}
@@ -611,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
<div style={{ top: this.yMargin }}>
<CollectionMasonryViewFieldRow
showHandle={first}
- Document={this.Document}
+ Doc={this.Document}
chromeHidden={this.chromeHidden}
pivotField={this.pivotField}
refList={this._refList}
@@ -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}
@@ -720,7 +720,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this._props.isContentActive() === false ? 'none' : undefined;
}
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
render() {
TraceMobx();
const editableViewProps = {
@@ -728,13 +727,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}
@@ -744,10 +742,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
ref={ele => {
this._masonryGridRef = ele;
this.createDashEventsTarget(ele); // so the whole grid is the drop target?
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(ele, this._props.isContentActive);
}}
style={{
overflowY: this.isContentActive() ? 'auto' : 'hidden',
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 6f32dd2e0..994669734 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,11 +26,12 @@ 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 {
- Document: Doc;
- TemplateDataDocument: Opt<Doc>;
+ Doc: Doc;
+ TemplateDataDoc: Opt<Doc>;
docList: Doc[];
heading: string;
pivotField: string;
@@ -90,7 +91,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
// is that the only way to have drop targets?
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document, this.onInternalPreDrop.bind(this));
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this));
else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
this._ele = ele;
};
@@ -183,7 +184,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
// TODO: I think this is where I'm supposed to edit stuff
startDrag = (e: PointerEvent) => {
// is MakeEmbedding a way to make a copy of a doc without rendering it?
- const embedding = Doc.MakeEmbedding(this._props.Document);
+ const embedding = Doc.MakeEmbedding(this._props.Doc);
embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
embedding._pivotField = undefined;
let value = this.getValue(this._heading);
@@ -230,7 +231,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
- const dataDoc = this._props.TemplateDataDocument || this._props.Document;
+ const dataDoc = this._props.TemplateDataDoc || this._props.Doc;
const width = this._ele ? DivWidth(this._ele) : 0;
const height = this._ele ? DivHeight(this._ele) : 0;
DocUtils.addDocumentCreatorMenuItems(
@@ -250,10 +251,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
docItems.push({
description: ':' + fieldKey,
event: () => {
- const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+ const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Doc));
if (created) {
- if (this._props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+ if (this._props.Doc.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
}
return this._props.addDocument?.(created);
}
@@ -270,10 +271,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
event: () => {
const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
if (created) {
- const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
+ 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);
+ return Doc.AddDocToList(container, Doc.LayoutDataKey(container), created);
}
return this._props.addDocument?.(created) || false;
}
@@ -285,11 +286,11 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
- Doc.GetProto(this._props.Document)[name] = '';
+ Doc.GetProto(this._props.Doc)[name] = '';
const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
- if (this._props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+ if (this._props.Doc.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
}
this._props.addDocument?.(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. */}
@@ -350,17 +349,17 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
</div>
) : null;
const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
- const { type } = this._props.Document;
+ const { type } = this._props.Doc;
return (
<>
- {this._props.Document._columnsHideIfEmpty ? null : headingView}
+ {this._props.Doc._columnsHideIfEmpty ? null : headingView}
{this.collapsed ? null : (
<div
style={{
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 655894e40..e79d0a76d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import * as rp from 'request-promise';
import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils';
import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, expandedFieldName, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -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',
@@ -57,6 +58,7 @@ export interface CollectionViewProps extends React.PropsWithChildren<FieldViewPr
childOpacity?: () => number;
childContextMenuItems?: () => { script: ScriptField; label: string }[];
childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+ childRejectDrop?: (de: DragManager.DropEvent, subView?: DocumentView) => boolean; // whether a child document can be dropped on this document
childHideDecorationTitle?: boolean;
childHideResizeHandles?: boolean;
childHideDecorations?: boolean;
@@ -108,7 +110,11 @@ export function CollectionSubView<X>() {
}
get dataDoc() {
- return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.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() {
@@ -127,16 +133,23 @@ export function CollectionSubView<X>() {
hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
@computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
- const { Document, TemplateDataDocument } = this._props;
- const validPairs = this.childDocs
- .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
- .filter(
- pair =>
- // filter out any documents that have a proto that we don't have permissions to
- !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))
- )
- .filter(pair => !this._filterFunc?.(pair.layout!));
- return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
+ const lastEle = this.DocumentView?.();
+ if (!lastEle?.IsInvalid(this.Document)) {
+ const rootTemplate = lastEle && Doc.LayoutDoc(lastEle.rootDoc).isTemplateDoc && Doc.LayoutDoc(lastEle.rootDoc);
+ const templateFieldKey = rootTemplate &&
+ [expandedFieldName(rootTemplate),
+ ...this._props.docViewPath()
+ .filter(dv => dv.Document.isTemplateForField)
+ .map(dv => dv.Document.title),
+ ].join('_'); // prettier-ignore
+ return this.childDocs
+ .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? this._props.TemplateDataDocument : undefined, doc, templateFieldKey || ""))
+ .filter(pair => // filter out any documents that have a proto that we don't have permissions to
+ !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)))
+ .filter(pair => !this._filterFunc?.(pair.layout!))
+ .map(({ data, layout }) => ({ data: data!, layout: layout! })); // prettier-ignore
+ }
+ return [];
}
/**
* This is the raw, stored list of children on a collection. If you modify this list, the database will be updated
@@ -156,7 +169,7 @@ export function CollectionSubView<X>() {
};
collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
- collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
+ collectionRangeDocFilters = () => this._focusRangeFilters ?? StrListCast(this.Document._childFiltersByRanges);
// child filters apply to the descendants of the documents in this collection
childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
// unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack
@@ -173,7 +186,7 @@ export function CollectionSubView<X>() {
rawdocs = [this.dataField];
} else if (Cast(this.dataField, listSpec(Doc), null)) {
// otherwise, if the collection data is a list, then use it.
- rawdocs = Cast(this.dataField, listSpec(Doc), null);
+ rawdocs = DocListCast(this.dataField);
} else if (this.dataField) {
// Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
// For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
@@ -201,7 +214,7 @@ export function CollectionSubView<X>() {
let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
if (notFiltered) {
notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
- const fieldKey = Doc.LayoutFieldKey(d);
+ const fieldKey = Doc.LayoutDataKey(d);
const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
@@ -214,7 +227,7 @@ export function CollectionSubView<X>() {
newarray = [];
// eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
- const docFieldKey = Doc.LayoutFieldKey(t);
+ const docFieldKey = Doc.LayoutDataKey(t);
const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
notFiltered =
notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
@@ -295,7 +308,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._props.Document);
+ 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
@@ -311,7 +324,7 @@ export function CollectionSubView<X>() {
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const { docDragData } = de.complete;
- if (docDragData && !docDragData.draggedDocuments.includes(this.Document)) {
+ if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(de, this.DocumentView?.())) {
let added;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
@@ -585,7 +598,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
@@ -598,7 +611,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/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 98bd06221..fd562e64f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -121,7 +121,7 @@ export class CollectionTimeView extends CollectionSubView() {
{this.contents}
<div style={{ right: 0, top: 0, position: 'absolute' }}>
<FieldsDropdown
- Document={this.Document}
+ Doc={this.Document}
selectFunc={fieldKey => {
this.layoutDoc._pivotField = fieldKey;
}}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e93724dd4..bee5d016d 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';
@@ -137,7 +137,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if (res && de.complete.docDragData) {
if (this.Document !== Doc.MyRecentlyClosed)
de.complete.docDragData.droppedDocuments.forEach(doc => {
- if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+ if (this.Document !== Doc.MyRecentlyClosed) Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
});
}
return res;
@@ -187,20 +187,20 @@ 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 &&
doclist.forEach(doc => {
Doc.SetContainer(doc, this.Document);
- if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+ if (this.Document !== Doc.MyRecentlyClosed) Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
});
return res;
};
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({
@@ -241,7 +241,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
- onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => {
+ onKey = (e: KeyboardEvent /* , textBox: FormattedTextBox */) => {
if (this.outlineMode && e.key === 'Enter') {
e.stopPropagation();
this.makeTextCollection(this.treeChildren);
@@ -252,7 +252,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
get documentTitle() {
return (
<FormattedTextBox
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey="text"
renderDepth={this._props.renderDepth + 1}
@@ -274,8 +273,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
childContextMenuItems = () => {
- const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!;
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!;
const icons = StrListCast(this.Document.childContextMenuIcons);
return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
@@ -348,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}
@@ -468,10 +467,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const scale = this._props.NativeDimScaling?.() || 1;
return (
- <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
+ <div className="collectionTreeView" style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
{!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
<CollectionFreeFormView
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7ba8989ce..eb9caf29d 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' });
@@ -184,15 +183,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
},
})
);
- DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
- onClicks.push({
- description: `Set child ${childClick.title}`,
- icon: 'edit',
- event: () => {
- this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
- },
- })
- );
+ DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data)
+ .filter(childClick => ScriptCast(childClick.data))
+ .forEach(childClick =>
+ onClicks.push({
+ description: `Set child ${childClick.title}`,
+ icon: 'edit',
+ event: () => {
+ this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)!);
+ },
+ })
+ );
!Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index c071c5fb8..2f46c00bd 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',
@@ -57,11 +60,11 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore
- @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
+ @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 cc56a8ff9..4348bc7dc 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,12 +10,12 @@ 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';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
import { DocServer } from '../../DocServer';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
@@ -41,9 +41,10 @@ import { CollectionDockingView } from './CollectionDockingView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { Tooltip } from '@mui/material';
interface TabMinimapViewProps {
- document: Doc;
+ doc: Doc;
tabView: () => DocumentView | undefined;
addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
PanelWidth: () => number;
@@ -100,15 +101,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
return bounds === undefined ? bounds : { l: bounds.x + width / 2 - dim / 2, t: bounds.y + height / 2 - dim / 2, cx, cy, dim };
}
@computed get xPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.doc._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
}
@computed get yPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.doc._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
}
- childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
+ childLayoutTemplate = () => Cast(this._props.doc.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._props.doc._miniMapSize, 150);
miniDown = (e: React.PointerEvent) => {
- const doc = this._props.document;
+ const doc = this._props.doc;
const miniSize = this.returnMiniSize();
doc &&
setupMoveUpEvents(
@@ -127,15 +128,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
popup = () => {
const { renderBounds } = this;
if (!renderBounds) return <div />;
- const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
- const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+ const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniLeft = () => 50 + ((NumCast(this._props.doc._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+ const miniTop = () => 50 + ((NumCast(this._props.doc._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
const miniSize = this.returnMiniSize();
return (
<div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
<CollectionFreeFormView
- Document={this._props.document}
+ Document={this._props.doc}
docViewPath={returnEmptyDocViewList}
childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
noOverlay // don't render overlay Docs since they won't scale
@@ -144,7 +145,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
select={emptyFunction}
isSelected={returnFalse}
dontRegisterView
- fieldKey={Doc.LayoutFieldKey(this._props.document)}
+ fieldKey={Doc.LayoutDataKey(this._props.doc)}
addDocument={returnFalse}
moveDocument={returnFalse}
removeDocument={returnFalse}
@@ -172,7 +173,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
);
};
render() {
- return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ return this._props.doc.layout !== CollectionView.LayoutString(Doc.LayoutDataKey(this._props.doc)) || this._props.doc?._type_collection !== CollectionViewType.Freeform ? null : (
<div className="miniMap-hidden">
<Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
</div>
@@ -206,10 +207,10 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
const docs = toList(docIn);
const batch = UndoManager.StartBatch('Pin doc to pres trail');
- const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
+ const curPres = Doc.ActivePresentation ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : Docs.Create.PresDocument({}));
if (!Doc.ActivePresentation) {
- Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
+ Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
Doc.ActivePresentation = curPres;
}
@@ -235,7 +236,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
- const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
+ const duration = NumCast(doc[`${Doc.LayoutDataKey(pinDoc)}_duration`], null);
if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc);
if (!pinProps?.audioRange && duration !== undefined) {
@@ -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
@@ -347,13 +348,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
undoable(() => {
const target = e.currentTarget as unknown as { value: string };
titleEle.size = target?.value.length + 3;
- doc[DocData].title = target?.value ?? '';
+ doc.$title = target?.value ?? '';
}, 'edit tab title')();
};
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);
@@ -547,7 +566,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
return false;
};
- getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
+ getCurrentFrame = () => NumCast(DocCast(PresBox.Instance.activeItem?.presentation_targetDoc)?._currentFrame);
focusFunc = () => {
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
@@ -631,7 +650,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
}}>
{!this._activated || !this._document ? null : this.renderDocView(this._document)}
{this.disableMinimap() || !this._document ? null : (
- <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+ <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} doc={this._document} tabView={this.tabView} />
)}
</div>
);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6208905fe..fb2d0955f 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';
@@ -70,7 +70,7 @@ export interface TreeViewProps {
treeViewHideHeaderFields: () => boolean;
renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
onCheckedClick?: () => ScriptField;
- onChildClick?: () => ScriptField;
+ onChildClick?: () => ScriptField | undefined;
skipFields?: string[];
firstLevel: boolean;
// TODO: [AL] add these
@@ -156,10 +156,10 @@ 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));
+ return StrCast(this.Document._treeView_FieldKey, Doc.LayoutDataKey(this.Document));
}
@computed get childDocs() {
return this.childDocList(this.fieldKey);
@@ -186,7 +186,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
moving: boolean = false;
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
if (this.Document !== target && addDoc !== returnFalse) {
- const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
+ const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.DisableCompute(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
// bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
if (canAdd1 && this._props.removeDoc?.(doc) === true) {
@@ -372,9 +372,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
_width: 1000,
_height: 10,
});
- const bulletData = bullet[DocData];
- bulletData.title = ComputedField.MakeFunction('this.text?.Text');
- bulletData.data = new List<Doc>([]);
+ bullet.$title = ComputedField.MakeFunction('this.text?.Text');
+ bullet.$data = new List<Doc>([]);
DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
return bullet;
@@ -432,9 +431,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
localAdd = (docs: Doc | Doc[]): boolean => {
const innerAdd = (doc: Doc): boolean => {
- const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+ const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
- dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
+ dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)!);
return added;
};
return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
@@ -512,9 +511,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc);
const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
const innerAdd = (iDoc: Doc) => {
- const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true);
- dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer));
+ dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)!);
return added;
};
return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean);
@@ -595,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;
@@ -634,7 +633,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
d.zIndex = i;
});
}
- const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false);
!dataIsComputed && added && Doc.SetContainer(doc, this.Document);
@@ -835,7 +834,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
@observable headerEleWidth = 0;
@computed get titleButtons() {
- const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations);
+ const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations) as JSX.Element;
const color = SettingsManager.userColor;
return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : (
<>
@@ -884,8 +883,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
];
};
childContextMenuItems = () => {
- const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!;
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!;
const icons = StrListCast(this.Document.childContextMenuIcons);
return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
@@ -940,7 +939,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
if (property.startsWith(StyleProp.Decorations)) return null;
return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
- onKeyDown = (e: React.KeyboardEvent) => {
+ onKey = (e: KeyboardEvent) => {
if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
@@ -1139,7 +1138,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
ScreenToLocalTransform={this.docTransform}
renderDepth={this._props.renderDepth + 1}
onClickScript={this.onChildClick}
- onKey={this.onKeyDown}
+ onKey={this.onKey}
containerViewPath={this.treeView.childContainerViewPath}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
@@ -1269,7 +1268,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
treeViewHideHeaderFields: () => boolean,
renderedIds: string[],
onCheckedClick: undefined | (() => ScriptField),
- onChildClick: undefined | (() => ScriptField),
+ onChildClick: undefined | (() => ScriptField | undefined),
skipFields: string[] | undefined,
firstLevel: boolean,
whenChildContentsActiveChanged: (isActive: boolean) => void,
@@ -1297,7 +1296,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return;
- const fieldKey = Doc.LayoutFieldKey(newParent);
+ const fieldKey = Doc.LayoutDataKey(newParent);
if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
remove(child);
DocumentView.SetSelectOnLoad(child);
@@ -1310,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/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index f99496bba..595bbf2e9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,21 +1,19 @@
import { makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { CollectionFreeFormInfoState, infoState } from './CollectionFreeFormInfoState';
-import { Button } from '../../../util/CurrentUserUtils';
-import { InfoState } from './CollectionFreeFormInfoState';
-import { DocButtonState } from '../../nodes/DocumentLinksButton';
+import { Doc, DocListCast, FieldResult, FieldType } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
-import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
-import { CollectionFreeFormView } from '.';
-import { DocListCast } from '../../../../fields/Doc';
-import { ButtonType } from '../../nodes/FontIconBox/FontIconBox';
+import { StrCast } from '../../../../fields/Types';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { TopBar } from '../../topbar/TopBar';
+import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
+import { CollectionFreeFormView } from './CollectionFreeFormView';
+import './CollectionFreeFormView.scss';
export interface CollectionFreeFormInfoUIProps {
- Document: Doc;
- LayoutDoc: Doc;
+ Doc: Doc;
+ layoutDoc: Doc;
childDocs: () => Doc[];
close: () => void;
}
@@ -26,7 +24,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
public tutorialStates: { [key: string]: infoState } = {};
public static Init() {
- CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} />);
+ CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => (
+ //
+ <CollectionFreeFormInfoUI Doc={doc} layoutDoc={layout} childDocs={childDocs} close={close} />
+ ));
}
constructor(props: CollectionFreeFormInfoUIProps) {
@@ -50,7 +51,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
}
componentWillUnmount(): void {
- this._props.Document.backgroundColor = this._originalBackground;
+ this._props.Doc.$backgroundColor = this._originalbackground;
}
skipToState = (newState: infoState) => {
@@ -77,14 +78,63 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
};
setupStates = () => {
- let docCounter = this._props.childDocs().length;
- let lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1];
- let linkCounter = Doc.Links(lastDocCreated)?.length;
- let presentationCounter = DocListCast(Doc.ActivePresentation?.data).length;
- this._originalBackground = this._props.Document.backgroundColor as string;
-
- this.tutorialStates.multipleDocs = InfoState(
- "Let's create a new link! Click the link icon on one document and connect it to another.",
+ this._originalbackground = StrCast(this._props.Doc.$backgroundColor);
+ // state entry functions
+ // const setBackground = (colour: string) => () => {this._props.Doc.$backgroundColor = colour;} // prettier-ignore
+ // const setOpacity = (opacity: number) => () => {this._props.layoutDoc.opacity = opacity;} // prettier-ignore
+ // arc transition trigger conditions
+ const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
+ const numDocs = () => this._props.childDocs().length;
+
+ let docX: FieldResult<FieldType>;
+ let docY: FieldResult<FieldType>;
+
+ const docNewX = () => firstDoc()?.x;
+ const docNewY = () => firstDoc()?.y;
+
+ const linkStart = () => DocumentLinksButton.StartLink;
+ const linkUnstart = () => !DocumentLinksButton.StartLink;
+
+ const numDocLinks = () => Doc.Links(firstDoc())?.length;
+ const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
+
+ const activeTool = () => Doc.ActiveTool;
+
+ const pin = () => DocListCast(Doc.ActivePresentation?.data);
+
+ let trail: number;
+
+ const presentationMode = () => Doc.ActivePresentation?.presentation_status;
+
+ // set of states
+ const start = InfoState(
+ 'Click anywhere and begin typing to create your first text document.',
+ {
+ docCreated: [() => numDocs(), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
+ return oneDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const oneDoc = InfoState(
+ 'Hello world! You can drag and drop to move your document around.',
+ {
+ // docCreated: [() => numDocs() > 1, () => multipleDocs],
+ docDeleted: [() => numDocs() < 1, () => start],
+ docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => {
+ docX = firstDoc()?.x;
+ docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
+ return movedDoc;
+ }],
+ }
+ ); // prettier-ignore
+
+ const movedDoc = InfoState(
+ 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")',
{
// eslint-disable-next-line no-use-before-define
linkStarted: [
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 241a56a88..4d17dedfb 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';
@@ -107,7 +108,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
}
function toNumber(val: FieldResult<FieldType>) {
- return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getMilliseconds() : NumCast(val, Number(StrCast(val)));
+ return val === undefined ? undefined : DateCast(val) ? DateCast(val)!.date.getTime() : NumCast(val, Number(StrCast(val)));
}
export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
@@ -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/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index bc9dd022c..2683d9439 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -7,7 +7,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
export interface CollectionFreeFormPannableContentsProps {
- Document: Doc;
+ Doc: Doc;
viewDefDivClick?: ScriptField;
children?: React.ReactNode | undefined;
transition: () => string;
@@ -33,7 +33,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent
makeObservable(this);
}
@computed get presPaths() {
- return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null;
+ return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Doc) : null;
}
// rectangle highlight used when following trail/link to a region of a collection that isn't a document
showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index f64c6715b..86310dca3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -13,7 +13,7 @@ import './CollectionFreeFormView.scss';
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
@computed protected get cursors(): CursorField[] {
- const { Document } = this.props;
+ const { Document: Document } = this.props;
const cursors = Cast(Document.cursors, listSpec(CursorField));
if (!cursors) {
return [];
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 0ce72add5..93314e383 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,14 +1,17 @@
-import { Bezier } from 'bezier-js';
import { Button, Colors, Type } from '@dash/components';
+import { Slider } from '@mui/material';
+import { Bezier } from 'bezier-js';
import { Property } from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { AiOutlineSend } from 'react-icons/ai';
+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';
@@ -28,6 +31,7 @@ import { DragManager } from '../../../util/DragManager';
import { dropActionType } from '../../../util/DropActionTypes';
import { CompileScript } from '../../../util/Scripting';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SettingsManager } from '../../../util/SettingsManager';
import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoable, UndoManager } from '../../../util/UndoManager';
@@ -37,26 +41,26 @@ import { InkingStroke } from '../../InkingStroke';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
import {
+ ActiveEraserWidth,
ActiveInkArrowEnd,
ActiveInkArrowStart,
- ActiveInkDash,
- ActiveEraserWidth,
- ActiveInkFillColor,
ActiveInkBezierApprox,
ActiveInkColor,
+ ActiveInkDash,
+ ActiveInkFillColor,
ActiveInkWidth,
ActiveIsInkMask,
DocumentView,
SetActiveInkColor,
SetActiveInkWidth,
} from '../../nodes/DocumentView';
-import { FieldViewProps } from '../../nodes/FieldView';
import { FocusViewOptions } from '../../nodes/FocusViewOptions';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { OpenWhere } from '../../nodes/OpenWhere';
import { PinDocView, PinProps } from '../../PinFuncs';
-import { StickerPalette } from '../../smartdraw/StickerPalette';
+import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
+import { StickerPalette } from '../../smartdraw/StickerPalette';
import { StyleProp } from '../../StyleProp';
import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
import { TreeViewType } from '../CollectionTreeViewType';
@@ -67,12 +71,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
-import ReactLoading from 'react-loading';
-import { SettingsManager } from '../../../util/SettingsManager';
-import { Slider } from '@mui/material';
-import { AiOutlineSend } from 'react-icons/ai';
-import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
-import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
@observer
class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
@@ -183,10 +181,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get nativeWidth() {
- return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document);
}
@computed get nativeHeight() {
- return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document);
}
@computed get centeringShiftX(): number {
return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / this.nativeDimScaling; // shift so pan position is at center of window for non-overlay collections
@@ -260,8 +258,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
override contentBounds = () => {
const { x, y, r, b } = aggregateBounds(
this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
- NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 0),
- NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 0)
+ NumCast(this.layoutDoc._xPadding, NumCast(this.layoutDoc._xMargin, this._props.xPadding ?? 0)),
+ NumCast(this.layoutDoc._yPadding, NumCast(this.layoutDoc._yMargin, this._props.yPadding ?? 0))
);
const [width, height] = [r - x, b - y];
return {
@@ -279,7 +277,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
- panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
+ panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? ''));
fitContentOnce = () => {
const { cx, cy, scale } = this.contentBounds(); // prettier-ignore
this.layoutDoc._freeform_panX = cx;
@@ -288,9 +286,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
// freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
// 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(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
- panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+ 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(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);
@@ -392,7 +390,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (options.easeFunc) this.setPresEaseFunc(options.easeFunc);
if (this._lightboxDoc) return undefined;
if (options.pointFocus) return this.focusOnPoint(options);
- const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor);
+ const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutDataKey(this.Document)]).includes(anchor);
const anchorInChildViews = this.childLayoutPairs.map(pair => pair.layout).includes(anchor);
if (!anchorInCollection && !anchorInChildViews) {
return undefined;
@@ -444,7 +442,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);
@@ -500,6 +498,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}, 'link drop');
onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => {
+ if (this._props.rejectDrop?.(de, this._props.DocumentView?.())) return false;
if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
@@ -564,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const { points } = ge;
const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = this.createInkDoc(points, B);
- if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc[DocData].backgroundColor = 'transparent';
+ if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc.$backgroundColor = 'transparent';
if (Doc.ActiveInk === InkInkTool.Write) {
this.unprocessedDocs.push(inkDoc);
CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
@@ -642,7 +641,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height);
const inkDoc = this.createInkDoc(points, B);
['color', 'fillColor', 'stroke_width', 'stroke_dash', 'stroke_bezier'].forEach(field => {
- inkDoc[DocData][field] = stroke.dataDoc[field];
+ inkDoc['$' + field] = stroke.dataDoc[field];
});
this.addDocument(inkDoc);
});
@@ -1263,15 +1262,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
* Adds the created drawing to the freeform canvas and sets the metadata.
*/
addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => {
- const docData = doc[DocData];
- docData.title = opts.text;
- docData._width = opts.size;
- docData.ai_drawing_input = opts.text;
- docData.ai_drawing_complexity = opts.complexity;
- docData.ai_drawing_colored = opts.autoColor;
- docData.ai_drawing_size = opts.size;
- docData.ai_drawing_data = gptRes;
- docData.ai = 'gpt';
+ doc.$title = opts.text;
+ doc.$width = opts.size;
+ doc.$ai_drawing_input = opts.text;
+ doc.$ai_drawing_complexity = opts.complexity;
+ doc.$ai_drawing_colored = opts.autoColor;
+ doc.$ai_drawing_size = opts.size;
+ doc.$ai_drawing_data = gptRes;
+ doc.$ai = 'gpt';
this._drawingContainer = doc;
if (x !== undefined && y !== undefined) {
[doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y);
@@ -1313,7 +1311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
SnappingManager.TriggerUserPanned();
if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return;
e.stopPropagation();
- const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
+ const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight);
const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
switch (
!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
@@ -1445,9 +1443,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) {
@@ -1493,20 +1490,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
* @param below whether to place the new text Doc below or to the right of the one being typed into.
* @returns whether the new text doc was created and added successfully
*/
- createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
- const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
- const newDoc = Doc.MakeCopy(textDoc, true);
- newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = 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);
- return this.addDocument?.(newDoc);
+ createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => {
+ const textDoc = DocCast(textBox.Document);
+ if (textDoc) {
+ const newDoc = Doc.MakeCopy(textDoc, true);
+ newDoc['$' + Doc.LayoutDataKey(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);
+ return this.addDocument?.(newDoc);
+ }
+ return false;
}, 'copied text note');
- onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
- if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
+ onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => {
+ if ((e.metaKey || e.ctrlKey || e.altKey || textBox.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
- return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
+ return this.createTextDocCopy(textBox, !e.altKey && e.key !== 'Tab');
}
return undefined;
};
@@ -1546,7 +1546,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
rootSelected={childData ? this.rootSelected : returnFalse}
waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
onClickScript={this.onChildClickHandler}
- onKey={this.onKeyDown}
+ onKey={this.onKey}
onDoubleClickScript={this.onChildDoubleClickHandler}
bringToFront={this.bringToFront}
ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
@@ -1565,6 +1565,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
pinToPres={this._props.pinToPres}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+ rejectDrop={this._props.childRejectDrop}
showTitle={this._props.childlayout_showTitle}
dontRegisterView={this._props.dontRegisterView}
pointerEvents={this.childPointerEventsFunc}
@@ -1593,7 +1594,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;
}
@@ -1605,14 +1606,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',
@@ -1630,8 +1631,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: '',
@@ -1724,7 +1725,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,
})
);
@@ -1747,7 +1748,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (addAsAnnotation) {
if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+ Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor);
} else {
this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
@@ -1882,15 +1883,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
Object.values(this._disposers).forEach(disposer => disposer?.());
}
- updateIcon = (usePanelDimensions?: boolean) => {
+ updateIcon = (/*usePanelDimensions?: boolean*/) => {
const contentDiv = this._mainCont;
return !contentDiv
? new Promise<void>(res => res())
: UpdateIcon(
this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
contentDiv,
- usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
- usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
+ 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(),
this._props.PanelHeight(),
0,
@@ -2029,7 +2030,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
optionItems.push({
description: 'Use Background Color as Default',
event: () => {
- Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+ DocCast(Doc.UserDoc().emptyCollection) && (DocCast(Doc.UserDoc().emptyCollection)!.backgroundColor = StrCast(this.layoutDoc.backgroundColor));
},
icon: 'palette',
});
@@ -2129,7 +2130,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
onPassiveWheel = (e: WheelEvent) => {
- const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
+ const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight);
const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
this._props.isSelected() && !scrollable && e.preventDefault();
};
@@ -2152,12 +2153,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
</div>
);
}
- transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
+ transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? ''));
get pannableContents() {
this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters
return (
<CollectionFreeFormPannableContents
- Document={this.Document}
+ Doc={this.Document}
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.PanZoomCenterXf}
@@ -2175,6 +2176,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<MarqueeView
{...this._props}
ref={this._marqueeViewRef}
+ Doc={this.Document}
ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
@@ -2191,7 +2193,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isAnnotationOverlay={this.isAnnotationOverlay}>
{this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
{this.pannableContents}
- {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null}
+ {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} Doc={this._props.Document} /> : null}
</MarqueeView>
);
}
@@ -2306,10 +2308,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
id={this._paintedId}
ref={r => {
this.createDashEventsTarget(r);
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = r;
- // prevent wheel events from passivly propagating up through containers
- r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel);
r?.addEventListener('mouseleave', this.onMouseLeave);
r?.addEventListener('mouseenter', this.onMouseEnter);
}}
@@ -2356,7 +2355,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
NativeWidth={returnZero}
NativeHeight={returnZero}
onClickScript={this.onChildClickHandler}
- onKey={this.onKeyDown}
+ onKey={this.onKey}
onDoubleClickScript={this.onChildDoubleClickHandler}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
@@ -2428,7 +2427,7 @@ ScriptingGlobals.add(function datavizFromSchema() {
const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text');
if (!keys) return;
- const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
+ const children = DocListCast(view.Document[Doc.LayoutDataKey(view.Document)]);
const csvRows = [];
csvRows.push(keys.join(','));
for (let i = 0; i < children.length; i++) {
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index b9f8b13a7..142085e14 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';
@@ -24,6 +24,7 @@ import { FaceRecognitionHandler } from '../../search/FaceRecognitionHandler';
import { CollectionStackingView } from '../CollectionStackingView';
import './FaceCollectionBox.scss';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
+import { returnEmptyDocViewList } from '../../StyleProvider';
/**
* This code is used to render the sidebar collection of unique recognized faces, where each
@@ -106,9 +107,9 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
// assign the face in the image that's closest to the face collection's face
if (faceAnno) {
- faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face));
+ DocCast(faceAnno.face) && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face)!);
FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
- faceAnno[DocData].face = this.Document[DocData];
+ faceAnno.$face = this.Document[DocData];
}
}
});
@@ -116,9 +117,9 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
?.filter(doc => DocCast(doc.face)?.type === DocumentType.UFACE)
.forEach(faceAnno => {
const imgDoc = faceAnno;
- faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face));
+ DocCast(faceAnno.face) && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face)!);
FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
- faceAnno[DocData].face = this.Document[DocData];
+ faceAnno.$face = this.Document[DocData];
});
e.stopPropagation();
return true;
@@ -149,11 +150,6 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, this.Document);
}, 'remove doc from face');
- /**
- * This stops scroll wheel events when they are used to scroll the face collection.
- */
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
-
render() {
return (
<div className="face-document-item" ref={ele => this.createDropTarget(ele!)}>
@@ -180,14 +176,9 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
pointerEvents: this._props.isContentActive() ? undefined : 'none',
}}
- ref={action((ele: HTMLDivElement | null) => {
- this._listRef?.removeEventListener('wheel', this.onPassiveWheel);
- this._listRef = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
- })}>
+ ref={r => this.fixWheelEvents(r, this._props.isContentActive)}>
{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.LayoutDataKey(doc)]) ?? ['-missing-', '.png'];
return (
<div
className="image-wrapper"
@@ -197,7 +188,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
this,
e,
() => {
- const dragDoc = DocListCast(doc.data_annotations).find(a => a.face === this.Document[DocData]) ?? this.Document;
+ const dragDoc = DocListCast(doc?.data_annotations).find(a => a.face === this.Document[DocData]) ?? this.Document;
DragManager.StartDocumentDrag([e.target as HTMLElement], new DragManager.DocumentDragData([dragDoc], dropActionType.embed), e.clientX, e.clientY);
return true;
},
@@ -205,7 +196,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
emptyFunction
)
}>
- <img onClick={() => DocumentView.showDocument(doc, { willZoomCentered: true })} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
+ <img onClick={() => doc && DocumentView.showDocument(doc, { willZoomCentered: true })} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
<div className="remove-item">
<IconButton tooltip={'Remove Doc From Face Collection'} onPointerDown={() => this.removeFaceImageFromUniqueFace(doc)} icon={'x'} style={{ width: '4px' }} size={Size.XSMALL} />
</div>
@@ -239,11 +230,12 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
addDocument = (doc: Doc | Doc[], annotationKey?: string) => {
const uniqueFaceDoc = doc instanceof Doc ? doc : doc[0];
const added = uniqueFaceDoc.type === DocumentType.UFACE;
if (added) {
- Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
+ Doc.MyFaceCollection && Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard[DocData], 'myUniqueFaces', uniqueFaceDoc);
}
return added;
@@ -267,6 +259,8 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() {
{...this._props} //
styleProvider={this.stackingStyleProvider}
Document={Doc.ActiveDashboard}
+ DocumentView={undefined}
+ docViewPath={returnEmptyDocViewList}
fieldKey="myUniqueFaces"
moveDocument={this.moveDocument}
addDocument={this.addDocument}
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index a3d9641da..ff9fb14e7 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -9,9 +9,8 @@ import React from 'react';
import { imageUrlToBase64 } from '../../../../ClientUtils';
import { Utils, numberRange } from '../../../../Utils';
import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
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,9 +164,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them.
const imageInfos = this._selectedImages.map(async doc => {
- if (!doc[DocData].tags_chat) {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
- return imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
+ if (!doc.$tags_chat) {
+ const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? '';
+ return imageUrlToBase64(url).then(hrefBase64 =>
!hrefBase64 ? undefined :
gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels =>
({ doc, labels }))) ; // prettier-ignore
@@ -176,7 +175,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
(await Promise.all(imageInfos)).forEach(imageInfo => {
if (imageInfo) {
- imageInfo.doc[DocData].tags_chat = (imageInfo.doc[DocData].tags_chat as List<string>) ?? new List<string>();
+ imageInfo.doc.$tags_chat = (imageInfo.doc.$tags_chat as List<string>) ?? new List<string>();
const labels = imageInfo.labels.split('\n');
labels.forEach(label => {
@@ -186,7 +185,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
.replace(/^\d+\.\s*|-|f\*/, '')
.replace(/^#/, '')
.trim();
- (imageInfo.doc[DocData].tags_chat as List<string>).push(hashLabel);
+ (imageInfo.doc.$tags_chat as List<string>).push(hashLabel);
});
}
});
@@ -200,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[DocData].tags_chat as List<string>).length; index++) {
- const label = (doc[DocData].tags_chat as List<string>)[index];
- const embedding = await gptGetEmbedding(label);
- doc[DocData][`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.
@@ -215,13 +212,13 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
// For each image, loop through the labels, and calculate similarity. Associate it with the
// most similar one.
this._selectedImages.forEach(doc => {
- const embedLists = numberRange((doc[DocData].tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[DocData][`tags_embedding_${n + 1}`])));
+ const embedLists = numberRange((doc.$tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[`$tags_embedding_${n + 1}`])));
const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map(l => (embedding && similarity(Array.from(embedding), l)!) || 0));
const {label: mostSimilarLabelCollect} =
this._labelGroups.map(label => ({ label, similarityScore: bestEmbedScore(labelToEmbedding.get(label)) }))
.reduce((prev, cur) => cur.similarityScore < 0.3 || cur.similarityScore <= prev.similarityScore ? prev: cur,
{ label: '', similarityScore: 0, }); // prettier-ignore
- doc[DocData].data_label = mostSimilarLabelCollect; // The label most similar to the image's contents.
+ doc.$data_label = mostSimilarLabelCollect; // The label most similar to the image's contents.
});
this.endLoading();
@@ -313,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.LayoutDataKey(doc)]);
return (
<div className="image-information" style={{ borderColor: SettingsManager.userColor }} key={Utils.GenerateGuid()}>
<img
@@ -322,7 +319,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
await DocumentView.showDocument(doc, { willZoomCentered: true });
}}></img>
<div className="image-information-labels" onClick={() => this._props.addDocTab(doc, OpenWhere.addRightKeyvalue)}>
- {(doc[DocData].tags_chat as List<string>).map(label => {
+ {(doc.$tags_chat as List<string>).map(label => {
return (
<div key={Utils.GenerateGuid()} className="image-label" style={{ backgroundColor: SettingsManager.userVariantColor, borderColor: SettingsManager.userColor }}>
{label}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 00d7ea451..3cc7c0f2d 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, DocCast, 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';
@@ -32,6 +31,7 @@ import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
interface MarqueeViewProps {
+ Doc: Doc;
getContainerTransform: () => Transform;
getTransform: () => Transform;
activeDocuments: () => Doc[];
@@ -113,6 +113,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
+
if (e.key === '?') {
cm.setDefaultItem('?', (str: string) =>
this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
@@ -164,7 +165,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
error && console.log(error);
data &&
ClientUtils.convertDataUri(data, this._props.Document[Id] + '_icon_' + new Date().getTime()).then(returnedfilename => {
- this._props.Document[DocData].icon = new ImageField(returnedfilename);
+ this._props.Document.$icon = new ImageField(returnedfilename);
});
})
);
@@ -370,13 +371,12 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const newCollection = creator
? creator(selected, { title: 'nested stack' })
: ((doc: Doc) => {
- const docData = doc[DocData];
- docData.data = new List<Doc>(selected);
- docData.isGroup = makeGroup;
- docData.title = makeGroup ? 'grouping' : 'nested freeform';
+ doc.$data = new List<Doc>(selected);
+ doc.$isGroup = makeGroup;
+ doc.$title = makeGroup ? 'grouping' : 'nested freeform';
doc._freeform_panX = doc._freeform_panY = 0;
return doc;
- })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+ })(DocCast(Doc.UserDoc().emptyCollection) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyCollection)!, true) : Docs.Create.FreeformDocument([], {}));
newCollection.isSystem = undefined;
newCollection._width = bounds.width || 1; // if width/height are unset/0, then groups won't autoexpand to contain their children
newCollection._height = bounds.height || 1;
@@ -437,11 +437,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
*/
@undoBatch
classifyImages = async () => {
- const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
+ const groupButton = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImageGrouper);
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 });
}
};
@@ -461,9 +461,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const newColDim = 900;
for (const label of labelGroups) {
const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds);
- newCollection[DocData].title = label + ' Collection';
- newCollection._x = this.Bounds.left + x_offset;
- newCollection._y = this.Bounds.top + y_offset;
+ newCollection.$title = label + ' Collection';
+ newCollection.x = this.Bounds.left + x_offset;
+ newCollection.y = this.Bounds.top + y_offset;
newCollection._width = newColDim;
newCollection._height = newColDim;
newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
@@ -480,8 +480,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
for (const doc of selectedImages) {
- if (doc[DocData].data_label) {
- Doc.AddDocToList(labelToCollection.get(doc[DocData].data_label as string)!, undefined, doc);
+ if (doc.$data_label) {
+ Doc.AddDocToList(labelToCollection.get(doc.$data_label as string)!, undefined, doc);
this._props.removeDocument?.(doc);
}
}
@@ -590,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..1d5e70be7 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}
@@ -201,6 +201,7 @@ export class CollectionGridView extends CollectionSubView() {
isContentActive={this.isChildContentActive}
PanelWidth={width}
PanelHeight={height}
+ rejectDrop={this._props.childRejectDrop}
ScreenToLocalTransform={dxf}
setContentViewBox={emptyFunction}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 80116dd2f..3c2a99b1e 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -102,7 +102,7 @@ export class CollectionLinearView extends CollectionSubView() {
getLinkUI = () =>
!DocumentLinksButton.StartLink ? null : (
- <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
+ <span key="-link-ui-" className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
<span className="bottomPopup-text">
Creating link from:{' '}
<b>
@@ -126,7 +126,7 @@ export class CollectionLinearView extends CollectionSubView() {
);
getCurrentlyPlayingUI = () =>
!DocumentView.CurrentlyPlaying?.length ? null : (
- <span className="bottomPopup-background">
+ <span key="-currently-playing-" className="bottomPopup-background">
<span className="bottomPopup-text">
Currently playing:
{DocumentView.CurrentlyPlaying.map((clip, i) => (
@@ -192,6 +192,7 @@ export class CollectionLinearView extends CollectionSubView() {
renderDepth={this._props.renderDepth + 1}
dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
focus={emptyFunction}
+ rejectDrop={this._props.childRejectDrop}
styleProvider={this._props.styleProvider}
containerViewPath={this.childContainerViewPath}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 8aae24df0..caad1c7e0 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,52 @@ 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}
+ rejectDrop={this._props.childRejectDrop}
+ 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 +316,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 +327,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..593598479 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,52 @@ 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}
+ rejectDrop={this._props.childRejectDrop}
+ 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.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 0bf78f57c..53c0823ea 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -224,6 +224,7 @@
display: none;
}
+.schema-table-cell-selected,
.schema-table-cell,
.row-menu {
border: 1px solid global.$medium-gray;
@@ -232,6 +233,14 @@
display: inline-flex;
padding: 0;
align-items: center;
+ input[type='text'] {
+ border: unset;
+ }
+}
+.schema-table-cell-selected {
+ input[type='text'] {
+ background: lightgray;
+ }
}
.schemaRTFCell {
@@ -310,4 +319,7 @@
.schemaField-editing {
outline: none;
height: 100%;
+ cursor: text;
+ outline: none;
+ overflow: auto;
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 5a5cc3622..c06391f35 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -12,7 +12,7 @@ import { List } from '../../../../fields/List';
import { ColumnType } from '../../../../fields/SchemaHeaderField';
import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocUtils } from '../../../documents/DocUtils';
-import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
+import { Docs, DocumentOptions, FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { dropActionType } from '../../../util/DropActionTypes';
@@ -49,14 +49,16 @@ import { SchemaRowBox } from './SchemaRowBox';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
-export const FInfotoColType: { [key: string]: ColumnType } = {
+export const FInfotoColType: { [key in FInfoFieldType]: ColumnType } = {
string: ColumnType.String,
number: ColumnType.Number,
boolean: ColumnType.Boolean,
date: ColumnType.Date,
- image: ColumnType.Image,
- rtf: ColumnType.RTF,
- enumeration: ColumnType.Enumeration,
+ richtext: ColumnType.RTF,
+ enum: ColumnType.Enumeration,
+ Doc: ColumnType.Any,
+ list: ColumnType.Any,
+ map: ColumnType.Any,
};
const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
@@ -198,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 => {
@@ -250,7 +252,8 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
onKeyDown = (e: KeyboardEvent) => {
if (this._selectedDocs.length > 0) {
- switch (e.key) {
+ switch (e.key + (e.shiftKey ? 'Shift' : '')) {
+ case 'Enter':
case 'ArrowDown':
{
const lastDoc = this._selectedDocs.lastElement();
@@ -270,6 +273,7 @@ export class CollectionSchemaView extends CollectionSubView() {
e.preventDefault();
}
break;
+ case 'EnterShift':
case 'ArrowUp':
{
const firstDoc = this._selectedDocs.lastElement();
@@ -289,6 +293,7 @@ export class CollectionSchemaView extends CollectionSubView() {
e.preventDefault();
}
break;
+ case 'Tab':
case 'ArrowRight':
if (this._selectedCells) {
this._selectedCol = Math.min(this._colEles.length - 1, this._selectedCol + 1);
@@ -296,6 +301,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.selectCell(this._selectedDocs[0], 0, false, false);
}
break;
+ case 'TabShift':
case 'ArrowLeft':
if (this._selectedCells) {
this._selectedCol = Math.max(0, this._selectedCol - 1);
@@ -1187,7 +1193,7 @@ export class CollectionSchemaView extends CollectionSubView() {
let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
if (notFiltered) {
notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
- const fieldKey = Doc.LayoutFieldKey(d);
+ const fieldKey = Doc.LayoutDataKey(d);
const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
@@ -1200,7 +1206,7 @@ export class CollectionSchemaView extends CollectionSubView() {
newarray = [];
// eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
- const docFieldKey = Doc.LayoutFieldKey(t);
+ const docFieldKey = Doc.LayoutDataKey(t);
const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
@@ -1287,7 +1293,6 @@ export class CollectionSchemaView extends CollectionSubView() {
isContentActive = () => this._props.isSelected() || this._props.isContentActive();
screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
previewWidthFunc = () => this.previewWidth;
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
displayedDocsFunc = () => this.docsWithDrag.docs;
render() {
return (
@@ -1301,15 +1306,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.setColDrag(false);
}}>
<div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} />
- <div
- className="schema-table"
- style={{ width: `calc(100% - ${this.previewWidth}px)` }}
- onWheel={e => this._props.isContentActive() && e.stopPropagation()}
- ref={ele => {
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- (this._oldWheel = ele)?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
- }}>
+ <div className="schema-table" style={{ width: `calc(100% - ${this.previewWidth}px)` }} onWheel={e => this._props.isContentActive() && e.stopPropagation()} ref={ele => this.fixWheelEvents(ele, this._props.isContentActive)}>
<div className="schema-header-row" style={{ height: this.rowHeightFunc() }}>
<div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
<IconButton
diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
index 5a64ecc62..9ad94cb31 100644
--- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
@@ -21,11 +21,11 @@ import DOMPurify from 'dompurify';
*/
export interface SchemaCellFieldProps {
- contents: FieldType;
+ Doc: Doc;
+ contents: FieldType | undefined;
fieldContents?: FieldViewProps;
editing?: boolean;
oneLine?: boolean;
- Document: Doc;
fieldKey: string;
// eslint-disable-next-line no-use-before-define
refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined };
@@ -55,7 +55,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
}); //must be moved to end of batch or else other docs aren't loaded, so render as d-1 in function
}
- get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore
+ get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore
get selfRefPattern() {
return `d${this.docIndex}.${this._props.fieldKey}`;
@@ -85,16 +85,8 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
() => this._editing,
editing => {
if (editing) {
+ this.setContent((this._unrenderedContent = this._props.GetValue() ?? ''));
this.setupRefSelect(this.refSelectConditionMet);
- setTimeout(() => {
- if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) {
- this._overlayDisposer?.();
- this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- this._props.highlightCells?.(this._unrenderedContent);
- this.setContent(this._unrenderedContent);
- setTimeout(() => this.setCursorPosition(this._unrenderedContent.length));
- }
- });
} else {
this._overlayDisposer?.();
this._overlayDisposer = undefined;
@@ -107,9 +99,8 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
this._disposers.fieldUpdate = reaction(
() => this._props.GetValue(),
fieldVal => {
- console.log('Update: ' + this._props.Document.title, this._props.fieldKey, fieldVal);
this._unrenderedContent = fieldVal ?? '';
- this.finalizeEdit(false, false, false);
+ this._editing && this.finalizeEdit(false, false, false);
}
);
}
@@ -127,7 +118,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
_unmounted = false;
componentWillUnmount(): void {
this._unmounted = true;
- console.log('Unmount: ' + this._props.Document.title, this._props.fieldKey);
this._overlayDisposer?.();
Object.values(this._disposers).forEach(disposer => disposer?.());
this.finalizeEdit(false, true, false);
@@ -194,6 +184,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
setIsFocused = (value: boolean) => {
const wasFocused = this._editing;
this._editing = value;
+ this._editing && setTimeout(() => this._inputref?.focus());
return wasFocused !== this._editing;
};
@@ -274,8 +265,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here
-
switch (e.key) {
case 'Tab':
e.stopPropagation();
@@ -286,9 +275,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
break;
case 'Enter':
e.stopPropagation();
- if (!e.ctrlKey) {
- this.finalizeEdit(e.shiftKey, false, true);
- }
+ !e.ctrlKey && this.finalizeEdit(e.shiftKey, false, true);
break;
case 'Escape':
e.stopPropagation();
@@ -299,7 +286,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
case 'ArrowLeft':
case 'ArrowRight': // prettier-ignore
e.stopPropagation();
- setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0);
+ setTimeout(() => this.setupRefSelect(this.refSelectConditionMet));
break;
case ' ':
{
@@ -308,18 +295,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
setTimeout(() => {
this.setContent(this._unrenderedContent);
setTimeout(() => this.setCursorPosition(cursorPos));
- }, 0);
+ });
}
break;
- case 'u': // for some reason 'u' otherwise exits the editor
- e.stopPropagation();
- break;
case 'Shift':
case 'Alt':
case 'Meta':
case 'Control':
- case ':': // prettier-ignore
- break;
+ case ':':
default:
break;
}
@@ -363,12 +346,9 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
<div
contentEditable
className="schemaField-editing"
- ref={r => {
- this._inputref = r;
- }}
- style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }}
+ ref={r => (this._inputref = r)}
+ style={{ minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }}
onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))}
- autoFocus
onInput={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={e => {
@@ -385,14 +365,37 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
);
};
+ onFocus = () => {
+ if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) {
+ this._overlayDisposer?.();
+ this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+ this._props.highlightCells?.(this._unrenderedContent);
+ this.setContent(this._unrenderedContent);
+ setTimeout(() => this.setCursorPosition(this._unrenderedContent.length));
+ }
+ };
+
+ onBlur = action(() => {
+ this._editing = false;
+ });
+
render() {
const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
if (this._editing && gval !== undefined) {
- return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>;
+ return (
+ <div
+ className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} //
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}>
+ {this.renderEditor()}
+ </div>
+ );
} else
return this._props.contents instanceof ObjectField ? null : (
<div
className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
style={{
minHeight: '10px',
whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line',
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 81a2d8e64..16d33eb93 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -107,7 +107,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea
focus: emptyFunction,
addDocTab: SchemaTableCell.addFieldDoc,
pinToPres: returnZero,
- Document: DocCast(Document.rootDocument, Document),
+ Document: DocCast(Document.rootDocument, Document)!,
fieldKey: fieldKey,
PanelWidth: columnWidth,
PanelHeight: props.rowHeight,
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index da203abfa..c9853fab0 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -171,7 +171,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
isolatedSelection={this.isolatedSelection}
key={key}
rowSelected={this._props.isSelected}
- Document={this.Document}
+ Doc={this.Document}
col={index}
fieldKey={key}
allowCRs={false} // to enter text with new lines, must use \n
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index c847bc546..8b34b4139 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.
@@ -40,7 +41,7 @@ import { SchemaCellField } from './SchemaCellField';
*/
export interface SchemaTableCellProps {
- Document: Doc;
+ Doc: Doc;
col: number;
deselectCell: () => void;
selectCell: (doc: Doc, col: number, shift: boolean, ctrl: boolean) => void;
@@ -71,7 +72,7 @@ export interface SchemaTableCellProps {
}
function selectedCell(props: SchemaTableCellProps) {
- return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Document)?.length;
+ return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Doc)?.length;
}
@observer
@@ -84,11 +85,11 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
makeObservable(this);
}
- get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore
+ get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore
get isDefault(){return SchemaColumnHeader.isDefaultField(this._props.fieldKey);} // prettier-ignore
- get lockedInteraction(){return (this.isDefault || this._props.Document._lockedSchemaEditing);} // prettier-ignore
+ get lockedInteraction(){return (this.isDefault || this._props.Doc._lockedSchemaEditing);} // prettier-ignore
get backgroundColor() {
if (this.lockedInteraction) {
@@ -102,15 +103,16 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
return true;
};
public static renderProps(props: SchemaTableCellProps) {
- const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props;
+ const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
let protoCount = 0;
+ const layoutDoc = fieldKey.startsWith('_') ? Document[DocLayout] : Document;
let doc: Doc | undefined = 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,13 +132,13 @@ 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,
rootSelected: props.rootSelected,
};
- const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
+ const readOnly = false; // getFinfo(fieldKey)?.readOnly ?? false;
const cursor = !readOnly ? 'text' : 'default';
const pointerEvents: 'all' | 'none' = !readOnly && isRowActive() ? 'all' : 'none';
return { color, textDecoration, fieldProps, cursor, pointerEvents };
@@ -166,16 +168,19 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
while ((matches = idPattern.exec(field)) !== null) {
results.push([matches[0], matches[1].replace(/"/g, '')]);
}
- results.forEach(idFuncPair => {
- modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])).toString());
- });
+ results
+ .filter(idFuncPair => IdToDoc(idFuncPair[1]))
+ .forEach(idFuncPair => {
+ modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])!).toString());
+ });
if (modField.endsWith(';')) modField = modField.substring(0, modField.length - 1);
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;
};
@@ -186,7 +191,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
return (
<div
className="schemacell-edit-wrapper"
- // onContextMenu={}
+ tabIndex={1}
style={{
color,
textDecoration,
@@ -196,7 +201,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
<SchemaCellField
fieldKey={this._props.fieldKey}
refSelectModeInfo={this._props.refSelectModeInfo}
- Document={this._props.Document}
+ Doc={this._props.Doc}
highlightCells={(text: string) => this._props.highlightCells(this.adjustSelfReference(text))}
getCells={(text: string) => this._props.eqHighlightFunc(this.adjustSelfReference(text))}
ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)}
@@ -211,8 +216,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;
@@ -224,7 +228,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
get getCellType() {
const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType;
- const cellValue = this._props.Document[this._props.fieldKey];
+ const cellValue = this._props.Doc[this._props.fieldKey];
if (cellValue instanceof ImageField) return ColumnType.Image;
if (cellValue instanceof DateField) return ColumnType.Date;
@@ -232,9 +236,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
if (typeof cellValue === 'number') return ColumnType.Any;
if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any;
if (typeof cellValue === 'boolean') return ColumnType.Boolean;
- if (columnTypeStr && columnTypeStr in FInfotoColType) return FInfotoColType[columnTypeStr];
- return ColumnType.Any;
+ return columnTypeStr ? FInfotoColType[columnTypeStr] : ColumnType.Any;
}
get content() {
@@ -253,15 +256,15 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
const sides: Array<string | undefined> = [];
sides[0] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // left
sides[1] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // right
- sides[2] = !this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
- sides[3] = !this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
+ sides[2] = !this._props.isolatedSelection(this._props.Doc)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
+ sides[3] = !this._props.isolatedSelection(this._props.Doc)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
return sides;
}
render() {
return (
<div
- className="schema-table-cell"
+ className={`schema-table-cell${selectedCell(this._props) ? '-selected' : ''}`}
onContextMenu={e => StopEvent(e)}
onPointerDown={action(e => {
if (this.lockedInteraction) {
@@ -273,7 +276,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)) {
e.stopPropagation();
e.preventDefault();
- this._props.selectReference(this._props.Document, this._props.col);
+ this._props.selectReference(this._props.Doc, this._props.col);
return;
}
@@ -281,9 +284,9 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
const ctrl: boolean = e.ctrlKey;
if (this._props.isRowActive?.()) {
if (selectedCell(this._props) && ctrl) {
- this._props.selectCell(this._props.Document, this._props.col, shift, ctrl);
+ this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl);
e.stopPropagation();
- } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl);
+ } else !selectedCell(this._props) && this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl);
}
})}
style={{
@@ -322,12 +325,12 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
}
get url() {
- const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const field = Cast(this._props.Doc[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this._props.Doc[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
const altpaths = alts
- .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+ .map(doc => Cast(doc[Doc.LayoutDataKey(doc)], ImageField, null)?.url)
.filter(url => url)
- .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ .map(url => this.choosePath(url!)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
// If there is a path, follow it; otherwise, follow a link to a default image icon
const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
@@ -360,7 +363,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
};
render() {
- const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio
+ const aspect = Doc.NativeAspect(this._props.Doc); // aspect ratio
// let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px
// const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
@@ -378,9 +381,9 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
}
@observable _pickingDate: boolean = false;
- @computed get date(): DateField {
+ @computed get date(): DateField | undefined {
// if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
- return DateCast(this._props.Document[this._props.fieldKey]);
+ return DateCast(this._props.Doc[this._props.fieldKey]);
}
handleChange = undoable((date: Date | null) => {
@@ -389,7 +392,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
// this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
- date && (this._props.Document[this._props.fieldKey] = new DateField(date));
+ date && (this._props.Doc[this._props.fieldKey] = new DateField(date));
// }
}, 'date change');
@@ -397,10 +400,10 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
const { pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<>
- <div style={{ pointerEvents: 'none' }}>
- <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} />
+ <div style={{ pointerEvents: 'none' }} tabIndex={1}>
+ <DatePicker dateFormat="Pp" selected={this.date?.date ?? new Date()} onChange={emptyFunction} />
</div>
- {pointerEvents === 'none' ? null : (
+ {pointerEvents === 'none' || !selectedCell(this._props) ? null : (
<Popup
icon={<FontAwesomeIcon size="xs" icon="caret-down" />}
size={Size.XSMALL}
@@ -409,7 +412,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
background={SnappingManager.userBackgroundColor}
popup={
<div style={{ width: 'fit-content', height: '200px' }}>
- <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+ <DatePicker open dateFormat="Pp" selected={this.date?.date ?? new Date()} onChange={this.handleChange} />
</div>
}
/>
@@ -449,13 +452,14 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
return (
<div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
<input
+ onPointerDown={e => e.stopPropagation()}
style={{ marginRight: 4 }}
type="checkbox"
- checked={BoolCast(this._props.Document[this._props.fieldKey])}
+ checked={BoolCast(this._props.Doc[this._props.fieldKey])}
onChange={undoable((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
if ((value?.nativeEvent as MouseEvent | PointerEvent).shiftKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
- } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
+ } else Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
}, 'set bool cell')}
/>
@@ -463,14 +467,14 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
contents=""
fieldContents={fieldProps}
editing={selectedCell(this._props) ? undefined : false}
- GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Doc, this._props.fieldKey)}
SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
this._props.finishEdit?.();
return true;
}
- const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
+ const set = Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Doc) ? true : undefined);
this._props.finishEdit?.();
return set;
}, 'set bool cell')}
@@ -494,14 +498,22 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
<div style={{ width: '100%' }}>
<Select
styles={{
+ dropdownIndicator: base => ({
+ ...base,
+ display: selectedCell(this._props) ? 'unset' : 'none',
+ }),
container: base => ({
...base,
height: 20,
+ border: 'unset !important',
+ pointerEvents: selectedCell(this._props) ? 'all' : 'none',
}),
control: base => ({
...base,
height: 20,
minHeight: 20,
+ border: 'unset !important',
+ background: selectedCell(this._props) ? 'lightgray' : undefined,
}),
placeholder: base => ({
...base,
@@ -517,6 +529,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
...base,
height: 20,
transform: 'scale(0.75)',
+ border: 'unset !important',
}),
menuPortal: base => ({
...base,
@@ -529,10 +542,10 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
}}
menuPortalTarget={this._props.menuTarget}
menuPosition="absolute"
- placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
+ placeholder={StrCast(this._props.Doc[this._props.fieldKey], 'select...')}
options={options}
isMulti={false}
- onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+ onChange={val => Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
/>
</div>
</div>
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 835c28daa..f4beb1004 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,15 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Colors } from '@dash/components';
import { runInAction } from 'mobx';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { InkEraserTool, InkInkTool, InkProperty, InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { Gestures } from '../../../pen-gestures/GestureTypes';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
@@ -17,30 +14,31 @@ import { UndoManager, undoable } from '../../util/UndoManager';
import { GestureOverlay } from '../GestureOverlay';
import { InkTranscription } from '../InkTranscription';
import { PropertiesView } from '../PropertiesView';
+import { docSortings } from '../collections/CollectionSubView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import {
ActiveEraserWidth,
- ActiveInkFillColor,
- ActiveInkColor,
ActiveHideTextLabels,
+ ActiveInkColor,
+ ActiveInkFillColor,
ActiveInkWidth,
ActiveIsInkMask,
DocumentView,
- SetActiveInkFillColor,
SetActiveInkColor,
- SetactiveHideTextLabels,
+ SetActiveInkFillColor,
SetActiveInkWidth,
SetActiveIsInkMask,
SetEraserWidth,
+ SetactiveHideTextLabels,
} from '../nodes/DocumentView';
import { ImageBox } from '../nodes/ImageBox';
+import { OpenWhere } from '../nodes/OpenWhere';
import { VideoBox } from '../nodes/VideoBox';
import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-import { OpenWhere } from '../nodes/OpenWhere';
-import { docSortings } from '../collections/CollectionSubView';
+import { DocData } from '../../../fields/DocSymbols';
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function IsNoneSelected() {
@@ -78,7 +76,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
const selView = selectedViews.lastElement();
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] || defaultBorder();
+ return (contentFrameNumber !== undefined && CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey]) || defaultBorder();
}
setDefaultBorder(color ?? 'transparent');
selectedViews.forEach(dv => {
@@ -89,7 +87,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
obj[fieldKey] = color;
CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
} else {
- const dataKey = Doc.LayoutFieldKey(dv.Document);
+ const dataKey = Doc.LayoutDataKey(dv.Document);
const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
dv.layoutDoc[fieldKey + alternate] = undefined;
dv.dataDoc[fieldKey + alternate] = color;
@@ -101,10 +99,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
return (selected.lastElement() ?? Doc.UserDoc()).borderColor ?? defaultBorder();
}
if (!selected.length) setDefaultBorder(color ?? 'transparent');
- else
- selected.forEach(doc => {
- doc[DocData].borderColor = color;
- });
+ else selected.forEach(doc => (doc.$borderColor = color));
}
return '';
});
@@ -114,7 +109,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
const selectedViews = DocumentView.Selected();
const selectedDoc = selectedViews.lastElement()?.Document;
- const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc[DocData].fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor();
+ const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc.$fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor();
const setDefaultFill = !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? (c: string) => { Doc.UserDoc().textBackgroundColor = c; }: SetActiveInkFillColor; // prettier-ignore
if (Doc.ActiveTool !== InkTool.None && !selectedViews.lastElement()?.Document._layout_isSvg) {
if (checkResult) return defaultFill();
@@ -125,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 => {
@@ -137,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.LayoutDataKey(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 {
@@ -149,10 +144,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
return selected.lastElement()?._backgroundColor ?? defaultFill();
}
if (!selected.length) setDefaultFill(color ?? 'transparent');
- else
- selected.forEach(doc => {
- doc[DocData][doc._layout_isSvg ? 'fillColor' : 'backgroundColor'] = color;
- });
+ else selected.forEach(doc => (doc[doc._layout_isSvg ? '$fillColor' : '$backgroundColor'] = color));
}
return '';
});
@@ -166,16 +158,19 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
if (checkResult) {
- return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
+ return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc()?.headingColor;
}
if (DocumentView.Selected().length) {
DocumentView.SelectedDocs().forEach(doc => {
- doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color;
+ doc.$layout_headingColor = color === 'transparent' ? undefined : color;
doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
});
} else {
- Doc.SharingDoc().headingColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+ const sharing = Doc.SharingDoc();
+ if (sharing) {
+ sharing.headingColor = undefined;
+ Doc.GetProto(sharing).headingColor = color === 'transparent' ? undefined : color;
+ }
Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
}
return undefined;
@@ -242,30 +237,30 @@ ScriptingGlobals.add(function showFreeform(
setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
}],
['time', {
- checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time",
- setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "time",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
}],
['docType', {
- checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type",
- setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "type",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
}],
['color', {
- checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color",
- setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "color",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutDataKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
}],
['tag', {
- checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag",
- setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "tag",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
}],
['reverse', {
- checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_reverse"]),
- setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"]; },
+ checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutDataKey(doc)+"_sort_reverse"]),
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutDataKey(doc)+"_sort_reverse"]; },
}],
['toggle-chat', {
checkResult: (doc: Doc) => SnappingManager.ChatVisible,
setDoc: (doc: Doc, dv: DocumentView) => {
if (SnappingManager.ChatVisible){
- doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
+ doc[Doc.LayoutDataKey(doc)+"_sort"] = '';
SnappingManager.SetChatVisible(false);
} else {
SnappingManager.SetChatVisible(true);
@@ -376,8 +371,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,
@@ -470,23 +465,23 @@ ScriptingGlobals.add(function setInkProperty(option: InkProperty, value: string
// prettier-ignore
const map: Map<InkProperty, { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
[InkProperty.Mask, {
- checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
- setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
+ checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected.$stroke_isInkMask) : ActiveIsInkMask())),
+ setInk: (doc: Doc) => { doc.$stroke_isInkMask = !doc.stroke_isInkMask; },
setMode: () => SetActiveIsInkMask(value ? true : false)
}],
[InkProperty.Labels, {
- checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_showLabel) : !ActiveHideTextLabels())),
- setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = value; },
+ checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected.$stroke_showLabel) : !ActiveHideTextLabels())),
+ setInk: (doc: Doc) => { doc.$stroke_showLabel = value; },
setMode: () => SetactiveHideTextLabels(value? false : true),
}],
[ InkProperty.StrokeWidth, {
- checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width, 1) : ActiveInkWidth()),
- setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
+ checkResult: () => (selected?._layout_isSvg ? NumCast(selected.$stroke_width, 1) : ActiveInkWidth()),
+ setInk: (doc: Doc) => { doc.$stroke_width = NumCast(value); },
setMode: () => SetActiveInkWidth(value.toString()),
}],
[InkProperty.StrokeColor, {
- checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
- setInk: (doc: Doc) => { doc[DocData].color = String(value); },
+ checkResult: () => (selected?._layout_isSvg? StrCast(selected.$color) : ActiveInkColor()),
+ setInk: (doc: Doc) => { doc.$color = String(value); },
setMode: () => SetActiveInkColor(StrCast(value))
}],
[ InkProperty.EraserWidth, {
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c15508669..9eaf0e491 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,10 +1,9 @@
-/* eslint-disable react/require-default-props */
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { Cast, DocCast } from '../../../fields/Types';
+import { DocCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentView } from '../nodes/DocumentView';
import './LinkMenu.scss';
@@ -56,7 +55,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
? DocCast(linkDoc.link_anchor_2)
: DocCast(linkDoc.link_anchor_1)
- : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+ : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, DocCast(linkDoc.link_anchor_2)?.annotationOn === sourceDoc ? DocCast(linkDoc.link_anchor_2) : DocCast(linkDoc.link_anchor_1));
return !destDoc || !sourceDoc ? null : (
<LinkMenuItem
key={linkDoc[Id]}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 1c92c3b0d..c984a7053 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -68,8 +68,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
@computed get sourceAnchor() {
const ldoc = this._props.linkDoc;
if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) {
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1)?.annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2)?.annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
}
return this._props.sourceDoc;
}
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
index f8c07cc43..7f9a13549 100644
--- a/src/client/views/newlightbox/ExploreView/ExploreView.tsx
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
import * as React from 'react';
import { StrCast } from '../../../../fields/Types';
import { NewLightboxView } from '../NewLightboxView';
@@ -19,7 +17,7 @@ export function ExploreView(props: IExploreView) {
const x = (rec.embedding.x / xBound) * 50;
const y = (rec.embedding.y / yBound) * 50;
return (
- <div className="exploreView-doc" onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}>
+ <div key={'' + x + ' ' + y} className="exploreView-doc" onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}>
{rec.title}
</div>
);
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index b060fc0b6..7ec45d0dd 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -5,7 +5,7 @@ import { returnEmptyFilter, returnTrue } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { CreateLinkToActiveAudio, Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { GestureOverlay } from '../GestureOverlay';
@@ -77,7 +77,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
NewLightboxView.SetNewLightboxDoc(
doc,
undefined,
- [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
+ [...DocListCast(doc[Doc.LayoutDataKey(doc)]), ...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
(a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
),
layoutTemplate
@@ -100,7 +100,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
SnappingManager.SetExploreMode(false);
} else {
const l = CreateLinkToActiveAudio(() => doc).lastElement();
- l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen');
DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
// DocumentView.PinDoc(doc, { hidePresBox: true });
this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
@@ -142,7 +142,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
const targetDocView = target && DocumentView.getLightboxDocumentView(target);
if (targetDocView && target) {
const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
- l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen');
DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
} else if (!target && NewLightboxView.path.length) {
@@ -326,7 +326,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
}
interface NewLightboxTourBtnProps {
navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
- // eslint-disable-next-line react/no-unused-prop-types
future: () => Opt<Doc[]>;
stepInto: () => void;
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 25e76e2a6..d4c512342 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -296,7 +296,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
'Content-Type': 'application/json',
},
});
- this.Document[DocData].phoneticTranscription = response.data['transcription'];
+ this.Document.$phoneticTranscription = response.data['transcription'];
};
// context menu
@@ -396,7 +396,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
returnFalse,
action(() => {
const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
- const textField = Doc.LayoutFieldKey(newDoc);
+ const textField = Doc.LayoutDataKey(newDoc);
const newDocData = newDoc[DocData];
newDocData[`${textField}_recordingSource`] = this.dataDoc;
newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ce1e9280a..940c4cb99 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
@@ -70,7 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
{ key: 'freeform_panY' },
]; // fields that are configured to be animatable using animation frames
public static animStringFields = ['backgroundColor', 'borderColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
- public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
+ public static animDataFields = (doc: Doc) => (Doc.LayoutDataKey(doc) ? [Doc.LayoutDataKey(doc)] : []); // fields that are configured to be animatable using animation frames
public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined {
return dv?._props.reactParent instanceof CollectionFreeFormDocumentView ? dv._props.reactParent : undefined;
}
@@ -186,7 +187,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
public static gotoKeyFrame(doc: Doc, newFrame: number) {
if (doc) {
- const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+ const childDocs = DocListCast(doc[Doc.LayoutDataKey(doc)]);
const currentFrame = Cast(doc._currentFrame, 'number', null);
if (currentFrame === undefined) {
doc._currentFrame = 0;
@@ -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._props.Document}
+ 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 5315612e1..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';
@@ -80,8 +79,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const front = Docs.Create.CenteredTextCreator('question', question, {}, img);
const back = Docs.Create.CenteredTextCreator('answer', answer, {});
if (useDoc) {
- useDoc[DocData][frontKey] = front;
- useDoc[DocData][backKey] = back;
+ useDoc['$' + frontKey] = front;
+ useDoc['$' + backKey] = back;
return useDoc;
}
return Docs.Create.FlashcardDocument(title, front, back, { _width: 300, _height: 300 });
@@ -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)[DocData].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) => {
@@ -520,7 +474,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
action(resp => {
switch (resp && callType) {
case GPTCallType.CHATCARD:
- DocCast(this.dataDoc[this.backKey])[DocData].text = resp;
+ DocCast(this.dataDoc[this.backKey]).$text = resp;
break;
case GPTCallType.QUIZDOC:
this._renderSide = this.backKey;
@@ -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
*/
@@ -618,7 +533,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const hrefBase64 = await imageUrlToBase64(u);
const response = await gptImageLabel(hrefBase64, 'Answer the following question as a short flashcard response. Do not include a label.' + (this.dataDoc.text as RichTextField)?.Text);
- DocCast(this.dataDoc[this.backKey])[DocData].text = response;
+ DocCast(this.dataDoc[this.backKey]).$text = response;
} catch (error) {
console.log('Error', error);
}
@@ -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/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index d5e37b3b5..9369ff98a 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,43 +1,39 @@
+import { Colors, Toggle, ToggleType, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox } from '@mui/material';
-import { Colors, Toggle, ToggleType, Type } from '@dash/components';
import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { ClientUtils, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { PrefetchProxy } from '../../../../fields/Proxy';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
-import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
+import { TraceMobx } from '../../../../fields/util';
import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
-import { LinkManager } from '../../../util/LinkManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FocusViewOptions } from '../FocusViewOptions';
import './DataVizBox.scss';
-import { Col, DataVizTemplateInfo, DocCreatorMenu, LayoutType} from './DocCreatorMenu/DocCreatorMenu';
+import { Col, DocCreatorMenu } from './DocCreatorMenu/DocCreatorMenu';
+import { TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu/TemplateBackend';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
-import { TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu/TemplateBackend';
export enum DataVizView {
TABLE = 'table',
@@ -112,7 +108,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
try {
- const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '');
this._urlError = false;
return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
} catch {
@@ -348,7 +344,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
componentDidMount() {
this._props.setContentViewBox?.(this);
if (!this._urlError) {
- if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+ if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '')) this.fetchData();
}
this._disposers.datavis = reaction(
() => {
@@ -356,7 +352,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
if (!getFrom?.schema_columnKeys) return undefined;
const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
- const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
+ const children = DocListCast(getFrom?.[Doc.LayoutDataKey(getFrom)]);
const current: { [key: string]: string }[] = [];
children
.filter(child => child)
@@ -390,8 +386,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.layoutDoc._dataViz_schemaOG = loading;
}
const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
- const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
- const { href } = CsvCast(this.Document[this.fieldKey]).url;
+ const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey])!.url.href : undefined;
+ const { href } = CsvCast(this.Document[this.fieldKey])?.url ?? { href: '' };
if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
// sets original dataset to the var
const lastRow = current.pop();
@@ -403,7 +399,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
},
current => {
if (current) {
- const { href } = CsvCast(this.Document[this.fieldKey]).url;
+ const { href } = CsvCast(this.Document[this.fieldKey])?.url ?? { href: '' };
if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
}
@@ -418,9 +414,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
fetchData = () => {
if (!this.Document.dataViz_asSchema) {
- DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+ DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '', []); // assign temporary dataset as a lock to prevent duplicate server requests
fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
- .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
+ .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '', jsonRes))));
}
};
@@ -440,10 +436,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
- case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
- case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
- case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}}
+ case DataVizView.TABLE: return <TableBox {...sharedProps} Doc={this.Document} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
+ case DataVizView.LINECHART: return <LineChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
+ case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
+ case DataVizView.PIECHART: return <PieChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}}
margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
default:
} // prettier-ignore
@@ -527,7 +523,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc;
GPTPopup.Instance.setDataJson('');
GPTPopup.Instance.setMode(GPTPopupMode.DATA);
- const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '');
GPTPopup.Instance.setDataJson(JSON.stringify(csvdata));
GPTPopup.Instance.generateDataAnalysis();
});
@@ -574,9 +570,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
- cols.forEach(col => {
- this.setColumnDefault(col, `${this.records[rowToCheck][col]}`);
- });
+ cols.forEach(col => this.setColumnDefault(col, `${this.records[rowToCheck][col]}`));
};
updateGPTSummary = async () => {
@@ -706,7 +700,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
ref={this._sidebarRef}
{...this._props}
fieldKey={this.fieldKey}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 8ae29a88c..1e2a95d31 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -11,10 +11,8 @@ import { DragManager } from '../../../util/DragManager';
import { DocumentView } from '../DocumentView';
import './SchemaCSVPopUp.scss';
-interface SchemaCSVPopUpProps {}
-
@observer
-export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+export class SchemaCSVPopUp extends React.Component<object> {
// eslint-disable-next-line no-use-before-define
static Instance: SchemaCSVPopUp;
@@ -23,7 +21,7 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
@observable public target: Doc | undefined = undefined;
@observable public visible: boolean = false;
- constructor(props: SchemaCSVPopUpProps) {
+ constructor(props: object) {
super(props);
makeObservable(this);
SchemaCSVPopUp.Instance = this;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 5a9442d2f..a7c4a00b0 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,27 +1,28 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorPicker, EditableText, IconButton, Size, Type } from '@dash/components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as d3 from 'd3';
-import { IReactionDisposer, action, computed, makeObservable, observable } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaFillDrip } from 'react-icons/fa';
import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
-import { listSpec } from '../../../../../fields/Schema';
-import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PinDocView } from '../../../PinFuncs';
+import { PinDocView, PinProps } from '../../../PinFuncs';
import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
import './Chart.scss';
+export interface HistogramData {
+ [key: string]: string | number;
+}
export interface HistogramProps {
- Document: Doc;
+ Doc: Doc;
layoutDoc: Doc;
axes: string[];
- titleCol: string;
- records: { [key: string]: any }[];
+ records: HistogramData[];
width: number;
height: number;
dataDoc: Doc;
@@ -39,64 +40,85 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _histogramRef: HTMLDivElement | null = null;
private _histogramSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- private numericalXData: boolean = false; // whether the data is organized by numbers rather than categoreis
- private numericalYData: boolean = false; // whether the y axis is controlled by provided data rather than frequency
- private maxBins = 15; // maximum number of bins that is readable on a normal sized doc
- @observable _currSelected: any | undefined = undefined; // Object of selected bar
- private curBarSelected: any = undefined; // histogram bin of selected bar for when just one bar is selected
- private selectedData: any[] = []; // array of selected bars
- private hoverOverData: any = undefined; // Selection of bar being hovered over
-
- constructor(props: any) {
+ private _numericalXData: boolean = false; // whether the data is organized by numbers rather than categoreis
+ private _numericalYData: boolean = false; // whether the y axis is controlled by provided data rather than frequency
+ private _maxBins = 15; // maximum number of bins that is readable on a normal sized doc
+ private _selectedBars: HistogramData[] = [];
+ @observable private _currSelected: { [key: string]: string | number; frequency: number } | undefined = undefined;
+
+ constructor(props: HistogramProps) {
super(props);
makeObservable(this);
}
+ @computed get xAxis() {
+ return this._props.axes[0];
+ }
+
+ @computed get yAxis() {
+ return this._props.axes[1];
+ }
+
+ @computed get Doc() {
+ return this._props.Doc;
+ }
+ @computed get layoutDoc() {
+ return this._props.layoutDoc;
+ }
+
@computed get _tableDataIds() {
return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
}
// returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
- @computed get _tableData() {
+
+ @computed get _tableData(): Record<string, string | number>[] {
return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
- // filters all data to just display selected data if brushed (created from an incoming link)
- @computed get _histogramData() {
+
+ @computed get _histogramData(): HistogramData[] {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
- const ax0 = this._props.axes[0];
- if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
- this.numericalXData = true;
+ if (!/[A-Za-z-:]/.test(this._props.records[0][this.xAxis] as string)) {
+ this._numericalXData = true;
}
- return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
+ return this._tableData.map(record => ({ [this.xAxis]: record[this.xAxis] }));
}
- const [ax0, ax1] = this._props.axes;
- if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
- this.numericalXData = true;
+ if (!/[A-Za-z-:]/.test(this._props.records[0][this.xAxis] as string)) {
+ this._numericalXData = true;
}
- if (!/[A-Za-z-:]/.test(this._props.records[0][ax1])) {
- this.numericalYData = true;
+ if (!/[A-Za-z-:]/.test(this._props.records[0][this.yAxis] as string)) {
+ this._numericalYData = true;
}
- return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
+ return this._tableData.map(record => ({
+ [this.xAxis]: record[this.xAxis],
+ [this.yAxis]: record[this.yAxis],
+ }));
}
- @computed get defaultGraphTitle() {
- const [ax0, ax1] = this._props.axes;
- if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
- return ax0 + ' Histogram';
+ @computed get defaultGraphTitle(): string {
+ if (!this.yAxis || !/\d/.test(this._props.records[0][this.yAxis] as string) || !this._numericalYData) {
+ return this.xAxis + ' Histogram';
}
- return ax0 + ' by ' + ax1 + ' Histogram';
+ return this.xAxis + ' by ' + this.yAxis + ' Histogram';
}
@computed get parentViz() {
- return DocCast(this._props.Document.dataViz_parentViz);
+ return DocCast(this._props.Doc.dataViz_parentViz);
+ }
+
+ @computed get defaultBarColor() {
+ return StrCast(this.layoutDoc.dataViz_histogram_defaultColor, '#69b3a2')!;
+ }
+ @computed get barColors() {
+ return StrListCast(this.layoutDoc.dataViz_histogram_barColors);
+ }
+ @computed get selectedBins() {
+ return NumListCast(this.layoutDoc.dataViz_histogram_selectedBins);
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
- if (this.numericalXData) {
- const data = this.data(this._histogramData);
- return { xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin: 0, yMax: 0 };
- }
- return { xMin: 0, xMax: 0, yMin: 0, yMax: 0 };
+ const data = this._numericalXData ? this.data(this._histogramData) : [0];
+ return { xMin: Math.min(...data), xMax: Math.max(...data), yMin: 0, yMax: 0 };
}
componentWillUnmount() {
@@ -104,21 +126,31 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
componentDidMount() {
// restore selected bars
- const svg = this._histogramSvg;
- if (svg) {
- const selectedDataBars = StrListCast(this._props.layoutDoc.dataViz_histogram_selectedData);
- svg.selectAll('rect').attr('class', (d: any) => {
- let selected = false;
- selectedDataBars.forEach(eachSelectedData => {
- if (d[0] === eachSelectedData) selected = true;
- });
- if (selected) {
- this.selectedData.push(d);
- return 'histogram-bar hover';
- }
- return 'histogram-bar';
- });
- }
+ this._histogramSvg?.selectAll('rect').attr('class', dIn => {
+ const d = dIn as HistogramData;
+ if (this.selectedBins?.some(selBin => d[0] === selBin)) {
+ this._selectedBars.push(d);
+ return 'histogram-bar hover';
+ }
+ return 'histogram-bar';
+ });
+ // setup filters to watch selections and filter toggle
+ this._disposers.selection = reaction(
+ () => ({ filter: this.layoutDoc.dataViz_filterSelection, hists: this._selectedBars.slice(), cur: this._currSelected }),
+ ({ filter, hists }) => {
+ this.layoutDoc.dataViz_selectedRows = !filter
+ ? undefined
+ : new List<number>(
+ this._tableDataIds.filter(rowID =>
+ hists.some(h => {
+ const val = this._props.records[rowID][this.xAxis];
+ return val == h.x0 || (+val >= +h.x0 && +val <= +h.x1);
+ })
+ )
+ );
+ },
+ { fireImmediately: true }
+ );
}
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
@@ -126,7 +158,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const anchor = Docs.Create.ConfigDocument({
title: 'histogram doc selection' + this._currSelected,
});
- PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
return anchor;
};
@@ -139,110 +171,92 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
// cleans data by converting numerical data to numbers and taking out empty cells
- data = (dataSet: any) => {
- const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key] as any)));
+ data = (dataSet: HistogramData[]): number[] => {
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key] as number)));
const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
return !field
? []
- : validData.map((d: { [x: string]: any }) =>
- !this.numericalXData //
- ? d[field]
- : +d[field!].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')
+ : validData.map(d =>
+ !this._numericalXData //
+ ? (d[field] as number)
+ : +d[field].toString().replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')
);
};
+ barLabel = (d: d3.Bin<number, number> | HistogramData) => '' + (Array.isArray(d) ? d[0] : d[0]);
+
// outlines the bar selected / hovered over
- highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => {
+ highlightSelectedBar = (changeSelectedVariables: boolean, svg: d3.Selection<SVGGElement, unknown, null, undefined>, eachRectWidth: number, pointerX: number, xAxisTitle: string, yAxisTitle: string, histDataSet: HistogramData[]) => {
let barCounter = -1;
- const selected = svg.selectAll('.histogram-bar').filter((d: any) => {
+ let hoverOverBar: HistogramData | undefined;
+ svg.selectAll('.histogram-bar').filter(dIn => {
+ const d = dIn as HistogramData;
barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over
- if (d.length && barCounter * eachRectWidth <= pointerX && pointerX <= (barCounter + 1) * eachRectWidth) {
- let showSelected = this.numericalYData
- ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') == d[0])[0]
- : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') == d[0])[0];
- if (this.numericalXData) {
- // calculating frequency
- if (d[0] && d[1] && d[0] !== d[1]) {
- showSelected = { [xAxisTitle]: d3.min(d) + ' to ' + d3.max(d), frequency: d.length };
- } else if (!this.numericalYData) showSelected = { [xAxisTitle]: showSelected[xAxisTitle], frequency: d.length };
- }
+ if (d.length && (barCounter * eachRectWidth <= pointerX + 1 || (!barCounter && pointerX <= 0)) && pointerX - 1 <= (barCounter + 1) * eachRectWidth) {
if (changeSelectedVariables) {
// for when a bar is selected - not just hovered over
- let sameAsAny = false;
- const selectedDataBars = Cast(this._props.layoutDoc.dataViz_histogram_selectedData, listSpec('number'), null);
- this.selectedData.forEach(eachData => {
- if (!sameAsAny) {
- let match = true;
- Object.keys(d).forEach(key => {
- if (d[key] !== eachData[key]) match = false;
- });
- if (match) {
- sameAsAny = true;
- const index = this.selectedData.indexOf(eachData);
- this.selectedData.splice(index, 1);
- selectedDataBars.splice(index, 1);
- this._currSelected = undefined;
- }
- }
- });
- if (!sameAsAny) {
- this.selectedData.push(d);
- selectedDataBars.push(d[0]);
- this._currSelected = this.selectedData.length > 1 ? undefined : showSelected;
+ const alreadySelected = this._selectedBars.findIndex(eachData => !Object.keys(d).some(key => d[key] !== eachData[key]));
+ if (alreadySelected !== -1) {
+ this._selectedBars.splice(alreadySelected, 1);
+ this.selectedBins?.splice(alreadySelected, 1);
+ } else {
+ this._selectedBars.push(d);
+ this.selectedBins?.push(d[0] as number);
}
+ const showSelectedLabel = (dataset: HistogramData[]) => {
+ const datum = dataset.lastElement();
+ const datumNum = datum as unknown as number[];
+ const showSelectedStart = this._numericalYData
+ ? this._histogramData.filter(data => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') == d[0])[0]
+ : histDataSet.filter(data => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') == d[0])[0];
- // for filtering child dataviz docs
- if (this._props.layoutDoc.dataViz_filterSelection) {
- const selectedRows = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
- this._tableDataIds.forEach(rowID => {
- let match = false;
- for (let i = 0; i < d.length; i++) {
- console.log('Compare: ' + this._props.records[rowID][xAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') + ' = ' + d[i]);
- if (this._props.records[rowID][xAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') == d[i]) match = true;
- }
- if (match && !selectedRows?.includes(rowID))
- selectedRows?.push(rowID); // adding to filtered rows
- else if (match && sameAsAny) selectedRows.splice(selectedRows.indexOf(rowID), 1); // removing from filtered rows
- });
- }
- } else this.hoverOverData = d;
+ const selectionLabel = dataset.length > 1
+ ? dataset.map(dd => this.barLabel(dd)).join('::')
+ : !this._numericalXData
+ ? this.barLabel(d)
+ : datum[0] !== undefined && datum[1] && datum[0] !== datum[1]
+ ? d3.min(datumNum) + ' to ' + d3.max(datumNum)
+ : !this._numericalYData
+ ? showSelectedStart?.[xAxisTitle]
+ : this.barLabel(d); // prettier-ignore
+ return { [xAxisTitle]: selectionLabel, frequency: dataset.length > 1 ? Number.NaN : datum.length } as { [key: string]: string | number; frequency: number };
+ };
+ this._currSelected = this._selectedBars.length ? showSelectedLabel(this._selectedBars) : undefined;
+ } else hoverOverBar = d;
return true;
}
return false;
});
- if (changeSelectedVariables) {
- if (this._currSelected) this.curBarSelected = selected;
- else this.curBarSelected = undefined;
- }
+ return hoverOverBar;
};
// draws the histogram
- drawChart = (dataSet: any, width: number, height: number) => {
+ drawChart = (dataSet: HistogramData[], width: number, height: number) => {
if (dataSet?.length <= 0) return;
d3.select(this._histogramRef).select('svg').remove();
d3.select(this._histogramRef).select('.tooltip').remove();
const data = this.data(dataSet);
const xAxisTitle = Object.keys(dataSet[0])[0];
- const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
+ const yAxisTitle = this._numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
const uniqueArr: unknown[] = [...new Set(data)];
- let numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
- let translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
- if (numBins > this.maxBins) numBins = this.maxBins;
- const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
- const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
+ let numBins = this._numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
+ let translateXAxis = !this._numericalXData || numBins < this._maxBins ? width / (numBins + 1) / 2 : 0;
+ if (numBins > this._maxBins) numBins = this._maxBins;
+ const startingPoint = this._numericalXData ? this.rangeVals.xMin! : 0;
+ const endingPoint = this._numericalXData ? this.rangeVals.xMax! : numBins;
// converts data into Objects
- let histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key] as any)));
- if (!this.numericalXData) {
- const histStringDataSet: { [x: string]: unknown }[] = [];
- if (this.numericalYData) {
+ let histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key] as number)));
+ if (!this._numericalXData) {
+ const histStringDataSet: { [x: string]: number }[] = [];
+ if (this._numericalYData) {
for (let i = 0; i < dataSet.length; i++) {
- histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle] });
+ histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle] as number, [xAxisTitle]: dataSet[i][xAxisTitle] as number });
}
} else {
for (let i = 0; i < uniqueArr.length; i++) {
- histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] });
+ histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] as number });
}
for (let i = 0; i < data.length; i++) {
const barData = histStringDataSet.filter(each => each[xAxisTitle] == data[i]);
@@ -263,12 +277,12 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.attr('transform', 'translate(' + this._props.margin.left + ',' + this._props.margin.top + ')'));
let x = d3
.scaleLinear()
- .domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
+ .domain(this._numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
.range([0, width]);
const histogram = d3
- .histogram()
+ .bin()
.value(d => d)
- .domain([startingPoint!, endingPoint!])
+ .domain([startingPoint, endingPoint])
.thresholds(x.ticks(numBins));
const bins = histogram(data);
let eachRectWidth = width / bins.length;
@@ -279,7 +293,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
// more calculations based on bins
// x-axis
- if (!this.numericalXData) {
+ if (!this._numericalXData) {
// reorganize to match data if the data is strings rather than numbers
// uniqueArr.sort()
histDataSet.sort();
@@ -294,9 +308,6 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
bins.pop();
eachRectWidth = width / bins.length;
- bins.forEach(d => {
- d.x0 = d.x0!;
- });
xAxis = d3
.axisBottom(x)
.ticks(bins.length > 1 ? bins.length - 1 : 1)
@@ -329,15 +340,15 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
x.range([0, width - eachRectWidth]);
}
// y-axis
- const maxFrequency = this.numericalYData ?
- d3.max(histDataSet, (d: any) => (d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '')
- .replace(/%/g, '').replace(/</g, '')) : 0)) :
+ const maxFrequency = this._numericalYData ?
+ d3.max(histDataSet, d => (d[yAxisTitle] ?
+ Number(StrCast(d[yAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')) : 0)) :
d3.max(bins, d => d.length); // prettier-ignore
const y = d3.scaleLinear().range([height, 0]);
- y.domain([0, +maxFrequency!]);
+ y.domain([0, maxFrequency ?? 0]);
const yAxis = d3.axisLeft(y).ticks(maxFrequency!);
- if (this.numericalYData) {
- const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0);
+ if (this._numericalYData) {
+ const yScale = scaleCreatorNumerical(0, maxFrequency ?? 0, height, 0);
yAxisCreator(svg.append('g'), width, yScale);
} else {
svg.append('g').call(yAxis);
@@ -347,29 +358,14 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.call(xAxis);
// click/hover
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const updateHighlights = (hoverOverBar?: HistogramData) => svg.selectAll('rect').attr('class', (d: any) => 'histogram-bar' + (hoverOverBar?.[0] == d[0] || this._selectedBars.some(hist => d[0] === hist[0]) ? ' hover' : ''));
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
- const onHover = action((e: any) => {
- this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
- // eslint-disable-next-line no-use-before-define
- updateHighlights();
- });
- const mouseOut = action(() => {
- this.hoverOverData = undefined;
- // eslint-disable-next-line no-use-before-define
- updateHighlights();
- });
- const updateHighlights = () => {
- const hoverOverBar = this.hoverOverData;
- const { selectedData } = this;
- svg.selectAll('rect').attr('class', (d: any) => {
- let selected = false;
- selectedData.forEach(eachSelectedData => {
- if (d[0] === eachSelectedData[0]) selected = true;
- });
- return (hoverOverBar && hoverOverBar[0] == d[0]) || selected ? 'histogram-bar hover' : 'histogram-bar';
- });
- };
- svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const mouseEnter = (e: any) => updateHighlights(this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
+ svg.on('click', onPointClick).on('pointerenter', mouseEnter).on('pointerleave', updateHighlights);
// axis titles
svg.append('text')
@@ -385,138 +381,54 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
d3.format('.0f');
// draw bars
- const selected = this.selectedData;
+ const selected = this._selectedBars;
svg.selectAll('rect')
.data(bins)
.enter()
.append('rect')
- .attr(
- 'transform',
- this.numericalYData
+ .attr('transform', this._numericalYData
? d => {
- const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] == d[0]);
+ const eachData = histDataSet.filter((hData: HistogramData) => hData[xAxisTitle] == d[0]);
const length = eachData.length ? StrCast(eachData[0][yAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
return 'translate(' + x(d.x0!) + ',' + y(Number(length)) + ')';
}
- : d => 'translate(' + x(d.x0!) + ',' + y(d.length) + ')'
- )
- .attr(
- 'height',
- this.numericalYData
+ : d => 'translate(' + x(d.x0!) + ',' + y(d.length) + ')')
+ .attr('height', this._numericalYData
? d => {
- const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] == d[0]);
+ const eachData = histDataSet.filter(hData => hData[xAxisTitle] == d[0]);
const length = eachData.length ? StrCast(eachData[0][yAxisTitle]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
- return height - y(Number(length));
+ return height - y(+length);
}
- : d => height - y(d.length)
- )
+ : d => height - y(d.length))
.attr('width', eachRectWidth)
- .attr('class', selected ? d => (selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar') : () => 'histogram-bar')
- .attr('fill', d => {
- let barColor;
- const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
- barColors.forEach(each => {
- // eslint-disable-next-line prefer-destructuring
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
- else {
- const range = StrCast(each[0]).split(' to ');
- // eslint-disable-next-line prefer-destructuring
- if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
- }
- });
- return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
- });
- };
-
- @action changeSelectedColor = (color: string) => {
- this.curBarSelected.attr('fill', color);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
-
- const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
- barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
- barColors.push(StrCast(barName + '::' + color));
+ .attr('class', selected ? d => (selected?.[0]?.x0 == d.x0 ? 'histogram-bar hover' : 'histogram-bar') : () => 'histogram-bar')
+ .attr('fill', d => this.barColors?.map(bar => bar.split('::')).find(([barLabel]) => barLabel === this.barLabel(d))?.[1] ?? this.defaultBarColor); // prettier-ignore
};
- @action eraseSelectedColor = () => {
- this.curBarSelected.attr('fill', this._props.layoutDoc.dataViz_histogram_defaultColor);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
-
- const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
- barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
- };
-
- // reloads the bar colors and selected bars
- updateSavedUI = () => {
- const svg = this._histogramSvg;
- if (svg) {
- // bar color
- svg.selectAll('rect').attr('fill', (d: any) => {
- let barColor;
- const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
- barColors.forEach(each => {
- // eslint-disable-next-line prefer-destructuring
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
- else {
- const range = StrCast(each[0]).split(' to ');
- // eslint-disable-next-line prefer-destructuring
- if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
- }
- });
- return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
- });
- }
+ @action changeSelectedColor = (color: string, erase?: boolean) => {
+ if (!this.barColors) this.layoutDoc.dataViz_histogram_barColors = new List<string>();
+ this._selectedBars.map(this.barLabel).forEach(barName => {
+ this.barColors.forEach(bar => bar.split('::')[0] === barName && this.barColors.splice(this.barColors.indexOf(bar), 1));
+ !erase && this.barColors.push(barName + '::' + color);
+ });
};
render() {
- this.updateSavedUI();
- this._histogramData;
- let curSelectedBarName = '';
- let titleAccessor: any = 'dataViz_histogram_title';
- if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
- if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
- if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
- if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
- if (!this._props.layoutDoc.dataViz_histogram_selectedData) this._props.layoutDoc.dataViz_histogram_selectedData = new List<string>();
- let selected = 'none';
- if (this._currSelected) {
- curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
- selected = '{ ';
- Object.keys(this._currSelected).forEach(key => {
- key //
- ? (selected += key + ': ' + this._currSelected[key] + ', ')
- : '';
- });
- selected = selected.substring(0, selected.length - 2) + ' }';
- if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
- selected += '\n' + this._props.titleCol + ': ';
- this._tableData.forEach(each => {
- if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
- if (this._props.axes[1]) {
- if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
- } else selected += each[this._props.titleCol] + ', ';
- }
- });
- selected = selected.slice(0, -1).slice(0, -1);
- }
- }
- let selectedBarColor;
- const barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
- barColors.forEach(each => {
- // eslint-disable-next-line prefer-destructuring
- each[0] === curSelectedBarName && (selectedBarColor = each[1]);
- });
+ if (!this.selectedBins) this.layoutDoc.dataViz_histogram_selectedBins = new List<string>();
+
+ const titleAccessor = 'dataViz_histogram_title ' + this.xAxis + (this.yAxis ? '-' + this._props.axes[1] : '');
+ const selected = !this._currSelected ? 'none' : '{ ' + Object.keys(this._currSelected).map(key => key ? key + ': ' + this._currSelected?.[key]:'').join(", ") + ' }'; // prettier-ignore
+ const curSelectedBarName = this._selectedBars.length && this.barLabel(this._selectedBars.lastElement()); //.[this.xAxis]).replace(/\$/g, '').replace(/%/g, '').replace(/</g, '');
+ const selectedBarColor = this.barColors?.map(bar => bar.split('::'))?.find(([barLabel]) => barLabel === curSelectedBarName)?.[1];
if (this._histogramData.length > 0 || !this.parentViz) {
return this._props.axes.length >= 1 ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
<div className="graph-title">
<EditableText
- val={StrCast(this._props.layoutDoc[titleAccessor])}
+ val={StrCast(this.layoutDoc[titleAccessor], this.defaultGraphTitle)}
setVal={undoable(
- action(val => {
- this._props.layoutDoc[titleAccessor] = val as string;
- }),
+ action(val => (this.layoutDoc[titleAccessor] = val)),
'Change Graph Title'
)}
color="black"
@@ -528,13 +440,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
tooltip="Change Default Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor)}
- setFinalColor={undoable(color => {
- this._props.layoutDoc.dataViz_histogram_defaultColor = color;
- }, 'Change Default Bar Color')}
- setSelectedColor={undoable(color => {
- this._props.layoutDoc.dataViz_histogram_defaultColor = color;
- }, 'Change Default Bar Color')}
+ selectedColor={this.defaultBarColor}
+ setFinalColor={undoable(color => (this.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+ setSelectedColor={undoable(color => (this.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
size={Size.XSMALL}
/>
</div>
@@ -552,9 +460,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
tooltip="Change Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={selectedBarColor || this.curBarSelected.attr('fill')}
- setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
- setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
+ selectedColor={selectedBarColor}
+ setFinalColor={undoable(this.changeSelectedColor, 'Change Selected Bar Color')}
+ setSelectedColor={undoable(this.changeSelectedColor, 'Change Selected Bar Color')}
size={Size.XSMALL}
/>
&nbsp;
@@ -564,7 +472,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
color="black"
type={Type.SEC}
tooltip="Revert to the default bar color" //
- onClick={undoable(this.eraseSelectedColor, 'Change Selected Bar Color')}
+ onClick={undoable(() => this.changeSelectedColor(this.defaultBarColor, true), 'Change Selected Bar Color')}
/>
</div>
) : null}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index b55d509ff..80fadf178 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -22,11 +22,11 @@ export interface SelectedDataPoint extends DataPoint {
}
export interface LineChartProps {
vizBox: DataVizBox;
- Document: Doc;
+ Doc: Doc;
layoutDoc: Doc;
axes: string[];
titleCol: string;
- records: { [key: string]: any }[];
+ records: { [key: string]: string }[];
width: number;
height: number;
dataDoc: Doc;
@@ -47,13 +47,13 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
@observable _currSelected: DataPoint | undefined = undefined;
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
- constructor(props: any) {
+ constructor(props: LineChartProps) {
super(props);
makeObservable(this);
}
@computed get titleAccessor() {
- let titleAccessor: any = 'dataViz_lineChart_title';
+ let titleAccessor = 'dataViz_lineChart_title';
if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
return titleAccessor;
@@ -74,12 +74,12 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
return this._props.axes[1] + ' vs. ' + this._props.axes[0] + ' Line Chart';
}
@computed get parentViz() {
- return DocCast(this._props.Document.dataViz_parentViz);
+ return DocCast(this._props.Doc.dataViz_parentViz);
}
@computed get incomingHighlited() {
// return selected x and y axes
// otherwise, use the selection of whatever is linked to us
- const incomingVizBox = DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
+ const incomingVizBox = this.parentViz && (DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox);
const highlitedRowIds = NumListCast(incomingVizBox?.layoutDoc?.dataViz_highlitedRows);
return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor
}
@@ -113,7 +113,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
//
title: 'line doc selection' + (this._currSelected?.x ?? ''),
});
- PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
return anchor;
};
@@ -170,8 +170,8 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
});
if (!ptWasSelected) {
- selectedDatapoints.push(d.x + ',' + d.y);
- this._currSelected = selectedDatapoints.length > 1 ? undefined : d;
+ selectedDatapoints?.push(d.x + ',' + d.y);
+ this._currSelected = (selectedDatapoints?.length ?? 0 > 1) ? undefined : d;
}
// for filtering child dataviz docs
@@ -190,7 +190,14 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
}
- drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>, higlightFocusPt: any, tooltip: any) {
+ drawDataPoints(
+ data: DataPoint[], //
+ idx: number,
+ xScale: d3.ScaleLinear<number, number, never>,
+ yScale: d3.ScaleLinear<number, number, never>,
+ higlightFocusPt: d3.Selection<SVGGElement, unknown, null, undefined>,
+ tooltip: d3.Selection<HTMLDivElement, unknown, null, undefined>
+ ) {
if (this._lineChartSvg) {
const circleClass = '.circle-' + idx;
this._lineChartSvg
@@ -211,7 +218,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.on('mouseleave', () => {
tooltip?.transition().duration(300).style('opacity', 0);
})
- .on('click', (e: any) => {
+ .on('click', e => {
const d0 = { x: Number(e.target.getAttribute('data-x')), y: Number(e.target.getAttribute('data-y')) };
// find .circle-d1 with data-x = d0.x and data-y = d0.y
this.setCurrSelected(d0);
@@ -220,7 +227,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
}
- drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
+ drawChart = (dataSet: { x: number; y: number }[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
// clearing tooltip and the current chart
d3.select(this._lineChartRef).select('svg').remove();
d3.select(this._lineChartRef).select('.tooltip').remove();
@@ -277,7 +284,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
svg.append('path').attr('stroke', 'red');
// legend
- const color: any = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+ const color = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
svg.selectAll('mydots')
.data([this._props.axes[1], this._props.axes[2]])
.enter()
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 86e6ad8e4..0ae70786f 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -16,7 +16,7 @@ import { PinProps, PinDocView } from '../../../PinFuncs';
import './Chart.scss';
export interface PieChartProps {
- Document: Doc;
+ Doc: Doc;
layoutDoc: Doc;
axes: string[];
titleCol: string;
@@ -83,7 +83,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
@computed get parentViz() {
- return DocCast(this._props.Document.dataViz_parentViz);
+ return DocCast(this._props.Doc.dataViz_parentViz);
}
componentWillUnmount() {
@@ -114,7 +114,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
//
title: 'piechart doc selection' + this._currSelected,
});
- PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
return anchor;
};
@@ -169,7 +169,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// inside the slice of it crosses an odd number of edges
const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
let key = 'data'; // key that represents slice
- // eslint-disable-next-line prefer-destructuring
if (Object.keys(showSelected)[0] === 'frequency') key = Object.keys(showSelected)[1];
if (changeSelectedVariables) {
let sameAsAny = false;
@@ -296,7 +295,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
try {
dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
- } catch (error) {
+ } catch {
/* empty */
}
possibleDataPointVals.push(dataPointVal);
@@ -306,7 +305,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// to make sure all important slice information is on 'd' object
let addKey: any = false;
if (pieDataSet.length && Object.keys(pieDataSet[0])[0] === 'frequency') {
- // eslint-disable-next-line prefer-destructuring
addKey = Object.keys(pieDataSet[0])[1];
}
arcs.append('path')
@@ -324,7 +322,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
const sliceTitle = dataPoint[this._props.axes[0]];
const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
sliceColors.forEach(each => {
- // eslint-disable-next-line prefer-destructuring
each[0] === accessByName && (sliceColor = each[1]);
});
}
@@ -337,7 +334,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
});
return selectThisData ? 'slice hover' : 'slice';
})
- // @ts-ignore
+ // @ts-expect-error types don't match
.attr('d', arc)
.on('click', onPointClick)
.on('mouseover', onHover)
@@ -388,7 +385,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- let titleAccessor: any = 'dataViz_pie_title';
+ let titleAccessor = 'dataViz_pie_title';
if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
@@ -420,7 +417,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
let selectedSliceColor;
const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
sliceColors.forEach(each => {
- // eslint-disable-next-line prefer-destructuring
if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
});
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 7ef4bca6b..ad2731109 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -5,7 +5,6 @@ import * as React from 'react';
import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
import { emptyFunction } from '../../../../../Utils';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
-import { DocData } from '../../../../../fields/DocSymbols';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast } from '../../../../../fields/Types';
@@ -20,7 +19,7 @@ import './Chart.scss';
const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
interface TableBoxProps {
- Document: Doc;
+ Doc: Doc;
layoutDoc: Doc;
records: { [key: string]: unknown }[];
selectAxes: (axes: string[]) => void;
@@ -82,7 +81,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}
@computed get parentViz() {
- return DocCast(this._props.Document.dataViz_parentViz);
+ return DocCast(this._props.Doc.dataViz_parentViz);
}
@computed get columns() {
@@ -129,7 +128,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
selected.splice(selected.indexOf(rowId), 1);
} else selected?.push(rowId);
e.stopPropagation();
- this.hasRowsToFilter = selected.length > 0;
+ this.hasRowsToFilter = (selected?.length ?? 0) > 0;
};
columnPointerDown = (e: React.PointerEvent, col: string) => {
@@ -140,28 +139,28 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
e,
moveEv => {
// dragging off a column to create a brushed DataVizBox
- const sourceAnchorCreator = () => this._props.docView?.()?.Document || this._props.Document;
+ const sourceAnchorCreator = () => this._props.docView?.()?.Document || this._props.Doc;
const targetCreator = (annotationOn: Doc | undefined) => {
const doc = this._props.docView?.()?.Document;
if (doc) {
const embedding = Doc.MakeEmbedding(doc);
embedding._dataViz = DataVizView.TABLE;
embedding._dataViz_axes = new List<string>([col]);
- embedding._dataViz_parentViz = this._props.Document;
+ embedding._dataViz_parentViz = this._props.Doc;
embedding.annotationOn = annotationOn;
embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
embedding.defaultHistogramColor = this._props.layoutDoc.defaultHistogramColor;
embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
return embedding;
}
- return this._props.Document;
+ return this._props.Doc;
};
if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
dragComplete: completeEv => {
if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
- completeEv.linkDocument[DocData].link_matchEmbeddings = true;
- completeEv.linkDocument[DocData].stroke_startMarker = true;
+ completeEv.linkDocument.$link_matchEmbeddings = true;
+ completeEv.linkDocument.$stroke_startMarker = true;
this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument);
}
},
diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx
index e77cde431..7cfccf0dc 100644
--- a/src/client/views/nodes/DiagramBox.tsx
+++ b/src/client/views/nodes/DiagramBox.tsx
@@ -3,7 +3,6 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { Cast, DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { Gestures } from '../../../pen-gestures/GestureTypes';
@@ -18,6 +17,7 @@ import { InkingStroke } from '../InkingStroke';
import './DiagramBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { Tooltip } from '@mui/material';
/**
* this is a class for the diagram box doc type that can be found in the tools section of the side bar
*/
@@ -45,7 +45,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable _errorMessage = '';
@computed get mermaidcode() {
- return StrCast(this.Document[DocData].text, RTFCast(this.Document[DocData].text)?.Text);
+ return StrCast(this.Document.$text, RTFCast(this.Document.$text)?.Text);
}
componentDidMount() {
@@ -88,7 +88,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
setMermaidCode = undoable((res: string) => {
- this.Document[DocData].text = new RichTextField(
+ this.Document.$text = new RichTextField(
JSON.stringify({
doc: {
type: 'doc',
@@ -185,11 +185,6 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return '( )';
};
- /**
- * This stops scroll wheel events when they are used to scroll the face collection.
- */
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
-
render() {
return (
<div
@@ -197,18 +192,15 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
style={{
pointerEvents: this._props.isContentActive() ? undefined : 'none',
}}
- ref={action((ele: HTMLDivElement | null) => {
- this._boxRef?.removeEventListener('wheel', this.onPassiveWheel);
- this._boxRef = ele;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
- })}>
+ ref={r => this.fixWheelEvents(r, this._props.isContentActive)}>
<div className="DIYNodeBox-searchbar">
<input type="text" value={this._inputValue} onKeyDown={action(e => e.key === 'Enter' && this.generateMermaidCode())} onChange={action(e => (this._inputValue = e.target.value))} />
<button type="button" onClick={this.generateMermaidCode}>
Gen
</button>
- <input type="checkbox" onClick={action(() => (this._showCode = !this._showCode))} />
+ <Tooltip title="show diagram code">
+ <input type="checkbox" onClick={action(() => (this._showCode = !this._showCode))} />
+ </Tooltip>
</div>
<div className="DIYNodeBox-content">
{this._showCode ? (
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index aab8a183a..504c1491e 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;
}
@@ -23,7 +98,6 @@ interface HTMLtagProps {
htmltag: string;
onClick?: ScriptField;
onInput?: ScriptField;
- scaling: number;
children?: JSX.Element[];
}
@@ -43,7 +117,7 @@ interface HTMLtagProps {
export class HTMLtag extends React.Component<HTMLtagProps> {
click = () => {
const clickScript = this.props.onClick as Opt<ScriptField>;
- clickScript?.script.run({ this: this.props.Document, scale: this.props.scaling });
+ clickScript?.script.run({ this: this.props.Document });
};
onInput = (e: React.FormEvent<unknown>) => {
const onInputScript = this.props.onInput as Opt<ScriptField>;
@@ -56,7 +130,6 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
'dragStarting',
'dragEnding',
'htmltag',
- 'scaling',
'Document',
'key',
'onInput',
@@ -65,7 +138,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
]).omit;
const replacer = (match: string, expr: string) =>
// bcz: this executes a script to convert a property expression string: { script } into a value
- (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+ (ScriptField.MakeFunction(expr, { this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '';
Object.keys(divKeys).forEach((prop: string) => {
const p = (this.props as unknown as { [key: string]: string })[prop] as string;
style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
@@ -79,7 +152,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
}
-export interface DocumentContentsViewProps extends FieldViewProps {
+interface DocumentContentsViewProps extends DocumentViewProps, FieldViewProps {
layoutFieldKey: string;
}
@observer
@@ -115,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);
- return Doc.expandTemplateLayout(template, this._props.Document);
+ Doc.LayoutDoc(this._props.Document, DocCast(this._props.Document[this._props.layoutFieldKey]));
+ return Doc.expandTemplateLayout(template, this._props.Document, this._props.layoutFieldKey);
}
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,
@@ -148,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
};
return {
props: {
- ...OmitKeys(list, [...docOnlyProps], '').omit,
+ ...OmitKeys(list, DocOnlyProps, '').omit,
} as BindingProps,
};
}
@@ -166,12 +221,11 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
- const replacer2 = (match: string, p1: string) => `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
+ const replacer2 = (match: string, p1: string) => `<HTMLtag Document={props.Document} htmltag='${p1}'`;
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
// replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag>
const replacer3 = (/* match: any, p1: string, offset: any, string: any */) => `</HTMLtag`;
-
layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
// add onClick function to props
@@ -181,7 +235,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] });
layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
- return ScriptField.MakeScript(script, { this: Doc.name, scale: 'number', value: 'string' });
+ return ScriptField.MakeScript(script, { this: Doc.name, value: 'string' });
}
return undefined;
// add input function to props
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 a514ee04e..284014e54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,7 +9,7 @@ import { Fade, JackInTheBox } from 'react-awesome-reveal';
import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, returnVal, simMouseEvent, simulateMouseClick } from '../../../ClientUtils';
import { Utils, emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
-import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -40,53 +40,22 @@ import { FieldsDropdown } from '../FieldsDropdown';
import { ObserverJsxParser } from '../ObservableReactComponent';
import { PinProps } from '../PinFuncs';
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';
import { PresEffect, PresEffectDirection } from './trails/PresEnums';
import SpringAnimation from './trails/SlideEffect';
import { SpringType, springMappings } from './trails/SpringUtils';
-import { TagsView } from '../TagsView';
-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 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.layoutDoc, 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
@@ -200,9 +169,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
componentDidMount() {
- runInAction(() => {
- this._mounted = true;
- });
+ runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
() =>
@@ -214,16 +181,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
? true
: undefined,
- active => {
- this._isContentActive = active;
- },
+ active => (this._isContentActive = active),
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
() => this.style(this.Document, StyleProp.PointerEvents) as Property.PointerEvents | undefined,
- pointerevents => {
- this._pointerEvents = pointerevents;
- },
+ pointerevents => (this._pointerEvents = pointerevents),
{ fireImmediately: true }
);
}
@@ -336,7 +299,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
const clickFunc = this.onClickFunc?.()?.script ? () => (this.onClickFunc?.()?.script.run(scriptProps, console.log).result as Opt<{ select: boolean }>)?.select && this._props.select(false) : undefined;
if (!clickFunc) {
- // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ // onDragStart implies a button doc that we don't want to select when clicking.
if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false;
preventDefault = false;
}
@@ -426,7 +389,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
noOnClick = undoable(() => {
this.Document.ignoreClick = false;
- this.Document.onClick = this.Document[DocData].onClick = undefined;
+ this.Document.onClick = this.Document.$onClick = undefined;
}, 'default on click');
deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
@@ -533,7 +496,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const items = this._props.styleProvider?.(this.Document, this._props, StyleProp.ContextMenuItems) as ContextMenuProps[];
items?.forEach(item => ContextMenu.Instance.addItem(item));
- const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []);
+ const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), [])!;
StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
@@ -554,7 +517,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
}
appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'map-pin' });
- appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
+ this._componentView?.componentAIView?.() && appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
!Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
@@ -567,9 +530,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
zorderItems.push({
description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
event: undoable(
- action(() => {
- this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged;
- }),
+ action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged)),
'set zIndex drag'
),
icon: 'hand-point-up',
@@ -691,10 +652,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
this._props
.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;
+ .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
+ 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
+ setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view));
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
@@ -719,12 +680,12 @@ 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;
+ const x = NumCast(this.Document._height) / viewXfScale / 80;
const xscale = x >= 1 ? 0 : 1 / (1 + x * (viewXfScale - 1));
- const y = NumCast(this.Document.width) / viewXfScale / 200;
+ const y = NumCast(this.Document._width) / viewXfScale / 200;
const yscale = y >= 1 ? 0 : 1 / (1 + y * viewXfScale - 1);
return Math.max(xscale, yscale, 1 / viewXfScale);
}
@@ -733,16 +694,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);
+ @computed get aiEditor() {
+ return (
+ <>
+ <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);
+ viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
+ _contentsRef = React.createRef<DocumentContentsView>();
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -753,17 +766,18 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
className="documentView-contentsView"
style={{
pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
- width: this._props.showAIEditor ? this.aiContentsWidth() : undefined,
- height: this._props.showAIEditor ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+ width: this.viewingAiEditor() ? this.aiContentsWidth() : undefined,
+ height: this.viewingAiEditor() ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView
{...this._props}
+ ref={this._contentsRef}
layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
pointerEvents={this.contentPointerEvents}
setContentViewBox={this.setContentView}
childFilters={this.childFilters}
- PanelWidth={this._props.showAIEditor ? this.aiContentsWidth : this._props.PanelWidth}
- PanelHeight={this._props.showAIEditor ? this.aiContentsHeight : this.panelHeight}
+ PanelWidth={this.viewingAiEditor() ? this.aiContentsWidth : this._props.PanelWidth}
+ PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight}
setHeight={this.setHeight}
isContentActive={this.isContentActive}
ScreenToLocalTransform={this.screenToLocalContent}
@@ -773,43 +787,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.viewingAiEditor() ?? this.tagsOverlayFunc()}
+ {this.widgetOverlayFunc()}
</>
);
}
@@ -827,11 +806,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
fieldsDropdown = (placeholder: string) => (
<div
- ref={r => { r && runInAction(() => (this._titleDropDownInnerWidth = DivWidth(r)));}} // prettier-ignore
- onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
+ ref={action((r:HTMLDivElement|null) => r && (this._titleDropDownInnerWidth = DivWidth(r)))} // prettier-ignore
+ onPointerDown={action(() => (this._changingTitleField = true))}
style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
<FieldsDropdown
- Document={this.Document}
+ Doc={this.Document}
placeholder={placeholder}
selectFunc={action((field: string | number) => {
if (this.layoutDoc.layout_showTitle) {
@@ -841,7 +820,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
this._changingTitleField = false;
})}
- menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+ menuClose={action(() => (this._changingTitleField = false))}
/>
</div>
);
@@ -857,7 +836,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const background = StrCast(
this.layoutDoc.layout_headingColor,
// StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor,
- StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)
+ StrCast(Doc.SharingDoc()?.headingColor, SnappingManager.userBackgroundColor)
// )
);
const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
@@ -1043,7 +1022,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
root: Doc
) {
const effectDirection = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection;
- const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null));
+ const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null) ?? null);
const effectProps = {
left: effectDirection === PresEffectDirection.Left,
right: effectDirection === PresEffectDirection.Right,
@@ -1158,7 +1137,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public static GetDocImage(doc?: Doc) {
return DocumentView.getDocumentView(doc)
?.ComponentView?.updateIcon?.()
- .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutFieldKey(doc!)])));
+ .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutDataKey(doc!)])));
}
public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore
@@ -1176,7 +1155,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public static UniquifyId(inLightbox: boolean | undefined, id: string) {
return (inLightbox ? 'lightbox-' : '') + id;
}
- public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
+ public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), 'D' + Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
public DocUniqueId = DocumentView.UniquifyId(DocumentView.LightboxContains(this), this.Document[Id]);
constructor(props: DocumentViewProps) {
@@ -1194,6 +1173,25 @@ 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;
+
+ /**
+ * Tests whether the component Doc being rendered matches the Doc that this view thinks its rendering.
+ * When switching the layout_fieldKey, component views may react before the internal DocumentView has re-rendered.
+ * If this happens, the component view may try to write invalid data, such as when expanding a template (which
+ * depends on the DocumentView being in synch with the subcomponents).
+ * @param renderDoc sub-component Doc being rendered
+ * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering
+ */
+ IsInvalid = (renderDoc?: Doc): boolean => {
+ const docContents = this._docViewInternal?._contentsRef.current;
+ return !(
+ (!renderDoc ||
+ (docContents?.layoutDoc === renderDoc && //
+ !this.docViewPath().some(dv => dv.IsInvalid()))) &&
+ docContents?._props.layoutFieldKey === this.Document.layout_fieldKey
+ );
+ };
@computed get showTags() {
return this.Document._layout_showTags || this._props.showTags;
@@ -1210,7 +1208,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 = [DocumentType.RTF, DocumentType.JOURNAL].includes(this.Document.type as DocumentType) ? 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);
@@ -1264,16 +1262,19 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
!BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this);
}
- public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore
+ public set IsSelected(val) { runInAction(() => (this._selected = val)) } // prettier-ignore
public get IsSelected() { return this._selected; } // prettier-ignore
public get IsContentActive(){ return this._docViewInternal?.isContentActive(); } // prettier-ignore
public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore
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
+ public get HasAIEditor() { return !!this._docViewInternal?._componentView?.componentAIView?.(); } // prettier-ignore
get LayoutFieldKey() {
- return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
+ return Doc.LayoutDataKey(this.Document, this._props.LayoutTemplateString);
}
@computed get layout_fitWidth() {
@@ -1327,10 +1328,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
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) {
+ public iconify = action((finished?: () => void, animateTime?: number) => {
this.ComponentView?.updateIcon?.();
const animTime = this._docViewInternal?.animateScaleTime();
- runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore
+ this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime);
const finalFinished = action(() => {
finished?.();
this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
@@ -1340,15 +1341,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this.switchViews(true, 'icon', finalFinished);
if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', '');
} else {
- const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
+ const deiconifyLayout = StrCast(this.Document.deiconifyLayout);
this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true);
this.Document.deiconifyLayout = undefined;
this._props.bringToFront?.(this.Document);
}
- }
+ });
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 +1361,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)),
});
- 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:
}
@@ -1416,14 +1417,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
tempDoc = view.Document;
MakeTemplate(tempDoc);
Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
- Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
- tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc);
+ Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
+ DocCast(Doc.UserDoc().template_user) && tempDoc && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', tempDoc);
} else {
- tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
- if (!tempDoc) {
- tempDoc = view.Document;
- while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto);
- }
+ tempDoc = DocCast(Doc.LayoutField(view.Document));
}
}
Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
@@ -1445,10 +1442,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true);
else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
};
- public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+ public switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
const batch = UndoManager.StartBatch('switchView:' + view);
// shrink doc first..
- runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore
+ this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1);
setTimeout(
action(() => {
if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1468,7 +1465,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}),
Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
);
- };
+ });
/**
* @returns a hierarchy path through the nested DocumentViews that display this view. The last element of the path is this view.
*/
@@ -1521,7 +1518,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
ref={r => {
const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
if (r && val !== this._enableHtmlOverlayTransitions) {
- setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore
+ setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
}
}}
style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
@@ -1548,12 +1545,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
<div
id={this.ViewGuid}
className="contentFittingDocumentView"
- onPointerEnter={action(() => {
- this._isHovering = true;
- })}
- onPointerLeave={action(() => {
- this._isHovering = false;
- })}>
+ onPointerEnter={action(() => (this._isHovering = true))} //
+ onPointerLeave={action(() => (this._isHovering = false))}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
@@ -1581,9 +1574,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToContentsTransform}
focus={this._props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => {
- r && (this._docViewInternal = r);
- })}
+ ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
/>
{this.htmlOverlay()}
{this.ComponentView?.infoUI?.()}
@@ -1627,7 +1618,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
} else {
- const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
+ const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc))!;
const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
options.toggleTarget = undefined;
DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
@@ -1706,7 +1697,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
- const collectedLinks = DocListCast(linkCollection[DocData].data);
+ const collectedLinks = DocListCast(linkCollection.$data);
let wid = NumCast(linkSource._width);
let embedding: Doc | undefined;
const links = Doc.Links(linkSource);
@@ -1729,7 +1720,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
const tag = StrCast(collection.title).split('-->')[1];
const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
- const collectionDocs = DocListCast(collection[DocData].data).concat(collection);
+ const collectionDocs = DocListCast(collection.$data).concat(collection);
let wid = 100;
let created = false;
const matchedDocs = matchedTags
@@ -1749,6 +1740,6 @@ ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
return aset;
}, new Set<Doc>());
- created && (collection[DocData].data = new List<Doc>(Array.from(matchedDocs)));
+ created && (collection.$data = new List<Doc>(Array.from(matchedDocs)));
return true;
});
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index dcc6e27ed..3cacb6692 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,7 +1,6 @@
import { action, computed, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { DocUtils } from '../../documents/DocUtils';
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 2e40f39ed..a6872f8dc 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -6,15 +6,17 @@ import { DateField } from '../../../fields/DateField';
import { Doc, Field, FieldType, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ScriptField } from '../../../fields/ScriptField';
+import { WebField } from '../../../fields/URLField';
+import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
import { Transform } from '../../util/Transform';
+import { ContextMenuProps } from '../ContextMenuItem';
import { PinProps } from '../PinFuncs';
import { ViewBoxInterface } from '../ViewBoxInterface';
import { DocumentView } from './DocumentView';
import { FocusViewOptions } from './FocusViewOptions';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { OpenWhere } from './OpenWhere';
-import { WebField } from '../../../fields/URLField';
-import { ContextMenuProps } from '../ContextMenuItem';
export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
export type StyleProviderFuncType = (
@@ -47,11 +49,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;
@@ -61,14 +61,15 @@ 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
isGroupActive?: () => string | undefined; // is this document part of a group that is active
// eslint-disable-next-line no-use-before-define
setContentViewBox?: (view: ViewBoxInterface<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
-
+ rejectDrop?: (de: DragManager.DropEvent, subView?: DocumentView) => boolean; // whether a document drop is rejected
PanelWidth: () => number;
PanelHeight: () => number;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
@@ -79,14 +80,12 @@ export interface FieldViewSharedProps {
styleProvider: Opt<StyleProviderFuncType>;
setTitleFocus?: () => void;
focus: FocusFuncType;
- onClickScript?: () => ScriptField;
- onDoubleClickScript?: () => ScriptField;
+ onClickScript?: () => ScriptField | undefined;
+ onDoubleClickScript?: () => ScriptField | undefined;
onPointerDownScript?: () => ScriptField;
onPointerUpScript?: () => ScriptField;
- // eslint-disable-next-line no-use-before-define
- onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+ 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 +101,6 @@ export interface FieldViewSharedProps {
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt<Property.PointerEvents>;
- suppressSetHeight?: boolean;
}
/**
@@ -110,13 +108,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..a3167ee06 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';
@@ -117,10 +117,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
default: type = 'slider';
break;
} // prettier-ignore
- const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
+ const numScript = (value?: number) => ScriptCast(this.Document.script)?.script.run({ this: this.Document, value, _readOnly_: value === undefined });
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
// Script for checking the outcome of the toggle
- const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
+ const checkResult = Number(Number(numScript()?.result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
return (
<NumberDropdown
@@ -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
/>
@@ -207,6 +207,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
*/
@computed get dropdownListButton() {
const script = ScriptCast(this.Document.script);
+ if (!script) return null;
const selectedFunc = () => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
const { buttonList, selectedVal, getStyle, jsx, toolTip } = (() => {
switch (this.Document.title) {
@@ -252,7 +253,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 +273,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 +290,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));
+ 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} />,
@@ -313,7 +317,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
// it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define
// arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript)
const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val));
- itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, value: toggleStatus, itemDoc, _readOnly_: false }));
+ itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick)?.script.run({ this: itemDoc, _added_: added, value: toggleStatus, itemDoc, _readOnly_: false }));
}}
/>
);
@@ -339,11 +343,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 +409,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/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 91c351895..8e4b64851 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -2,10 +2,11 @@ import functionPlot, { Chart } from 'function-plot';
import { action, computed, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, NumListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast } from '../../../fields/Types';
+import { StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { DocUtils } from '../../documents/DocUtils';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -15,8 +16,6 @@ import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { FieldView, FieldViewProps } from './FieldView';
-import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -76,7 +75,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
this._plotEle = ele || this._plotEle;
const width = this._props.PanelWidth();
const height = this._props.PanelHeight();
- const xrange = Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]);
+ const xrange = NumListCast(this.layoutDoc.xRange, [-10, 10])!;
try {
this._plotEle?.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
this._plot = functionPlot({
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..0bbd6a0d3 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.LayoutDataKey(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..9f7a5d03f 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 {
@@ -245,3 +241,34 @@
color: black;
}
}
+.imageBox-regenerate-dialog {
+ position: absolute;
+ background: white;
+ padding: 10px;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+ z-index: 10000;
+
+ h3 {
+ margin-top: 0;
+ }
+
+ input {
+ width: 300px;
+ padding: 8px;
+ margin-bottom: 10px;
+ }
+
+ .buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+
+ .generate-btn {
+ background: #0078d4;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ }
+ }
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 017ef7191..d16baada6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,8 +1,8 @@
-import { Button, Colors, Size, Type } from '@dash/components';
+import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Slider, Tooltip } from '@mui/material';
import axios from 'axios';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
@@ -13,11 +13,12 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
+import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { Upload } from '../../../server/SharedMediaTypes';
import { emptyFunction } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -26,7 +27,7 @@ import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
-import { undoable, undoBatch } from '../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
@@ -45,8 +46,8 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
-import { RichTextField } from '../../../fields/RichTextField';
+const DefaultPath = '/assets/unknown-file-icon-hi.png';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
private static _instance: ImageEditorData;
@@ -103,6 +104,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable private _regenerateLoading = false;
@observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];
+ // Add these observable properties to the ImageBox class
+ @observable private _outpaintingInProgress = false;
+ @observable private _outpaintingPrompt = '';
+
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
@@ -146,11 +151,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) }),
- ({ nativeSize, width }) => {
- if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !this.layoutDoc._height) {
+ () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
+ ({ nativeSize, width, height }) => {
+ if (!this.layoutDoc._layout_nativeDimEditable || !height) {
this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
}
},
@@ -166,6 +170,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
},
{ fireImmediately: true }
);
+ this._disposers.outpaint = reaction(
+ () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey,
+ complete => complete && this.openOutpaintPrompt(),
+ { fireImmediately: true }
+ );
}
componentWillUnmount() {
@@ -200,7 +209,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
drop = undoable(
action((e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
+ if (de.complete.docDragData && !this._props.rejectDrop?.(de, this.DocumentView?.())) {
let added: boolean | undefined;
const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => {
if (!ele) return false;
@@ -210,19 +219,21 @@ 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 dragField = drag[Doc.LayoutDataKey(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];
- const targetField = Doc.LayoutFieldKey(layoutDoc);
+ const targetField = Doc.LayoutDataKey(layoutDoc);
const targetDoc = layoutDoc[DocData];
if (targetDoc[targetField] instanceof ImageField) {
added = true;
@@ -252,17 +263,17 @@ 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;
doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth;
- if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) {
+ if (!RTFCast(doc[Doc.LayoutDataKey(doc)])) {
doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth;
doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth;
}
@@ -284,40 +295,38 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
- const regionData = region[DocData];
- regionData.lockedPosition = true;
- regionData.title = 'region:' + this.Document.title;
- regionData.followLinkToggle = true;
+ region.$lockedPosition = true;
+ region.$title = 'region:' + this.Document.title;
+ region.$followLinkToggle = true;
this.addDocument(region);
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
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;
+ const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw;
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;
- const croppingProto = cropping[DocData];
- croppingProto.annotationOn = undefined;
- croppingProto.isDataDoc = true;
- croppingProto.backgroundColor = undefined;
- croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
- croppingProto.type = DocumentType.IMG;
- croppingProto.layout = ImageBox.LayoutString('data');
- croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto.data_nativeWidth = anchw;
- croppingProto.data_nativeHeight = anchh;
- croppingProto.freeform_scale = viewScale;
- croppingProto.freeform_panX = anchx / viewScale;
- croppingProto.freeform_panY = anchy / viewScale;
- croppingProto.freeform_scale_min = viewScale;
- croppingProto.freeform_panX_min = anchx / viewScale;
- croppingProto.freeform_panX_max = anchw / viewScale;
- croppingProto.freeform_panY_min = anchy / viewScale;
- croppingProto.freeform_panY_max = anchh / viewScale;
+ cropping.$title = 'crop: ' + this.Document.title;
+ cropping.$annotationOn = undefined;
+ cropping.$isDataDoc = true;
+ cropping.$backgroundColor = undefined;
+ cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ cropping.$type = DocumentType.IMG;
+ cropping.$layout = ImageBox.LayoutString('data');
+ cropping.$data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
+ cropping.$data_nativeWidth = anchw;
+ cropping.$data_nativeHeight = anchh;
+ cropping.$freeform_scale = viewScale;
+ cropping.$freeform_panX = anchx / viewScale;
+ cropping.$freeform_panY = anchy / viewScale;
+ cropping.$freeform_scale_min = viewScale;
+ cropping.$freeform_panX_min = anchx / viewScale;
+ cropping.$freeform_panX_max = anchw / viewScale;
+ cropping.$freeform_panY_min = anchy / viewScale;
+ cropping.$freeform_panY_max = anchh / viewScale;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
@@ -339,6 +348,228 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
});
+ @observable _showOutpaintPrompt: boolean = false;
+ @observable _outpaintPromptInput: string = 'Extend this image naturally with matching content';
+
+ @action
+ openOutpaintPrompt = () => {
+ this._outpaintVAlign = '';
+ this._outpaintAlign = '';
+ this._showOutpaintPrompt = true;
+ };
+
+ @action
+ closeOutpaintPrompt = () => {
+ this._showOutpaintPrompt = false;
+ };
+
+ @action
+ cancelOutpaintPrompt = () => {
+ const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
+ const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
+ this.Document._width = origWidth;
+ this.Document._height = origHeight;
+ this._outpaintingInProgress = false;
+ this.closeOutpaintPrompt();
+ };
+
+ @action
+ handlePromptChange = (val: string | number) => {
+ this._outpaintPromptInput = '' + val;
+ };
+
+ @action
+ submitOutpaintPrompt = () => {
+ this.closeOutpaintPrompt();
+ this.processOutpaintingWithPrompt(this._outpaintPromptInput);
+ };
+
+ @action
+ processOutpaintingWithPrompt = async (customPrompt: string) => {
+ const field = Cast(this.dataDoc[this.fieldKey], ImageField);
+ if (!field) return;
+
+ // Set flag that outpainting is in progress
+ this._outpaintingInProgress = true;
+
+ // Revert dimensions if prompt is blank (acts like Cancel)
+ if (!customPrompt) {
+ this.cancelOutpaintPrompt();
+ return;
+ }
+
+ try {
+ const currentPath = this.choosePath(field.url);
+ const newWidth = NumCast(this.Document._width);
+ const newHeight = NumCast(this.Document._height);
+
+ // Optional: add loading indicator
+ const loadingOverlay = document.createElement('div');
+ loadingOverlay.style.position = 'absolute';
+ loadingOverlay.style.top = '0';
+ loadingOverlay.style.left = '0';
+ loadingOverlay.style.width = '100%';
+ loadingOverlay.style.height = '100%';
+ loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
+ loadingOverlay.style.display = 'flex';
+ loadingOverlay.style.justifyContent = 'center';
+ loadingOverlay.style.alignItems = 'center';
+ loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
+ this._mainCont?.appendChild(loadingOverlay);
+
+ const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
+ const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
+ const response = await Networking.PostToServer('/outpaintImage', {
+ imageUrl: currentPath,
+ prompt: customPrompt,
+ originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) },
+ newDimensions: { width: newWidth, height: newHeight },
+ halignment: this._outpaintAlign,
+ valignment: this._outpaintVAlign,
+ });
+
+ const error = ('error' in response && (response.error as string)) || '';
+ if (error.includes('Dropbox') && confirm('Outpaint image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
+ DrawingFillHandler.authorizeDropbox();
+ } else {
+ const batch = UndoManager.StartBatch('outpaint image');
+ if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
+ if (!this.dataDoc[this.fieldKey + '_alternates']) {
+ this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
+ }
+
+ const originalDoc = Docs.Create.ImageDocument(field.url.href, {
+ title: `Original: ${this.Document.title}`,
+ _nativeWidth: Doc.NativeWidth(this.dataDoc),
+ _nativeHeight: Doc.NativeHeight(this.dataDoc),
+ });
+
+ Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
+
+ // Replace with new outpainted image
+ this.dataDoc[this.fieldKey] = new ImageField(response.url);
+
+ Doc.SetNativeWidth(this.dataDoc, newWidth);
+ Doc.SetNativeHeight(this.dataDoc, newHeight);
+
+ this.Document.$ai = true;
+ this.Document.$ai_outpainted = true;
+ this.Document.$ai_outpaint_prompt = customPrompt;
+ this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined;
+ this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined;
+ } else {
+ this.cancelOutpaintPrompt();
+ alert('Failed to receive a valid image URL from server.');
+ }
+ batch.end();
+ }
+
+ this._mainCont?.removeChild(loadingOverlay);
+ } catch (error) {
+ this.cancelOutpaintPrompt();
+ alert('An error occurred while outpainting.' + error);
+ } finally {
+ runInAction(() => (this._outpaintingInProgress = false));
+ }
+ };
+
+ @observable _outpaintAlign = '';
+ @observable _outpaintVAlign = '';
+ @computed get outpaintVertical() {
+ return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight;
+ }
+
+ componentUI = (/* boundsLeft: number, boundsTop: number*/) =>
+ !this._showOutpaintPrompt ? null : (
+ <div
+ key="imageBox-componentui"
+ className="imageBox-regenerate-dialog"
+ style={{
+ top: -70 + (this._props.DocumentView?.().getBounds?.top ?? 0),
+ left: this._props.DocumentView?.().getBounds?.left ?? 0,
+ backgroundColor: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
+ }}>
+ <div style={{ position: 'absolute', top: 5, right: 5 }}>
+ <IconButton type={Type.TERT} onClick={this.cancelOutpaintPrompt} icon={<FontAwesomeIcon icon="times" color={'red'} />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
+ </div>
+ <div>Outpaint Image</div>
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
+ <EditableText
+ placeholder="Enter a prompt for extending the image:"
+ setVal={val => this.handlePromptChange(val)}
+ val={this._outpaintPromptInput}
+ type={Type.TERT}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
+ />
+ <div className="buttons" style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
+ <IconButton type={Type.TERT} onClick={this.submitOutpaintPrompt} icon={<AiOutlineSend />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} />
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
+ {this.outpaintVertical ? null : (
+ <Toggle
+ type={Type.TERT}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this._outpaintAlign === 'left' && this._outpaintVAlign === ''}
+ onClick={action(() => (this._outpaintAlign = 'left'))}
+ icon={<FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} />}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userVariantColor}
+ />
+ )}
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+ {!this.outpaintVertical ? null : (
+ <Toggle
+ type={Type.TERT}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'top'}
+ onClick={action(() => (this._outpaintVAlign = 'top'))}
+ icon={<FontAwesomeIcon icon="chevron-up" color={SnappingManager.userColor} />}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userColor}
+ />
+ )}
+ <Toggle
+ type={Type.TERT}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === ''}
+ onClick={action(() => {
+ this._outpaintAlign = '';
+ this._outpaintVAlign = '';
+ })}
+ icon={<FontAwesomeIcon icon="bullseye" color={SnappingManager.userColor} />}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userColor}
+ />
+ {!this.outpaintVertical ? null : (
+ <Toggle
+ type={Type.TERT}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'bottom'}
+ onClick={action(() => (this._outpaintVAlign = 'bottom'))}
+ icon={<FontAwesomeIcon icon="chevron-down" color={SnappingManager.userColor} />}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userColor}
+ />
+ )}
+ </div>
+ {this.outpaintVertical ? null : (
+ <Toggle
+ type={Type.TERT}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this._outpaintAlign === 'right' && this._outpaintVAlign === ''}
+ onClick={action(() => (this._outpaintAlign = 'right'))}
+ icon={<FontAwesomeIcon icon="chevron-right" color={SnappingManager.userColor} />}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userColor}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+
specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
@@ -346,36 +577,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
- funcs.push({
- description: 'GetImageText',
- event: () => {
+ funcs.push({ description: 'GetImageText', event: () => {
Networking.PostToServer('/queryFireflyImageText', {
file: (file => {
- const ext = extname(file);
- return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
- })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+ const ext = file ? extname(file) : '';
+ return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+ })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href),
}).then(text => alert(text));
},
icon: 'expand-arrows-alt',
- });
- funcs.push({
- description: 'Expand Image',
- event: () => {
- Networking.PostToServer('/expandImage', {
- prompt: 'sunny skies',
- file: (file => {
- const ext = extname(file);
- return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
- })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
- }).then(res => {
- const info = res as Upload.ImageInformation;
- const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
- DocUtils.assignImageInfo(info, img);
- this._props.addDocTab(img, OpenWhere.addRight);
- });
- },
- icon: 'expand-arrows-alt',
- });
+ }); // prettier-ignore
funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
this.layoutDoc.ai &&
@@ -396,20 +607,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
});
+ // Add new outpainting option
+ funcs.push({ description: 'Outpaint Image', event: () => this.openOutpaintPrompt(), icon: 'brush' });
+
+ // Add outpainting history option if the image was outpainted
+ this.Document.ai_outpainted &&
+ funcs.push({
+ description: 'View Original Image',
+ event: action(() => {
+ const alternates = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
+ if (alternates && alternates.length) {
+ // Toggle to show the original image
+ this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate';
+ }
+ }),
+ icon: 'image',
+ });
ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
};
// 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,
- usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
- usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
+ 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(),
this._props.PanelHeight(),
0,
@@ -422,19 +648,18 @@ 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);
};
- getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
+ getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc.freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
@computed get usingAlternate() {
const usePath = StrCast(this.Document[this.fieldKey + '_usePath']);
@@ -443,7 +668,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);
@@ -453,15 +678,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`];
@@ -508,30 +733,33 @@ 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={() => DocCast(this.Document.ai_firefly_generatedDocs) && 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))
+ ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutDataKey(doc)])?.url ?? defaultUrl) : defaultUrl))
.filter(url => url)
.map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
@@ -541,11 +769,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;
@@ -572,21 +804,33 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
})}
key={this.layoutDoc[Id]}
onPointerDown={this.marqueeDown}>
- <div className="imageBox-fader" style={{ opacity: backAlpha }}>
+ <div
+ className="imageBox-fader"
+ style={{
+ opacity: backAlpha,
+ flexDirection: this._outpaintVAlign ? 'row' : 'column',
+ alignItems: this._outpaintAlign === 'left' || this._outpaintVAlign === 'top' ? 'flex-start' : this._outpaintAlign === 'right' || this._outpaintVAlign === 'bottom' ? 'flex-end' : undefined,
+ }}>
<img
alt=""
ref={action((r: HTMLImageElement | null) => (this.imageRef = r))}
key="paths"
src={srcpath}
- style={{ transform, transformOrigin }}
- onError={action(e => {
- this._error = e.toString();
- })}
+ style={{
+ position: 'relative',
+ transform,
+ transformOrigin,
+ width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined,
+ height: this._outpaintVAlign ? 'max-content' : this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined,
+ }}
+ 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>
)}
@@ -650,7 +894,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onClick={action(async () => {
this._regenerateLoading = true;
if (this._fireflyRefStrength) {
- DrawingFillHandler.drawingToImage(this.props.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 => {
@@ -659,7 +903,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const url = firstImg.pathname;
const imgField = new ImageField(url);
this._prevImgs.length === 0 &&
- this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field.url.pathname });
+ this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' });
this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url });
this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
this.dataDoc.ai_firefly_prompt = firstImg.prompt;
@@ -704,21 +948,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
@@ -743,85 +990,91 @@ 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"
- onContextMenu={this.specificContextMenu}
- ref={this.createDropTarget}
- onScroll={action(() => {
- if (!this._forcedScroll) {
- if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) {
- this._ignoreScroll = true;
- this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop;
- this._ignoreScroll = false;
+ <>
+ <div
+ className="imageBox"
+ onContextMenu={this.specificContextMenu}
+ ref={this.createDropTarget}
+ onScroll={action(() => {
+ if (!this._forcedScroll) {
+ if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) {
+ this._ignoreScroll = true;
+ this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop;
+ this._ignoreScroll = false;
+ }
}
- }
- })}
- style={{
- width: this._props.PanelWidth() ? undefined : `100%`,
- height: this._props.PanelHeight() ? undefined : `100%`,
- pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
- borderRadius,
- overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden',
- }}>
- <CollectionFreeFormView
- ref={this._ffref}
- {...this._props}
- Document={doc}
- setContentViewBox={emptyFunction}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- renderDepth={this._props.renderDepth + 1}
- fieldKey={this.annotationKey}
- styleProvider={this._props.styleProvider}
- isAnnotationOverlay
- annotationLayerHostsContent
- PanelWidth={this._props.PanelWidth}
- PanelHeight={this._props.PanelHeight}
- ScreenToLocalTransform={this.screenToLocalTransform}
- select={emptyFunction}
- focus={this.focus}
- getScrollHeight={this.getScrollHeight}
- NativeDimScaling={returnOne}
- isAnyChildContentActive={returnFalse}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}>
- {this.content}
- </CollectionFreeFormView>
- {this.Loading ? (
- <div className="loading-spinner" style={{ position: 'absolute' }}>
- <ReactLoading type="spin" height={50} width={50} color={'blue'} />
- </div>
- ) : null}
- {this.regenerateImageIcon}
- {this.overlayImageIcon}
- {this.annotationLayer}
- {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
- <MarqueeAnnotator
+ })}
+ style={{
+ width: this._props.PanelWidth() ? undefined : `100%`,
+ height: this._props.PanelHeight() ? undefined : `100%`,
+ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
+ borderRadius,
+ overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden',
+ }}>
+ <CollectionFreeFormView
+ ref={this._ffref}
+ {...this._props}
Document={this.Document}
- ref={this.marqueeref}
- scrollTop={0}
- annotationLayerScrollTop={0}
- scaling={returnOne}
- annotationLayerScaling={this._props.NativeDimScaling}
- screenTransform={this.DocumentView().screenToViewTransform}
- docView={this.DocumentView}
- addDocument={this.addDocument}
- finishMarquee={this.finishMarquee}
- savedAnnotations={this.savedAnnotations}
- selectionText={returnEmptyString}
- annotationLayer={this._annotationLayer.current}
- marqueeContainer={this._mainCont}
- highlightDragSrcColor=""
- anchorMenuCrop={this.crop}
- // anchorMenuFlashcard={() => this.getImageDesc()}
- />
- )}
- </div>
+ setContentViewBox={emptyFunction}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ renderDepth={this._props.renderDepth + 1}
+ fieldKey={this.annotationKey}
+ styleProvider={this._props.styleProvider}
+ isAnnotationOverlay
+ annotationLayerHostsContent
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ select={emptyFunction}
+ focus={this.focus}
+ rejectDrop={this._props.rejectDrop}
+ getScrollHeight={this.getScrollHeight}
+ NativeDimScaling={returnOne}
+ isAnyChildContentActive={returnFalse}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}>
+ {this.content}
+ </CollectionFreeFormView>
+ {this.Loading ? (
+ <div className="loading-spinner" style={{ position: 'absolute' }}>
+ <ReactLoading type="spin" height={50} width={50} color={'blue'} />
+ </div>
+ ) : null}
+ {this.regenerateImageIcon}
+ {this.overlayImageIcon}
+ {this.annotationLayer}
+ {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ Document={this.Document}
+ ref={this.marqueeref}
+ scrollTop={0}
+ annotationLayerScrollTop={0}
+ scaling={returnOne}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ screenTransform={this.DocumentView().screenToViewTransform}
+ docView={this.DocumentView}
+ addDocument={this.addDocument}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ selectionText={returnEmptyString}
+ annotationLayer={this._annotationLayer.current}
+ marqueeContainer={this._mainCont}
+ highlightDragSrcColor=""
+ anchorMenuCrop={this.crop}
+ // anchorMenuFlashcard={() => this.getImageDesc()}
+ />
+ )}
+ {this._outpaintingInProgress && (
+ <div className="imageBox-outpaintingSpinner">
+ <ReactLoading type="spin" color="#666" height={60} width={60} />
+ </div>
+ )}
+ </div>
+ </>
);
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 40c687b7e..aa66b5ba9 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;
@@ -54,16 +55,12 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable private rows: KeyValuePair[] = [];
@observable _splitPercentage = 50;
- get fieldDocToLayout() {
- return DocCast(this.Document);
- }
-
@action
onEnterKey = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter') {
e.stopPropagation();
- if (this._keyInput.current?.value && this._valInput.current?.value && this.fieldDocToLayout) {
- if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput.current.value, this._valInput.current.value)) {
+ if (this._keyInput.current?.value && this._valInput.current?.value && this.Document) {
+ if (KeyValueBox.SetField(this.Document, this._keyInput.current.value, this._valInput.current.value)) {
this._keyInput.current.value = '';
this._valInput.current.value = '';
document.body.focus();
@@ -95,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
@@ -106,15 +104,15 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
default: {
const _setCacheResult_ = (value: FieldResult) => {
field = value as FieldType;
- if (setResult) setResult?.(value);
+ if (setResult) setResult(value);
else target[key] = field;
};
- const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log);
+ const res = script.run({ this: doc, _setCacheResult_ }, console.log);
if (!res.success) {
if (key) target[key] = script.originalScript;
return false;
}
- field === undefined && (field = res.result instanceof Array ? new List<FieldType>(res.result) : (typeof res.result === 'function' ? res.result.name : res.result as FieldType));
+ field === undefined && (field = res.result instanceof Array ? new List<FieldType>(res.result) : typeof res.result === 'function' ? res.result.name : (res.result as FieldType));
}
}
if (!key) return false;
@@ -125,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 => {
@@ -141,7 +145,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
rowHeight = () => 30;
@computed get createTable() {
- const doc = this.fieldDocToLayout;
+ const doc = this.Document;
if (!doc) {
return (
<tr>
@@ -149,25 +153,39 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
</tr>
);
}
- const realDoc = doc;
const ids: { [key: string]: string } = {};
const protos = Doc.GetAllPrototypes(doc);
protos.forEach(proto => {
Object.keys(proto).forEach(key => {
- if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
+ if (!(key in ids) && doc[key] !== ComputedField.undefined) {
ids[key] = key;
}
});
});
+ 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 (doc[key] !== ComputedField.undefined) {
+ if (key.replace(/^_/, '') in ids) delete ids[key.replace(/^_/, '')];
+ ids[key] = key;
+ }
+ });
+ });
+
const rows: JSX.Element[] = [];
let i = 0;
const keys = Object.keys(ids).slice();
// for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
const sortedKeys = keys.sort((a: string, b: string) => {
- const a_ = a.split('_')[0];
- const b_ = b.split('_')[0];
+ const a_ = a.replace(/^_/, '').split('_')[0];
+ const b_ = b.replace(/^_/, '').split('_')[0];
if (a_ < b_) return -1;
if (a_ > b_) return 1;
if (a === a_) return -1;
@@ -177,7 +195,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
sortedKeys.forEach(key => {
rows.push(
<KeyValuePair
- doc={realDoc}
+ doc={doc}
addDocTab={this._props.addDocTab}
PanelWidth={this._props.PanelWidth}
PanelHeight={this.rowHeight}
@@ -243,15 +261,15 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
getFieldView = () => {
const rows = this.rows.filter(row => row.isChecked);
if (rows.length > 1) {
- const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true });
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${this.Document.title}`, _chromeHidden: true });
rows.forEach(row => {
- const field = this.createFieldView(DocCast(this.Document), row);
+ const field = this.createFieldView(this.Document, row);
field && Doc.AddDocToList(parent, 'data', field);
row.uncheck();
});
return parent;
}
- return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined;
+ return rows.length ? this.createFieldView(this.Document, rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
@@ -300,7 +318,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
description: 'Default Perspective',
event: () => {
this._props.addDocTab(this.Document, OpenWhere.close);
- this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
+ this._props.addDocTab(this.Document, OpenWhere.addRight);
},
icon: 'image',
});
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 85aff04c3..d8f968a40 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,30 +61,23 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
};
render() {
- // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
+ let doc: Doc | undefined = 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;
- let { doc } = this._props;
- while (doc) {
- if (Object.keys(doc).includes(this._props.keyName)) {
- break;
- }
+ while (doc && !Object.keys(doc).includes(key)) {
protoCount++;
doc = DocCast(doc.proto);
}
const parenCount = Math.max(0, protoCount - 1);
- const keyStyle = protoCount === 0 ? 'black' : 'blue';
-
+ 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
@@ -91,17 +85,15 @@ 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];
+ doc && 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, '').match(/_/g)?.length || 0), color: keyStyle }}>
+ <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)}
- {this._props.keyName}
+ {(keyStyle === 'black' ? '' : layoutField ? '_' : '$') + key}
{')'.repeat(parenCount)}
</div>
</Tooltip>
@@ -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/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 7fb83571f..b08ed84b7 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -13,11 +13,11 @@ import { undoable } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
+import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import './LabelBox.scss';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { RichTextMenu } from './formattedText/RichTextMenu';
-import { DocumentView } from './DocumentView';
@observer
export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -103,11 +103,11 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
if (r) {
if (!r.offsetHeight || !r.offsetWidth) {
- //console.log("CAN'T FIT TO EMPTY BOX");
- this._timeout && clearTimeout(this._timeout);
+ r.style.opacity = '0';
this._timeout = setTimeout(() => this.fitTextToBox(r));
return textfitParams;
}
+ r.style.opacity = '1';
r.style.whiteSpace = ''; // textfit sets to nowrap if not multiline, but doesn't reeset if it becomes multiline
r.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align']); // textfit doesn't reset textAlign if it has been set to center, so we just set it to what we want
r.firstChild instanceof HTMLElement && (r.firstChild.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align']));
@@ -222,7 +222,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
FormattedTextBox.LiveTextUndo = undefined;
}}
dangerouslySetInnerHTML={{
- __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title.startsWith('#') ? null : (this.Title ?? '')}</span>`,
+ __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title?.startsWith('#') ? '' : (this.Title ?? '')}</span>`,
}}
contentEditable={this._props.onClickScript?.() ? undefined : true}
ref={r => {
@@ -239,7 +239,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.Title) {
this.resetCursor();
}
- }
+ } else this._timeout && clearTimeout(this._timeout);
}}
/>
</div>
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index d5dc256d9..78c8a686c 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -128,6 +128,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const getAnchor = (field: FieldResult): Element[] => {
const docField = DocCast(field);
const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
+ if (!doc) return [];
const ele = document.getElementById(DocumentView.UniquifyId(DocumentView.LightboxContains(this.DocumentView?.()), doc[Id]));
if (ele?.className === 'linkBox-label') foundParent = true;
if (ele?.getBoundingClientRect().width) return [ele];
@@ -252,7 +253,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
fontSize={fontSize}
GetValue={() => linkDesc}
SetValue={action(val => {
- this.Document[DocData].link_description = val;
+ this.Document.$link_description = val;
return true;
})}
/>
@@ -262,8 +263,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
background={color}
color={fontColor || lightOrDark(DashColor(color).fade(0.5).toString())}
type={Type.PRIM}
- val={StrCast(this.Document[DocData].link_description)}
- setVal={action(val => (this.Document[DocData].link_description = val))}
+ val={StrCast(this.Document.$link_description)}
+ setVal={action(val => (this.Document.$link_description = val))}
fillWidth
/> */}
</div>
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index ff95f8547..aeac100f4 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,25 +1,23 @@
import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { DocData } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
import './LinkDescriptionPopup.scss';
import { TaskCompletionBox } from './TaskCompletedBox';
@observer
-export class LinkDescriptionPopup extends React.Component<{}> {
+export class LinkDescriptionPopup extends React.Component<object> {
// eslint-disable-next-line no-use-before-define
public static Instance: LinkDescriptionPopup;
@observable public display: boolean = false;
- // eslint-disable-next-line react/no-unused-class-component-methods
@observable public showDescriptions: string = 'ON';
@observable public popupX: number = 700;
@observable public popupY: number = 350;
@observable description: string = '';
@observable popupRef = React.createRef<HTMLDivElement>();
- constructor(props: any) {
+ constructor(props: object) {
super(props);
makeObservable(this);
LinkDescriptionPopup.Instance = this;
@@ -47,15 +45,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
onDismiss = (add: boolean) => {
this.display = false;
- if (add) {
- LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description);
- }
+ add && LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink.$link_description = this.description);
this.description = '';
};
@action
onClick = (e: PointerEvent) => {
- if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) {
+ if (this.popupRef && !this.popupRef.current?.contains(e.target as Node)) {
this.display = false;
this.description = '';
TaskCompletionBox.taskCompleted = false;
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index a3ac68b99..f6509b885 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -3,7 +3,7 @@ import * as d3 from 'd3';
import { Feature, GeoJsonProperties, Geometry, LineString } from 'geojson';
import { MercatorCoordinate } from 'mapbox-gl';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
-import { MapRef } from 'react-map-gl';
+import { MapRef } from 'react-map-gl/mapbox';
export type Position = [number, number];
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index 8784a709a..8bb7ddd9f 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -43,7 +43,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return this._left > 0;
}
- constructor(props: Readonly<{}>) {
+ constructor(props: AntimodeMenuProps) {
super(props);
DirectionsAnchorMenu.Instance = this;
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 8079d96ea..fc5377ba4 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,7 +1,8 @@
+import { IconButton } from '@dash/components';
import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
-import { IconButton } from '@dash/components';
+import { LngLatLike } from 'mapbox-gl';
import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -14,12 +15,10 @@ import { CalendarManager } from '../../../util/CalendarManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { DocumentView } from '../DocumentView';
+import { Position } from './AnimationUtility';
import './MapAnchorMenu.scss';
import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
import { MarkerIcons } from './MarkerIcons';
-import { LngLatLike } from 'mapbox-gl';
-import { Position } from './AnimationUtility';
-// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
@@ -46,7 +45,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public IsTargetToggler: () => boolean = returnFalse;
public DisplayRoute: (routeInfoMap: Record<TransportationType, { coordinates: Position[] }> | undefined, type: TransportationType) => void = unimplementedFunction;
public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: { place_name: string; center: number[] }, createPinForDestination: boolean) => void = unimplementedFunction;
- public CreatePin: (feature: { place_name: string; center: LngLatLike; properties: { wikiData: unknown } }) => void = unimplementedFunction;
+ public CreatePin: (feature: { place_name: string; center: LngLatLike; properties?: { wikiData: string } }) => void = unimplementedFunction;
public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
@@ -293,7 +292,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return undefined;
};
- getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
+ getDirectionsButton = () => <IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />;
getAddToCalendarButton = (docType: string): JSX.Element => (
<IconButton
@@ -305,9 +304,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
);
- addToCalendarButton: JSX.Element = (
- <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
- );
+ addToCalendarButton = () => <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />;
getLinkNoteToDocButton = (docType: string): JSX.Element => (
<div ref={this._commentRef}>
@@ -320,7 +317,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</div>
);
- linkNoteToPinOrRoutenButton: JSX.Element = (
+ linkNoteToPinOrRoutenButton = () => (
<div ref={this._commentRef}>
<IconButton
tooltip="Link Note to Pin" //
@@ -331,9 +328,9 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</div>
);
- customizePinButton: JSX.Element = (<IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />);
+ customizePinButton = () => <IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />;
- centerOnPinButton: JSX.Element = (
+ centerOnPinButton = () => (
<IconButton
tooltip="Center on pin" //
onPointerDown={this.Center}
@@ -342,7 +339,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
);
- backButton: JSX.Element = (
+ backButton = () => (
<IconButton
tooltip="Go back" //
onPointerDown={this.BackClick}
@@ -351,7 +348,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
);
- addRouteButton: JSX.Element = (
+ addRouteButton = () => (
<IconButton
tooltip="Add route" //
onPointerDown={this.HandleAddRouteClick}
@@ -369,9 +366,9 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
);
- animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
+ animateRouteButton = () => <IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />;
- revertToOriginalMarkerButton = (
+ revertToOriginalMarkerButton = () => (
<IconButton
tooltip="Revert to original" //
onPointerDown={() => this.revertToOriginalMarker()}
@@ -386,31 +383,31 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
{this.menuType === 'standard' && (
<>
{this.getDeleteButton('pin')}
- {this.getDirectionsButton}
+ {this.getDirectionsButton()}
{this.getAddToCalendarButton('pin')}
{this.getLinkNoteToDocButton('pin')}
- {this.customizePinButton}
- {this.centerOnPinButton}
+ {this.customizePinButton()}
+ {this.centerOnPinButton()}
</>
)}
{this.menuType === 'routeCreation' && (
<>
- {this.backButton}
- {this.addRouteButton}
+ {this.backButton()}
+ {this.addRouteButton()}
</>
)}
{this.menuType === 'route' && (
<>
{this.getDeleteButton('route')}
- {this.animateRouteButton}
+ {this.animateRouteButton()}
{this.getAddToCalendarButton('route')}
{this.getLinkNoteToDocButton('route')}
</>
)}
{this.menuType === 'customize' && (
<>
- {this.backButton}
- {this.revertToOriginalMarkerButton}
+ {this.backButton()}
+ {this.revertToOriginalMarkerButton()}
</>
)}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index fdd8a29d7..bd4b51038 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -7,6 +7,9 @@
overflow: hidden;
display: flex;
position: absolute;
+ .mapboxgl-marker {
+ cursor: default;
+ }
.mapboxgl-map {
overflow: unset !important;
@@ -27,6 +30,9 @@
gap: 5px;
align-items: center;
width: calc(100% - 40px);
+ z-index: 1;
+ position: relative;
+ background: lightGray;
}
.mapbox-settings-panel {
@@ -171,6 +177,8 @@
.mapBox-wrapper {
width: 100%;
+ transform-origin: top left;
+
.mapBox-input {
box-sizing: border-box;
border: 1px solid transparent;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 792cb6b46..a563b7c1b 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,8 +1,8 @@
-import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
+import { IconButton, Size, Type } from '@dash/components';
+import { faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, FormControlLabel, TextField } from '@mui/material';
import * as turf from '@turf/turf';
-import { IconButton, Size, Type } from '@dash/components';
import * as d3 from 'd3';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString } from 'geojson';
import { LngLatBoundsLike, LngLatLike, MapLayerMouseEvent } from 'mapbox-gl';
@@ -10,11 +10,14 @@ import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObserv
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
-import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
+import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl/mapbox';
import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { TraceMobx } from '../../../../fields/util';
import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
@@ -34,7 +37,6 @@ import { MapAnchorMenu } from './MapAnchorMenu';
import './MapBox.scss';
import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
import { MarkerIcons } from './MarkerIcons';
-import { RichTextField } from '../../../../fields/RichTextField';
// import { GeocoderControl } from './GeocoderControl';
// amongus
@@ -76,7 +78,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
makeObservable(this);
}
- @observable _featuresFromGeocodeResults: { place_name: string; center: LngLatLike | undefined }[] = [];
+ @observable _featuresFromGeocodeResults: { place_name: string; center: LngLatLike | undefined; properties?: { wikiData: string } }[] = [];
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected
@observable _mapReady = false;
@@ -123,7 +125,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const originalCoordinates: Position[] = JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
// const index = Math.floor(this.animationPhase * originalCoordinates.length);
const index = this._animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
- console.log('Animation phase', this._animationPhase);
const startIndex = Math.floor(index);
const endIndex = Math.ceil(index);
let feature: Feature<Geometry, GeoJsonProperties>;
@@ -183,7 +184,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
});
return feature;
}
- console.log('ERROR');
return {
type: 'Feature',
properties: {},
@@ -199,7 +199,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get allRoutesGeoJson(): FeatureCollection {
const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map((routeDoc: Doc) => {
- console.log('Route coords: ', routeDoc.routeCoordinates);
const geometry: LineString = {
type: 'LineString',
coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
@@ -215,7 +214,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return {
type: 'FeatureCollection',
- features: features,
+ features,
};
}
@@ -241,7 +240,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
toList(docs).forEach(doc => {
let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this._selectedPinOrRoute;
if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
- existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
+ existingPin = this.createPushpin({ lng: NumCast(doc.longitude), lat: NumCast(doc.latitude) }, StrCast(doc.map));
}
if (existingPin) {
setTimeout(() => {
@@ -477,7 +476,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
deleteSelectedPinOrRoute = undoable(() => {
- console.log('deleting');
if (this._selectedPinOrRoute) {
// Removes filter
Doc.setDocFilter(this.Document, 'latitude', NumCast(this._selectedPinOrRoute.latitude), 'remove');
@@ -542,17 +540,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
* Creates Pushpin doc and adds it to the list of annotations
*/
@action
- createPushpin = undoable((center: LngLatLike, location?: string, wikiData?: string) => {
- const lat = 'lat' in center ? center.lat : center[0];
- const lon = 'lng' in center ? center.lng : 'lon' in center ? center.lon : center[1];
+ createPushpin = (center: LngLatLike, location?: string, wikiData?: string) => {
+ const [lng, lat] = center instanceof Array ? center : ['lng' in center ? center.lng : center.lon, center.lat];
// Stores the pushpin as a MapMarkerDocument
const pushpin = Docs.Create.PushpinDocument(
lat,
- lon,
+ lng,
false,
[],
{
- title: location ?? `lat=${lat},lng=${lon}`,
+ title: location ?? `lat=${lat},lng=${lng}`,
map: location,
description: '',
wikiData: wikiData,
@@ -563,11 +560,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// ,'pushpinIDamongus'+ this.incrementer++
);
this.addDocument(pushpin, this.annotationKey);
- console.log(pushpin);
return pushpin;
// mapMarker.infoWindowOpen = true;
- }, 'createpin');
+ };
@action
createMapRoute = undoable((coordinates: Position[], originName: string, destination: { place_name: string; center: number[] }, createPinForDestination: boolean) => {
@@ -575,7 +571,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${originName} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) });
this.addDocument(mapRoute, this.annotationKey);
if (createPinForDestination) {
- this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
+ this.createPushpin({ lng: destination.center[0], lat: destination.center[1] }, destination.place_name);
}
this._temporaryRouteSource = {
type: 'FeatureCollection',
@@ -598,18 +594,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
@action
- addMarkerForFeature = (feature: { place_name: string; center: LngLatLike | undefined; properties?: { wikiData: unknown } }) => {
- const location = feature.place_name;
+ addMarkerForFeature = (feature: { place_name: string; center: LngLatLike | undefined; properties?: { wikiData: string } }) => {
if (feature.center) {
- const wikiData = feature.properties?.wikiData;
-
- this.createPushpin(feature.center, location, wikiData);
-
- if (this._mapRef.current) {
- this._mapRef.current.flyTo({
- center: feature.center,
- });
- }
+ this.createPushpin(feature.center, feature.place_name, feature.properties?.wikiData);
+ this._mapRef.current?.flyTo({
+ center: feature.center,
+ });
this._featuresFromGeocodeResults = [];
} else {
// TODO: handle error
@@ -632,7 +622,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// try {
// const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
// const response = await fetch(url);
- // const data = await response.json();
+ // const data = await response.jchildDocson();
// runInAction(() => {
// this.featuresFromGeocodeResults = data.features;
// })
@@ -653,7 +643,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
layers: ['map-routes-layer'],
});
- console.error(features);
+ this.Document._childFilters = new List<string>(StrListCast(this.Document._childFilters).filter(filter => !filter.includes(LinkedTo)));
if (features && features.length > 0 && features[0].properties && features[0].geometry) {
const { routeTitle } = features[0].properties;
const routeDoc: Doc | undefined = this.allRoutes.find(rtDoc => rtDoc.title === routeTitle);
@@ -668,9 +658,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
-
MapAnchorMenu.Instance.Reset();
-
MapAnchorMenu.Instance.setRouteDoc(routeDoc);
// TODO: Subject to change
@@ -833,10 +821,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed
get preAnimationViewState() {
- if (!this._isAnimating) {
- return this.mapboxMapViewState;
- }
- return undefined;
+ return !this._isAnimating ? this.mapboxMapViewState : undefined;
}
@action
@@ -846,24 +831,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
updateAnimationSpeed = () => {
- let newAnimationSpeed: AnimationSpeed;
+ this._animationSpeed = (() => {
switch (this._animationSpeed) {
- case AnimationSpeed.SLOW:
- newAnimationSpeed = AnimationSpeed.MEDIUM;
- break;
- case AnimationSpeed.MEDIUM:
- newAnimationSpeed = AnimationSpeed.FAST;
- break;
- case AnimationSpeed.FAST:
- newAnimationSpeed = AnimationSpeed.SLOW;
- break;
- default:
- newAnimationSpeed = AnimationSpeed.MEDIUM;
- break;
- }
- this._animationSpeed = newAnimationSpeed;
+ case AnimationSpeed.SLOW: return AnimationSpeed.MEDIUM;
+ case AnimationSpeed.MEDIUM: return AnimationSpeed.FAST;
+ case AnimationSpeed.FAST: return AnimationSpeed.SLOW;
+ default: return AnimationSpeed.MEDIUM;
+ }})(); // prettier-ignore
if (this._animationUtility) {
- this._animationUtility.updateAnimationSpeed(newAnimationSpeed);
+ this._animationUtility.updateAnimationSpeed(this._animationSpeed);
}
};
@computed get animationSpeedTooltipText(): string {
@@ -890,19 +866,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._animationUtility?.updateIsStreetViewAnimation(newVal);
};
- getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
- const geometry: LineString = {
+ getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => ({
+ type: 'Feature',
+ properties: {
+ routeTitle: routeDoc.title,
+ },
+ geometry: {
type: 'LineString',
coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
- };
- return {
- type: 'Feature',
- properties: {
- routeTitle: routeDoc.title,
- },
- geometry: geometry,
- };
- };
+ },
+ });
@action
playAnimation = (status: AnimationStatus) => {
@@ -936,7 +909,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);
if (status !== AnimationStatus.RESUME) {
- const result = await animationUtil.flyInAndRotate({
+ await animationUtil.flyInAndRotate({
map: this._mapRef.current!,
// targetLngLat,
// duration 3000
@@ -948,9 +921,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// endPitch: this.isStreetViewAnimation ? 80 : 50,
updateFrameId,
});
-
- console.log('Bearing: ', result.bearing);
- console.log('Altitude: ', result.altitude);
}
runInAction(() => {
@@ -1028,7 +998,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.playAnimation(AnimationStatus.START); // Play from the beginning
}
}}
- icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+ icon={<FontAwesomeIcon icon={this._isAnimating && this._finishedFlyTo ? faPause : faPlay} />}
color="black"
size={Size.MEDIUM}
/>
@@ -1039,12 +1009,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.stopAnimation(false);
this.playAnimation(AnimationStatus.START);
}}
- icon={<FontAwesomeIcon icon={faRotate as IconLookup} />}
+ icon={<FontAwesomeIcon icon={faRotate} />}
color="black"
size={Size.MEDIUM}
/>
)}
- <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} />
+ <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark} />} color="black" size={Size.MEDIUM} />
<div className="animation-suboptions">
<div>|</div>
<FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this._isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
@@ -1085,7 +1055,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const bearing = parseInt(e.target.value);
if (!isNaN(bearing) && this._mapRef.current) {
- console.log('bearing change');
const fixedBearing = Math.max(0, Math.min(360, bearing));
this._mapRef.current.setBearing(fixedBearing);
this.dataDoc.map_bearing = fixedBearing;
@@ -1096,7 +1065,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const pitch = parseInt(e.target.value);
if (!isNaN(pitch) && this._mapRef.current) {
- console.log('pitch change');
const fixedPitch = Math.max(0, Math.min(85, pitch));
this._mapRef.current.setPitch(fixedPitch);
this.dataDoc.map_pitch = fixedPitch;
@@ -1141,16 +1109,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._showTerrain = !this._showTerrain;
};
- getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
- const markerType = StrCast(pinDoc.markerType);
- const markerColor = StrCast(pinDoc.markerColor);
-
- return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
- };
+ getMarkerIcon = (pinDoc: Doc) => MarkerIcons.getFontAwesomeIcon(StrCast(pinDoc.markerType), '2x', StrCast(pinDoc.markerColor)) ?? null;
render() {
- const scale = this._props.NativeDimScaling?.() || 1;
- const parscale = scale === 1 ? 1 : (this.ScreenToLocalBoxXf().Scale ?? 1);
+ TraceMobx();
+ const scale = (this._props.NativeDimScaling?.() || 1) + 0.001; // bcz: weird, but without this hack, MapBox doesn't locate map correctly
+ const parscale = this.ScreenToLocalBoxXf().Scale;
return (
<div className="mapBox" ref={this._ref}>
@@ -1158,13 +1122,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
className="mapBox-wrapper"
onWheel={e => e.stopPropagation()}
onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
- style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+ style={{ transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
{!this._routeToAnimate && (
- <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
+ <div className="mapBox-searchbar" style={{ width: `${100 / scale}%` }}>
<TextField fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={e => this.handleSearchChange(e.target.value)} />
- <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear} size="1x" />} type={Type.TERT} onClick={this.toggleSettings} />
<div style={{ opacity: 0 }}>
- <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear} size="1x" />} type={Type.TERT} onClick={this.toggleSettings} />
</div>
</div>
)}
@@ -1188,15 +1152,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
<div className="mapbox-bearing-selection">
<div>Bearing: </div>
- <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(0)} type="number" onChange={this.onBearingChange} />
+ <input value={this.mapboxMapViewState.bearing.toFixed(0)} type="number" onChange={this.onBearingChange} />
</div>
<div className="mapbox-pitch-selection">
<div>Pitch: </div>
- <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(0)} type="number" onChange={this.onPitchChange} />
+ <input value={this.mapboxMapViewState.pitch.toFixed(0)} type="number" onChange={this.onPitchChange} />
</div>
<div className="mapbox-pitch-selection">
<div>Zoom: </div>
- <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(0)} type="number" onChange={this.onZoomChange} />
+ <input value={this.mapboxMapViewState.zoom.toFixed(0)} type="number" onChange={this.onZoomChange} />
</div>
<div className="mapbox-terrain-selection">
<div>Show terrain: </div>
@@ -1230,17 +1194,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
)}
<MapProvider>
<MapboxMap
+ key={'' + this.Document.x + this.Document.y} // force map to rerender after dragging, otherwise it will display the wrong location until it gets re-rendered
ref={this._mapRef}
mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
- viewState={this._isAnimating || this._routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+ viewState={this._isAnimating || this._routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: this._props.PanelWidth(), height: this._props.PanelHeight() }}
mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
style={{
position: 'absolute',
top: 0,
left: 0,
zIndex: '0',
- width: NumCast(this.layoutDoc._width) * parscale,
- height: NumCast(this.layoutDoc._height) * parscale,
+ width: this._props.PanelWidth() * parscale,
+ height: this._props.PanelHeight() * parscale,
}}
initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState}
onZoom={this.onMapZoom}
@@ -1315,19 +1280,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
/>
</>
)}
-
- {!this._isAnimating &&
- this._animationPhase === 0 &&
- this.allPushpins // .filter(anno => !anno.layout_unrendered)
- .map((pushpin, idx) => (
- <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={e => this.handleMarkerClick(e.originalEvent.clientX, e.originalEvent.clientY, pushpin)}>
- {this.getMarkerIcon(pushpin)}
- </Marker>
- ))}
-
- {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
- <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
- ))} */}
+ {this._isAnimating || this._animationPhase
+ ? null
+ : this.allPushpins.map(p => (
+ <Marker
+ key={'' + p.longitude + p.latitude}
+ longitude={NumCast(p.longitude)}
+ latitude={NumCast(p.latitude)}
+ anchor="bottom"
+ onClick={e => this.handleMarkerClick(e.originalEvent.clientX, e.originalEvent.clientY, p)}>
+ {this.getMarkerIcon(p)}
+ </Marker>
+ ))}
</MapboxMap>
</MapProvider>
</div>
@@ -1336,7 +1300,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
ref={this._sidebarRef}
{...this._props}
fieldKey={this.fieldKey}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 0627d382e..e0efab576 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -4,7 +4,7 @@ import { Button, EditableText, IconButton, Type } from '@dash/components';
import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { MapProvider, Map as MapboxMap } from 'react-map-gl';
+import { MapProvider, Map as MapboxMap } from 'react-map-gl/mapbox';
import { ClientUtils, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt, returnEmptyDoclist } from '../../../../fields/Doc';
@@ -798,7 +798,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
PanelHeight={returnOne}
NativeWidth={returnOne}
NativeHeight={returnOne}
- onKey={undefined}
onDoubleClickScript={undefined}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
@@ -830,7 +829,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
ref={this._sidebarRef}
{...this._props}
fieldKey={this.fieldKey}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth
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 06b75e243..83c44c80f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -129,15 +129,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
cropping._width = anchw;
cropping._height = anchh;
cropping.onClick = undefined;
- const croppingProto = cropping[DocData];
- croppingProto.annotationOn = undefined;
- croppingProto.isDataDoc = true;
- croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
- croppingProto.type = DocumentType.IMG;
- croppingProto.layout = ImageBox.LayoutString('data');
- croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
- croppingProto.data_nativeWidth = anchw;
- croppingProto.data_nativeHeight = anchh;
+ cropping.$annotationOn = undefined;
+ cropping.$isDataDoc = true;
+ cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ cropping.$type = DocumentType.IMG;
+ cropping.$layout = ImageBox.LayoutString('data');
+ cropping.$data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+ cropping.$data_nativeWidth = anchw;
+ cropping.$data_nativeHeight = anchh;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
@@ -157,7 +156,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename =>
setTimeout(
action(() => {
- croppingProto.data = new ImageField(returnedfilename);
+ cropping.$data = new ImageField(returnedfilename);
}),
500
)
@@ -211,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._disposers.scroll = reaction(
() => this.layoutDoc.layout_scrollTop,
() => {
- if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
+ if (!(ComputedField.DisableCompute(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
}
this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
@@ -500,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" />
@@ -536,7 +537,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<SidebarAnnos
ref={this._sidebarRef}
{...this._props}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
setHeight={emptyFunction}
@@ -607,6 +608,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
pdfBox={this}
sidebarAddDoc={this.sidebarAddDocument}
addDocTab={this.sidebarAddDocTab}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
pdf={this._pdf}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 7ba313e92..25c708da8 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { BoolCast, DocCast } from '../../../../fields/Types';
@@ -99,7 +98,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
screengrabber.overlayX = 70; // was -400
screengrabber.overlayY = 590; // was 0
- screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+ screengrabber['$' + Doc.LayoutDataKey(screengrabber) + '_trackScreen'] = true;
Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
DocumentView.addViewRenderedCb(screengrabber, docView => {
RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 6289470b6..4f02d68d6 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -284,7 +284,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
setupDictation = () => {
if (this.dataDoc[this.fieldKey + '_dictation']) return;
const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
- const textField = Doc.LayoutFieldKey(dictationText);
+ const textField = Doc.LayoutDataKey(dictationText);
dictationText._layout_autoHeight = false;
const dictationTextProto = dictationText[DocData];
dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
@@ -301,7 +301,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div className="videoBox-viewer">
<div style={{ position: 'relative', height: this.videoPanelHeight() }}>
<CollectionFreeFormView
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -329,7 +328,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
{!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
<FormattedTextBox
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
fieldKey="text"
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8da422039..38a43e8d4 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,14 +1,12 @@
-/* eslint-disable react/button-has-type */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import { returnAlways, returnEmptyString } from '../../../ClientUtils';
-import { Doc } from '../../../fields/Doc';
+import { Doc, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
@@ -26,10 +24,8 @@ import './ScriptingBox.scss';
import * as ts from 'typescript';
import { FieldType } from '../../../fields/ObjectField';
-// eslint-disable-next-line @typescript-eslint/no-var-requires
const getCaretCoordinates = require('textarea-caret');
-// eslint-disable-next-line @typescript-eslint/no-var-requires
const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
@observer
@@ -105,7 +101,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc[this.fieldKey + '-functionDescription'] = value;
}
@computed({ keepAlive: true }) get compileParams() {
- return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+ return StrListCast(this.dataDoc[this.fieldKey + '-params']);
}
set compileParams(value) {
this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
@@ -426,7 +422,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
onChange={e => this.viewChanged(e, parameter)}
value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
{types.map((type, i) => (
- // eslint-disable-next-line react/no-array-index-key
<option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
{' '}
{type.toString()}{' '}
@@ -680,7 +675,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const definedParameters = !this.compileParams.length ? null : (
<div className="scriptingBox-plist" style={{ width: '30%' }}>
{this.compileParams.map((parameter, i) => (
- // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<EditableView
display="block"
@@ -760,7 +754,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
{!this.compileParams.length || !this.paramsNames ? null : (
<div className="scriptingBox-plist">
{this.paramsNames.map((parameter: string, i: number) => (
- // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
<div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9adee53e8..fa099178c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -331,7 +331,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
- // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
+ // link && (DocCast(link.link_anchor_2).$timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
@@ -918,11 +918,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
- const regionData = region[DocData];
- regionData.backgroundColor = 'transparent';
- regionData.lockedPosition = true;
- regionData.title = 'region:' + this.Document.title;
- regionData.followLinkToggle = true;
+ region.$backgroundColor = 'transparent';
+ region.$lockedPosition = true;
+ region.$title = 'region:' + this.Document.title;
+ region.$followLinkToggle = true;
region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
this.addDocument(region);
const anchx = NumCast(cropping.x);
@@ -938,25 +937,24 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
cropping.timecodeToHide = undefined;
cropping.timecodeToShow = undefined;
cropping.onClick = undefined;
- const croppingProto = cropping[DocData];
- croppingProto.annotationOn = undefined;
- croppingProto.isDataDoc = true;
- croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
- croppingProto.type = DocumentType.VID;
- croppingProto.layout = VideoBox.LayoutString('data');
- croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto.data_nativeWidth = anchw;
- croppingProto.data_nativeHeight = anchh;
- croppingProto.videoCrop = true;
- croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
- croppingProto.freeform_scale = viewScale;
- croppingProto.freeform_scale_min = viewScale;
- croppingProto.freeform_ = anchx / viewScale;
- croppingProto.freeform_panY = anchy / viewScale;
- croppingProto.freeform_panX_min = anchx / viewScale;
- croppingProto.freeform_panX_max = anchw / viewScale;
- croppingProto.freeform_panY_min = anchy / viewScale;
- croppingProto.freeform_panY_max = anchh / viewScale;
+ cropping.$annotationOn = undefined;
+ cropping.$isDataDoc = true;
+ cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ cropping.$type = DocumentType.VID;
+ cropping.$layout = VideoBox.LayoutString('data');
+ cropping.$data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
+ cropping.$data_nativeWidth = anchw;
+ cropping.$data_nativeHeight = anchh;
+ cropping.$videoCrop = true;
+ cropping.$layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
+ cropping.$freeform_scale = viewScale;
+ cropping.$freeform_scale_min = viewScale;
+ cropping.$freeform_ = anchx / viewScale;
+ cropping.$freeform_panY = anchy / viewScale;
+ cropping.$freeform_panX_min = anchx / viewScale;
+ cropping.$freeform_panX_max = anchw / viewScale;
+ cropping.$freeform_panY_min = anchy / viewScale;
+ cropping.$freeform_panY_max = anchh / viewScale;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e7a10cc29..5603786f0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
-import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { HtmlField } from '../../../fields/HtmlField';
import { InkTool } from '../../../fields/InkField';
@@ -54,7 +54,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
public static openSidebarWidth = 250;
public static sidebarResizerWidth = 5;
- static webStyleSheet = addStyleSheet();
+ static webStyleSheet = addStyleSheet().sheet;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -645,8 +645,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
forward = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'), []);
- const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+ const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
if (checkAvailable) return future.length;
runInAction(() => {
if (future.length) {
@@ -672,23 +672,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
back = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'));
- const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+ const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
if (checkAvailable) return history.length;
runInAction(() => {
if (history.length) {
const curUrl = this._url;
- if (future === undefined) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
+ if (!future.length) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
else this.dataDoc[this.fieldKey + '_future'] = new List<string>([...future, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
this._scrollHeight = 0;
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(
- action(() => {
- this._webUrl = this._url;
- })
- );
+ setTimeout(action(() => (this._webUrl = this._url)));
} else {
this._webUrl = this._url;
}
@@ -1220,7 +1216,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{...this._props}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
fieldKey={this.fieldKey + '_' + this._urlHash}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
setHeight={emptyFunction}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 009eb82cd..2b20a666d 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,23 +1,22 @@
import { Calendar, EventClickArg, EventDropArg, EventSourceInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
+import interactionPlugin from '@fullcalendar/interaction';
import multiMonthPlugin from '@fullcalendar/multimonth';
import timeGrid from '@fullcalendar/timegrid';
-import interactionPlugin from '@fullcalendar/interaction';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
-import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
-import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
-import './CalendarBox.scss';
import { Id } from '../../../../fields/FieldSymbols';
+import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocServer } from '../../../DocServer';
-import { DocumentView } from '../DocumentView';
-import { OpenWhere } from '../OpenWhere';
import { DragManager } from '../../../util/DragManager';
-import { DocData } from '../../../../fields/DocSymbols';
+import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
import { ContextMenu } from '../../ContextMenu';
+import { DocumentView } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
+import './CalendarBox.scss';
type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
@@ -25,7 +24,6 @@ type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDa
export class CalendarBox extends CollectionSubView() {
_calendarRef: HTMLDivElement | null = null;
_calendar: Calendar | undefined;
- _oldWheel: HTMLElement | null = null;
_observer: ResizeObserver | undefined;
_eventsDisposer: IReactionDisposer | undefined;
_selectDisposer: IReactionDisposer | undefined;
@@ -106,14 +104,15 @@ export class CalendarBox extends CollectionSubView() {
// TODO: Return a different color based on the event type
eventToColor = (event: Doc): string => {
- return 'red';
+ return 'red' + event;
};
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
internalDocDrop = (e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) => {
if (!super.onInternalDrop(e, de)) return false;
de.complete.docDragData?.droppedDocuments.forEach(doc => {
const today = new Date().toISOString();
- if (!doc.date_range) doc[DocData].date_range = `${today}|${today}`;
+ if (!doc.date_range) doc.$date_range = `${today}|${today}`;
});
return true;
};
@@ -194,7 +193,6 @@ export class CalendarBox extends CollectionSubView() {
setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end));
};
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
render() {
return (
<div
@@ -218,10 +216,7 @@ export class CalendarBox extends CollectionSubView() {
}}
ref={r => {
this.createDashEventsTarget(r);
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = r;
- // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
- r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ this.fixWheelEvents(r, this._props.isContentActive);
if (r) {
this._observer?.disconnect();
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index ad2f3e892..fc51ca5f9 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -196,7 +196,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// Add CSV details to linked files
this._linked_csv_files.push({
- filename: CsvCast(newLinkedDoc.data).url.pathname,
+ filename: CsvCast(newLinkedDoc.data)?.url.pathname ?? '',
id: csvId,
text: csvData,
});
@@ -621,6 +621,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
getDirectMatchingSegmentStart = (doc: Doc, citationText: string, indexesOfSegments: string[]): number => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const originalSegments = JSON.parse(StrCast(doc.original_segments!)).map((segment: any, index: number) => ({
index: index.toString(),
text: segment.text,
@@ -720,9 +721,9 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const x2 = parseFloat(values[2]) * Doc.NativeWidth(doc);
const y2 = parseFloat(values[3]) * Doc.NativeHeight(doc) + foundChunk.startPage * Doc.NativeHeight(doc);
- const annotationKey = Doc.LayoutFieldKey(doc) + '_annotations';
+ const annotationKey = '$' + Doc.LayoutDataKey(doc) + '_annotations';
- const existingDoc = DocListCast(doc[DocData][annotationKey]).find(d => d.citation_id === citation.citation_id);
+ const existingDoc = DocListCast(doc[annotationKey]).find(d => d.citation_id === citation.citation_id);
const highlightDoc = existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc);
DocumentManager.Instance.showDocument(highlightDoc, { willZoomCentered: true }, () => {});
@@ -766,7 +767,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
_height: y2 - y1,
backgroundColor: 'rgba(255, 255, 0, 0.5)',
});
- highlight_doc[DocData].citation_id = citation.citation_id;
+ highlight_doc.$citation_id = citation.citation_id;
Doc.AddDocToList(pdfDoc[DocData], annotationKey, highlight_doc);
highlight_doc.annotationOn = pdfDoc;
Doc.SetContainer(highlight_doc, pdfDoc);
@@ -866,7 +867,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return LinkManager.Instance.getAllRelatedLinks(this.Document)
.map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
.map(d => DocCast(d?.annotationOn, d))
- .filter(d => d);
+ .filter(d => d)
+ .map(d => d!);
}
/**
@@ -878,6 +880,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
.map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
.map(d => DocCast(d?.annotationOn, d))
.filter(d => d)
+ .map(d => d!)
.filter(d => {
console.log(d.ai_doc_id);
return d.ai_doc_id;
@@ -894,15 +897,14 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
LinkManager.Instance.getAllRelatedLinks(this.Document)
.map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
.map(d => DocCast(d?.annotationOn, d))
- .filter(d => d)
- .filter(d => d.summary)
+ .filter(d => d?.summary)
.map((doc, index) => {
- if (PDFCast(doc.data)) {
- return `<summary file_name="${PDFCast(doc.data).url.pathname}" applicable_tools=["rag"]>${doc.summary}</summary>`;
- } else if (CsvCast(doc.data)) {
- return `<summary file_name="${CsvCast(doc.data).url.pathname}" applicable_tools=["dataAnalysis"]>${doc.summary}</summary>`;
+ if (PDFCast(doc?.data)) {
+ return `<summary file_name="${PDFCast(doc!.data)!.url.pathname}" applicable_tools=["rag"]>${doc!.summary}</summary>`;
+ } else if (CsvCast(doc?.data)) {
+ return `<summary file_name="${CsvCast(doc!.data)!.url.pathname}" applicable_tools=["dataAnalysis"]>${doc!.summary}</summary>`;
} else {
- return `${index + 1}) ${doc.summary}`;
+ return `${index + 1}) ${doc?.summary}`;
}
})
.join('\n') + '\n'
diff --git a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
index 05482a66e..42a7747d3 100644
--- a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
+++ b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
@@ -40,7 +40,10 @@ export class GetDocsTool extends BaseTool<GetDocsToolParamsType> {
}
async execute(args: ParametersType<GetDocsToolParamsType>): Promise<Observation[]> {
- const docs = args.document_ids.map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id)));
+ const docs = args.document_ids
+ .map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id)))
+ .filter(d => d)
+ .map(d => d!);
const collection = Docs.Create.FreeformDocument(docs, { title: args.title });
this._docView._props.addDocTab(collection, OpenWhere.addRight);
return [{ type: 'text', text: `Collection created in Dash called ${args.title}` }];
diff --git a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
index afd34f28d..6d524e40f 100644
--- a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
+++ b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
@@ -15,7 +15,6 @@ import { Networking } from '../../../../Network';
import { AI_Document, CHUNK_TYPE, RAGChunk } from '../types/types';
import OpenAI from 'openai';
import { Embedding } from 'openai/resources';
-import { PineconeEnvironmentVarsNotSupportedError } from '@pinecone-database/pinecone/dist/errors';
dotenv.config();
@@ -42,7 +41,8 @@ export class Vectorstore {
constructor(id: string, doc_ids: () => string[]) {
const pineconeApiKey = process.env.PINECONE_API_KEY;
if (!pineconeApiKey) {
- throw new Error('PINECONE_API_KEY is not defined.');
+ console.log('PINECONE_API_KEY is not defined - Vectorstore will be unavailable');
+ return;
}
// Initialize Pinecone and OpenAI clients with API keys from the environment.
@@ -100,7 +100,7 @@ export class Vectorstore {
} else {
// Start processing the document.
doc.ai_document_status = 'PROGRESS';
- const local_file_path: string = CsvCast(doc.data)?.url?.pathname ?? PDFCast(doc.data)?.url?.pathname ?? VideoCast(doc.data)?.url?.pathname ?? AudioCast(doc.data)?.url?.pathname;
+ const local_file_path = CsvCast(doc.data)?.url?.pathname ?? PDFCast(doc.data)?.url?.pathname ?? VideoCast(doc.data)?.url?.pathname ?? AudioCast(doc.data)?.url?.pathname;
if (!local_file_path) {
console.log('Invalid file path.');
@@ -111,13 +111,13 @@ export class Vectorstore {
let result: AI_Document & { doc_id: string };
if (isAudioOrVideo) {
console.log('Processing media file...');
- const response = await Networking.PostToServer('/processMediaFile', { fileName: path.basename(local_file_path) });
+ const response = (await Networking.PostToServer('/processMediaFile', { fileName: path.basename(local_file_path) })) as { [key: string]: unknown };
const segmentedTranscript = response.condensed;
console.log(segmentedTranscript);
- const summary = response.summary;
+ const summary = response.summary as string;
doc.summary = summary;
// Generate embeddings for each chunk
- const texts = segmentedTranscript.map((chunk: any) => chunk.text);
+ const texts = (segmentedTranscript as { text: string }[])?.map(chunk => chunk.text);
try {
const embeddingsResponse = await this.openai.embeddings.create({
@@ -137,7 +137,7 @@ export class Vectorstore {
file_name: local_file_path,
num_pages: 0,
summary: '',
- chunks: segmentedTranscript.map((chunk: any, index: number) => ({
+ chunks: (segmentedTranscript as { text: string; start: number; end: number; indexes: string[] }[]).map((chunk, index) => ({
id: uuidv4(),
values: (embeddingsResponse.data as Embedding[])[index].embedding, // Assign embedding
metadata: {
@@ -172,7 +172,7 @@ export class Vectorstore {
} else {
// Existing document processing logic remains unchanged
console.log('Processing regular document...');
- const { jobId } = await Networking.PostToServer('/createDocument', { file_path: local_file_path });
+ const { jobId } = (await Networking.PostToServer('/createDocument', { file_path: local_file_path })) as { jobId: string };
while (true) {
await new Promise(resolve => setTimeout(resolve, 2000));
@@ -296,7 +296,7 @@ export class Vectorstore {
encoding_format: 'float',
});
- let queryEmbedding = queryEmbeddingResponse.data[0].embedding;
+ const queryEmbedding = queryEmbeddingResponse.data[0].embedding;
// Extract the embedding from the response.
diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx
index ec1f7a023..871c556e6 100644
--- a/src/client/views/nodes/formattedText/DailyJournal.tsx
+++ b/src/client/views/nodes/formattedText/DailyJournal.tsx
@@ -1,14 +1,22 @@
-import { action, makeObservable, observable } from 'mobx';
+import { makeObservable, action, observable } from 'mobx';
import * as React from 'react';
-import { RichTextField } from '../../../../fields/RichTextField';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { Plugin } from 'prosemirror-state';
+import { RTFCast } from '../../../../fields/Types';
export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable journalDate: string;
+ @observable typingTimeout: NodeJS.Timeout | null = null; // Track typing delay
+ @observable lastUserText: string = ''; // Store last user-entered text
+ _ref = React.createRef<FormattedTextBox>(); // reference to the formatted textbox
+ predictiveTextRange: { from: number; to: number } | null = null; // where predictive text starts and ends
+ private predictiveText: string | null = ' ... why?';
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(DailyJournal, fieldStr);
@@ -18,12 +26,13 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
super(props);
makeObservable(this);
this.journalDate = this.getFormattedDate();
-
- console.log('Constructor: Setting initial title and text...');
- this.setDailyTitle();
- this.setDailyText();
}
+ /**
+ * Method to get the current date in standard format
+ * @returns - date in standard long format
+ */
+
getFormattedDate(): string {
const date = new Date().toLocaleDateString(undefined, {
weekday: 'long',
@@ -35,6 +44,9 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
return date;
}
+ /**
+ * Method to set the title of the node to the date
+ */
@action
setDailyTitle() {
console.log('setDailyTitle() called...');
@@ -48,43 +60,259 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
console.log('New title after update:', this.dataDoc.title);
}
+ /**
+ * Method to set the standard text of the node (to the current date)
+ */
@action
setDailyText() {
- console.log('setDailyText() called...');
const placeholderText = 'Start writing here...';
- const initialText = `Journal Entry - ${this.journalDate}\n${placeholderText}`;
+ const dateText = `${this.journalDate}\n`;
console.log('Checking if dataDoc has text field...');
- const styles = {
- bold: true, // Make the journal date bold
- color: 'blue', // Set the journal date color to blue
- fontSize: 18, // Set the font size to 18px for the whole text
- };
-
- console.log('Setting new text field with:', initialText);
- this.dataDoc[this.fieldKey] = RichTextField.textToRtf(
- initialText,
- undefined, // No image DocId
- styles, // Pass the styles object here
- placeholderText.length // The position for text selection
+ this.dataDoc[this.fieldKey] = RichTextField.textToRtfFormat(
+ [
+ { text: 'Journal Entry:', styles: { bold: true, color: 'black', fontSize: 20 } },
+ { text: dateText, styles: { italic: true, color: 'gray', fontSize: 15 } },
+ { text: placeholderText, styles: { fontSize: 14, color: 'gray' } },
+ ],
+ undefined,
+ placeholderText.length
);
console.log('Current text field:', this.dataDoc[this.fieldKey]);
}
+ /**
+ * Tracks user typing text inout into the node, to call the insert predicted
+ * text function when appropriate (i.e. when the user stops typing)
+ */
+
+ @action onTextInput = () => {
+ const editorView = this._ref.current?.EditorView;
+ if (!editorView) return;
+
+ if (this.typingTimeout) clearTimeout(this.typingTimeout);
+
+ this.typingTimeout = setTimeout(() => {
+ this.insertPredictiveQuestion();
+ }, 3500);
+ };
+
+ /**
+ * Inserts predictive text at the end of what the user is typing
+ */
+
+ @action insertPredictiveQuestion = async () => {
+ const editorView = this._ref.current?.EditorView;
+ if (!editorView) return;
+
+ const { state, dispatch } = editorView;
+ const { schema } = state;
+ const { to } = state.selection;
+ const insertPos = to; // cursor position
+
+ const resolvedPos = state.doc.resolve(insertPos);
+ const parentNode = resolvedPos.parent;
+ const indexInParent = resolvedPos.index();
+ const isAtEndOfParent = indexInParent >= parentNode.childCount;
+
+ // Check if there's a line break or paragraph node after the current position
+ let hasNewlineAfter = false;
+ try {
+ const nextNode = parentNode.child(indexInParent);
+ hasNewlineAfter = nextNode.type.name === schema.nodes.hard_break.name || nextNode.type.name === schema.nodes.paragraph.name;
+ } catch {
+ hasNewlineAfter = false;
+ }
+
+ // Only insert if we're at end of node, or there's a newline node after
+ if (!isAtEndOfParent && !hasNewlineAfter) return;
+
+ const fontSizeMark = schema.marks.pFontSize.create({ fontSize: '14px' });
+ const fontColorMark = schema.marks.pFontColor.create({ fontColor: 'lightgray' });
+ const fontItalicsMark = schema.marks.em.create();
+
+ this.predictiveText = ' ...'; // placeholder for now
+
+ const fullTextUpToCursor = state.doc.textBetween(0, state.selection.to, '\n', '\n');
+ const gptPrompt = `Given the following incomplete journal entry, generate a single 2-5 word question that continues the user's thought:\n\n"${fullTextUpToCursor}"`;
+ const res = await gptAPICall(gptPrompt, GPTCallType.COMPLETION);
+ if (!res) return;
+
+ // styled text node
+ const text = ` ... ${res.trim()}`;
+ const predictedText = schema.text(text, [fontSizeMark, fontColorMark, fontItalicsMark]);
+
+ // Insert styled text at cursor position
+ const transaction = state.tr.insert(insertPos, predictedText).setStoredMarks([state.schema.marks.pFontColor.create({ fontColor: 'gray' })]); // should probably instead inquire marks before predictive prompt
+ dispatch(transaction);
+
+ this.predictiveText = text;
+ };
+
+ createPredictiveCleanupPlugin = () => {
+ return new Plugin({
+ view: () => {
+ return {
+ update: (view, prevState) => {
+ const { state, dispatch } = view;
+ if (!this.predictiveText) return;
+
+ // Check if doc or selection changed
+ if (!prevState.doc.eq(state.doc) || !prevState.selection.eq(state.selection)) {
+ const found = false;
+ const textToRemove = this.predictiveText;
+
+ state.doc.descendants((node, pos) => {
+ if (node.isText && node.text === textToRemove) {
+ const tr = state.tr.delete(pos, pos + node.nodeSize);
+
+ // Set the desired default marks for future input
+ const fontSizeMark = state.schema.marks.pFontSize.create({ fontSize: '14px' });
+ const fontColorMark = state.schema.marks.pFontColor.create({ fontColor: 'gray' });
+ tr.setStoredMarks([]);
+ tr.setStoredMarks([fontSizeMark, fontColorMark]);
+
+ dispatch(tr);
+
+ this.predictiveText = null;
+ return false;
+ }
+ return true;
+ });
+
+ if (!found) {
+ // fallback cleanup
+ this.predictiveText = null;
+ }
+ }
+ },
+ };
+ },
+ });
+ };
+
componentDidMount(): void {
console.log('componentDidMount() triggered...');
- // bcz: This should be moved into Docs.Create.DailyJournalDocument()
- // otherwise, it will override all the text whenever the note is reloaded
- this.setDailyTitle();
- this.setDailyText();
+ console.log('Text: ' + RTFCast(this.Document.text)?.Text);
+
+ const editorView = this._ref.current?.EditorView;
+ if (editorView) {
+ editorView.dom.addEventListener('input', this.onTextInput);
+
+ // Add plugin to state if not already added
+ const cleanupPlugin = this.createPredictiveCleanupPlugin();
+ this._ref.current?.addPlugin(cleanupPlugin);
+ }
+
+ const rawText = RTFCast(this.Document.text)?.Text ?? '';
+ const isTextEmpty = !rawText || rawText === '';
+
+ const currentTitle = this.dataDoc.title || '';
+ const isTitleString = typeof currentTitle === 'string';
+ const isDefaultTitle = isTitleString && currentTitle.includes('Untitled DailyJournal');
+
+ if (isTextEmpty && isDefaultTitle) {
+ console.log('Journal title and text are default. Initializing...');
+ this.setDailyTitle();
+ this.setDailyText();
+ } else {
+ console.log('Journal already has content. Skipping initialization.');
+ }
}
+ componentWillUnmount(): void {
+ const editorView = this._ref.current?.EditorView;
+ if (editorView) {
+ editorView.dom.removeEventListener('input', this.onTextInput);
+ }
+ if (this.typingTimeout) clearTimeout(this.typingTimeout);
+ }
+
+ @action handleGeneratePrompts = async () => {
+ const rawText = RTFCast(this.Document.text)?.Text ?? '';
+ console.log('Extracted Journal Text:', rawText);
+ console.log('Before Update:', this.Document.text, 'Type:', typeof this.Document.text);
+
+ if (!rawText.trim()) {
+ alert('Journal is empty! Write something first.');
+ return;
+ }
+
+ try {
+ // Call GPT API to generate prompts
+ const res = await gptAPICall('Generate 1-2 short journal prompts for the following journal entry: ' + rawText, GPTCallType.COMPLETION);
+
+ if (!res) {
+ console.error('GPT call failed.');
+ return;
+ }
+
+ const editorView = this._ref.current?.EditorView;
+ if (!editorView) {
+ console.error('EditorView is not available.');
+ return;
+ } else {
+ const { state, dispatch } = editorView;
+ const { schema } = state;
+
+ // Use available marks
+ const boldMark = schema.marks.strong.create();
+ const italicMark = schema.marks.em.create();
+ const fontSizeMark = schema.marks.pFontSize.create({ fontSize: '14px' });
+ const fontColorMark = schema.marks.pFontColor.create({ fontColor: 'gray' });
+
+ // Create text nodes with formatting
+ const headerText = schema.text('\n\n# Suggested Prompts:\n', [boldMark, italicMark, fontSizeMark, fontColorMark]);
+ const responseText = schema.text(res, [fontSizeMark, fontColorMark]);
+
+ // Insert formatted text
+ const transaction = state.tr.insert(state.selection.from, headerText).insert(state.selection.from + headerText.nodeSize, responseText);
+ dispatch(transaction);
+ }
+ } catch (err) {
+ console.error('Error calling GPT:', err);
+ }
+ };
+
render() {
return (
- <div style={{ background: 'beige', width: '100%', height: '100%' }}>
- <FormattedTextBox {...this._props} fieldKey={'text'} Document={this.Document} TemplateDataDocument={undefined} />
+ <div
+ style={{
+ // background: 'beige',
+ width: '100%',
+ height: '100%',
+ backgroundColor: 'beige',
+ backgroundImage: `
+ repeating-linear-gradient(
+ to bottom,
+ rgba(255, 26, 26, 0.2) 0px, rgba(255, 26, 26, 0.2) 1px, /* Thin red stripes */
+ transparent 1px, transparent 20px
+ )
+ `,
+ backgroundSize: '100% 20px',
+ backgroundRepeat: 'repeat',
+ }}>
+ {/* GPT Button */}
+ <button
+ style={{
+ position: 'absolute',
+ bottom: '5px',
+ right: '5px',
+ padding: '5px 10px',
+ backgroundColor: '#9EAD7C',
+ color: 'white',
+ border: 'none',
+ borderRadius: '5px',
+ cursor: 'pointer',
+ zIndex: 10,
+ }}
+ onClick={this.handleGeneratePrompts}>
+ Prompts
+ </button>
+
+ <FormattedTextBox ref={this._ref} {...this._props} fieldKey={'text'} Document={this.Document} TemplateDataDocument={undefined} />
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 78bbb520e..3734ad9cc 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -3,7 +3,7 @@
.dashFieldView-active,
.dashFieldView {
position: relative;
- display: inline-flex;
+ display: contents;
align-items: center;
.dashFieldView-enumerables {
@@ -26,6 +26,7 @@
display: inline-block;
font-weight: normal;
background: rgba(0, 0, 0, 0.1);
+ align-content: center;
cursor: default;
}
.dashFieldView-fieldSpan {
@@ -33,12 +34,18 @@
margin-left: 2px;
margin-right: 5px;
padding-left: 2px;
- display: inline-block;
- background-color: rgba(155, 155, 155, 0.24);
+ font-size: smaller;
+ display: contents;
+ > div {
+ background-color: rgba(155, 155, 155, 0.24);
+ }
span {
user-select: all;
min-width: 100%;
display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 100%;
}
}
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 18cf36603..7ea5d1fcf 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,8 +1,10 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import { Node } from 'prosemirror-model';
import { NodeSelection } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
@@ -13,6 +15,7 @@ import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { Cast, DocCast } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
+import { DocumentOptions, FInfo } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
import { undoable, undoBatch } from '../../../util/UndoManager';
@@ -23,8 +26,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../OpenWhere';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { Node } from 'prosemirror-model';
-import { EditorView } from 'prosemirror-view';
@observer
export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -98,7 +99,6 @@ interface IDashFieldViewInternal {
width: number;
height: number;
editable: boolean;
- nodeSelected: () => boolean;
node: Node;
getPos: () => number;
unclickable: () => boolean;
@@ -111,7 +111,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
_fieldKey: string;
_fieldRef = React.createRef<HTMLDivElement>();
@observable _dashDoc: Doc | undefined = undefined;
- @observable _expanded = this._props.nodeSelected();
+ @observable _expanded = false;
constructor(props: IDashFieldViewInternal) {
super(props);
@@ -139,7 +139,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
componentWillUnmount() {
this._reactionDisposer?.();
}
- isRowActive = () => (this._props.nodeSelected() || this._expanded) && this._props.editable;
+ isRowActive = () => this._props.tbox._props.isContentActive() && this._props.editable;
finishEdit = action(() => {
if (this._expanded) {
this._expanded = false;
@@ -148,24 +148,27 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
}
});
- selectedCells = () => (this._dashDoc ? [this._dashDoc] : undefined);
+ selectedCells = () => (this._dashDoc && this._expanded ? [this._dashDoc] : undefined);
columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey
+ finfo = (fieldKey: string) => (new DocumentOptions() as Record<string, FInfo>)[fieldKey];
+
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
<div
+ className="dashFieldView-fieldSpan"
onPointerDown={action(() => {
this._expanded = !this._props.editable ? false : !this._expanded;
- })}
- style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
+ })}>
<SchemaTableCell
- Document={this._dashDoc}
+ Doc={this._dashDoc}
col={0}
deselectCell={emptyFunction}
- selectCell={emptyFunction}
+ selectCell={() => (this._expanded ? true : undefined)}
+ autoFocus={true}
maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
- columnWidth={this._expanded || this._props.nodeSelected() ? this.columnWidth : returnZero}
+ columnWidth={returnZero}
selectedCells={this.selectedCells}
selectedCol={returnZero}
fieldKey={this._fieldKey}
@@ -178,14 +181,13 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
rowHeight={returnZero}
isRowActive={this.isRowActive}
padding={0}
- getFinfo={emptyFunction}
+ getFinfo={this.finfo}
setColumnValues={returnFalse}
allowCRs
- oneLine={!this._expanded && !this._props.nodeSelected()}
+ oneLine={!this._expanded && this._props.editable}
finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
- autoFocus
rootSelected={this._props.tbox._props.rootSelected}
/>
</div>
@@ -196,7 +198,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
- embedding._type_collection = CollectionViewType.Time;
+ embedding._type_collection = CollectionViewType.Pivot;
const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders';
let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField));
if (!list) {
@@ -230,7 +232,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}
@computed get _hideValue() {
- return this._props.hideValue && !this._props.nodeSelected();
+ return this._props.hideValue;
}
// clicking on the label creates a pivot view collection of all documents
@@ -252,7 +254,6 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
};
@computed get values() {
- if (this._props.nodeSelected()) return [];
const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
return vals.strings.map(facet => ({ value: facet, label: facet }));
@@ -264,7 +265,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
className={`dashFieldView${this.isRowActive() ? '-active' : ''}`}
ref={this._fieldRef}
style={{
- width: this._props.width,
+ // width: this._props.width,
height: this._props.height,
pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
@@ -274,7 +275,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
</span>
)}
{this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
- {!this.values.length ? null : (
+ {!this.values.length || !this.props.editable ? null : (
<select className="dashFieldView-select" tabIndex={-1} defaultValue={this._dashDoc && Field.toKeyValueString(this._dashDoc, this._fieldKey)} onChange={this.selectVal}>
<option value="-unset-">-unset-</option>
{this.values.map(val => (
@@ -294,8 +295,6 @@ export class DashFieldView {
node: Node;
tbox: FormattedTextBox;
getpos: () => number | undefined;
- @observable _nodeSelected = false;
- NodeSelected = () => this._nodeSelected;
unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some(m => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) {
@@ -308,26 +307,14 @@ export class DashFieldView {
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
+ this.dom.style.display = 'inline-flex';
+ this.dom.style.maxWidth = '100%';
this.dom.onkeypress = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeydown = (e: KeyboardEvent) => {
+ this.dom.onkeydown = action((e: KeyboardEvent) => {
e.stopPropagation();
- if (e.key === 'Tab') {
- e.preventDefault();
- const editor = tbox.EditorView;
- if (editor) {
- const { state } = editor;
- for (let i = getPosition() + 1; i < state.doc.content.size; i++) {
- if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
- return;
- }
- }
- }
- }
- };
+ });
this.dom.onkeyup = function (e: KeyboardEvent) {
e.stopPropagation();
};
@@ -348,7 +335,6 @@ export class DashFieldView {
hideKey={node.attrs.hideKey}
hideValue={node.attrs.hideValue}
editable={node.attrs.editable}
- nodeSelected={this.NodeSelected}
tbox={tbox}
/>
);
@@ -362,19 +348,6 @@ export class DashFieldView {
}
});
}
- deselectNode() {
- runInAction(() => {
- this._nodeSelected = false;
- });
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- setTimeout(
- action(() => {
- this._nodeSelected = true;
- }),
- 100
- );
- this.dom.classList.add('ProseMirror-selectednode');
- }
+ deselectNode() {}
+ selectNode() {}
}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index e0450b202..827db190a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -6,7 +6,6 @@ import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
import { StrCast } from '../../../../fields/Types';
import './DashFieldView.scss';
import EquationEditor from './EquationEditor';
@@ -63,9 +62,9 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
}}>
<EquationEditor
ref={this._ref}
- value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
+ value={StrCast(this._textBoxDoc['$' + this._fieldKey])}
onChange={str => {
- this._textBoxDoc[DocData][this._fieldKey] = str;
+ this._textBoxDoc['$' + this._fieldKey] = str;
}}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
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 a0eb6067e..c51f6c38b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,10 +1,10 @@
/* eslint-disable no-use-before-define */
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
+import { Property } from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { baseKeymap, selectAll } from 'prosemirror-commands';
+import { baseKeymap, selectAll, splitBlock } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
@@ -13,20 +13,19 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView, NodeViewConstructor } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, imageUrlToBase64, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, 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 { Id } from '../../../../fields/FieldSymbols';
+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';
-import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
-import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -34,7 +33,6 @@ import { DocUtils } from '../../../documents/DocUtils';
import { DictationManager } from '../../../util/DictationManager';
import { DragManager } from '../../../util/DragManager';
import { dropActionType } from '../../../util/DropActionTypes';
-import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -49,12 +47,14 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
+import { StickerPalette } from '../../smartdraw/StickerPalette';
import { StyleProp } from '../../StyleProp';
import { styleFromLayoutString } from '../../StyleProvider';
import { mediaState } from '../AudioBox';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FocusViewOptions } from '../FocusViewOptions';
+import { LabelBox } from '../LabelBox';
import { LinkInfo } from '../LinkDocPreview';
import { OpenWhere } from '../OpenWhere';
import './FormattedTextBox.scss';
@@ -64,9 +64,6 @@ import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
-import { Property } from 'csstype';
-import { LabelBox } from '../LabelBox';
-import { StickerPalette } from '../../smartdraw/StickerPalette';
// import * as applyDevTools from 'prosemirror-dev-tools';
export interface FormattedTextBoxProps extends FieldViewProps {
@@ -78,17 +75,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
- public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
+ public static MakeConfig(rules?: RichTextRules, textBox?: FormattedTextBox, plugs?: Plugin[]) {
return {
schema,
plugins: [
inputRules(rules?.inpRules ?? { rules: [] }),
- ...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
+ ...(textBox?._props ? [FormattedTextBox.richTextMenuPlugin(textBox._props)] : []),
history(),
- keymap(buildKeymap(schema, props ?? {})),
+ keymap(buildKeymap(schema, textBox)),
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({ view: () => new FormattedTextBoxComment() }),
+ ...(plugs ?? []),
],
};
}
@@ -103,12 +101,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection
private static _nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
- private static _globalHighlightsCache: string = '';
- private static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
- private static _highlightStyleSheet = addStyleSheet();
- private static _bulletStyleSheet = addStyleSheet();
- private static _userStyleSheet = addStyleSheet();
+ private _curHighlights = new ObservableSet<string>(['Audio Tags']);
+ private static _highlightStyleSheet = addStyleSheet().sheet;
+ private static _bulletStyleSheet = addStyleSheet().sheet;
+ private _userStyleSheetElement: HTMLStyleElement | undefined;
+ private _enteringStyle = false;
private _oldWheel: HTMLDivElement | null = null;
private _selectionHTML: string | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -140,30 +138,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public ApplyingChange: string = '';
@observable _showSidebar = false;
+ @observable _userPlugins: Plugin[] = [];
- @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._props); } // 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 config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this, this._userPlugins ?? []); } // 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,9 +214,8 @@ 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);
- if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
- const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
+ if (!pinProps && this.EditorView?.state.selection.empty) return this.rootDoc;
+ const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc?.title), annotationOn: this.rootDoc });
this.addDocument(anchor);
this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
@@ -226,22 +224,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return anchor;
};
- gptPDFFlashcards = async () => {
- const queryText = window.getSelection()?.toString() ?? '';
- try {
- if (queryText) {
- const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
- AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y));
- }
- } catch (err) {
- console.error(err);
- }
- };
-
@action
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
- AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards;
+ // AnchorMenu.Instance.gptFlashcards = this.selectionToFlashcards;
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
@@ -256,9 +243,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (target) {
anchor.followLinkAudio = true;
let stopFunc: () => void = emptyFunction;
- const targetData = target[DocData];
- targetData.mediaState = mediaState.Recording;
- DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+ target.$mediaState = mediaState.Recording;
+ DictationManager.recordAudioAnnotation(target, Doc.LayoutDataKey(target), stop => { stopFunc = stop }); // prettier-ignore
const reactionDisposer = reaction(
() => target.mediaState,
@@ -320,13 +306,17 @@ 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 + ':') + //
(node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
);
}
+ if (node.type === this.EditorView?.state.schema.nodes.dashDoc) {
+ const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ return refDoc[ToString]();
+ }
return '';
};
dispatchTransaction = (tx: Transaction) => {
@@ -436,8 +426,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const oldAutoLinks = Doc.Links(this.Document).filter(
link =>
((!Doc.isTemplateForField(this.Document) &&
- (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
- (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+ ((DocCast(link.link_anchor_1) && !Doc.isTemplateForField(DocCast(link.link_anchor_1)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+ ((DocCast(link.link_anchor_2) && !Doc.isTemplateForField(DocCast(link.link_anchor_2)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
(Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
@@ -449,7 +439,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
tr = this.hyperlinkTerm(tr, term, newAutoLinks);
});
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
+ const marks = tr.storedMarks;
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to))).setStoredMarks(marks);
this.EditorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
@@ -458,18 +449,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
updateTitle = () => {
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
- !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ !this._props.dontRegisterView && // only update the title if the data document's data field is changing
title.startsWith('-') &&
this.EditorView &&
!this.dataDoc.title_custom &&
- (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
+ (Doc.LayoutDataKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
) {
let node = this.EditorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
const prefix = '-';
- const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
}
@@ -595,8 +586,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
}
const dragData = de.complete.docDragData;
- if (dragData) {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ if (dragData && !this._props.rejectDrop?.(de, this.DocumentView?.())) {
+ const layoutProto = DocCast(this.layoutDoc.proto);
+ const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
const effectiveAcl = GetEffectiveAcl(dataDoc);
const draggedDoc = dragData.droppedDocuments.lastElement();
let added: Opt<boolean>;
@@ -604,7 +596,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) {
// replace text contents when dragging with Alt
if (de.altKey) {
- const fieldKey = Doc.LayoutFieldKey(draggedDoc);
+ const fieldKey = Doc.LayoutDataKey(draggedDoc);
if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
}
@@ -704,45 +696,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
updateHighlights = (highlights: string[]) => {
- if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
- setTimeout(() => {
- FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
- });
- clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (!highlights.includes('Audio Tags')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
- }
- if (highlights.includes('Text from Others')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
- }
- if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
- }
- if (highlights.includes('Todo Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
- }
- if (highlights.includes('Important Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' });
- }
- if (highlights.includes('Bold Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror strong > span', { 'font-size': 'large' }, '');
- addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
- }
- if (highlights.includes('Disagree Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' });
- }
- if (highlights.includes('Ignore Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
- }
+ const userStyleSheet = () => {
+ if (!this._userStyleSheetElement) {
+ this._userStyleSheetElement = addStyleSheet();
+ }
+ return this._userStyleSheetElement.sheet;
+ };
+ const viewId = this.DocumentView?.().ViewGuid ?? 1;
+ const userId = ClientUtils.CurrentUserEmail().replace(/\./g, '').replace('@', ''); // must match marks_rts -> user_mark's uid
+ highlights.filter(f => f !== 'Audio Tags').length && clearStyleSheetRules(userStyleSheet());
+ if (!highlights.includes('Audio Tags')) addStyleSheetRule(userStyleSheet(), `#${viewId} .audiotag`, { display: 'none' }, ''); // prettier-ignore
+ if (highlights.includes('Text from Others')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-remote`, { background: 'yellow' }, ''); // prettier-ignore
+ if (highlights.includes('My Text')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { background: 'moccasin' }, ''); // prettier-ignore
+ if (highlights.includes('Todo Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-todo`, { outline: 'black solid 1px' }, ''); // prettier-ignore
+ if (highlights.includes('Important Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-important`, { 'font-size': 'larger' }, ''); // prettier-ignore
+ if (highlights.includes('Disagree Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-disagree`, { 'text-decoration': 'line-through' }, ''); // prettier-ignore
+ if (highlights.includes('Ignore Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-ignore`, { 'font-size': '1' }, ''); // prettier-ignore
+ if (highlights.includes('Bold Text')) { addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong))`, { 'font-size': '0px' }, '');
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong)) ::after`, { content: '...', 'font-size': '5px' }, '')} // prettier-ignore
if (highlights.includes('By Recent Minute')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { opacity: '0.1' }, '');
const min = Math.round(Date.now() / 1000 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-min-` + (min - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
}
if (highlights.includes('By Recent Hour')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { opacity: '0.1' }, '');
const hr = Math.round(Date.now() / 1000 / 60 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-hr-` + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
}
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@@ -750,15 +730,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');
@@ -776,7 +762,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;
@@ -854,57 +840,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return;
}
- const changeItems: ContextMenuProps[] = [];
- changeItems.push({
- description: 'plain',
- event: undoable(() => {
- Doc.setNativeView(this.Document);
- this.layoutDoc.layout_autoHeightMargins = undefined;
- }, 'set plain view'),
- icon: 'eye',
- });
- changeItems.push({
- description: 'metadata',
- event: undoable(() => {
- this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => {
- this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
- }, 50);
- }, 'set metadata view'),
- icon: 'eye',
- });
- const noteTypesDoc = Cast(Doc.UserDoc().template_notes, Doc, null);
- DocListCast(noteTypesDoc?.data).forEach(note => {
- const icon: IconProp = StrCast(note.icon) as IconProp;
- changeItems.push({
- description: StrCast(note.title),
- event: undoable(
- () => {
- this.layoutDoc.layout_autoHeightMargins = undefined;
- Doc.setNativeView(this.Document);
- DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
- },
- `set ${StrCast(note.title)} view}`
- ),
- icon: icon,
- });
- });
const highlighting: ContextMenuProps[] = [];
const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
(Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (!FormattedTextBox._globalHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
+ description: (!this._curHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
event: action(() => {
e.stopPropagation();
- if (!FormattedTextBox._globalHighlights.has(option)) {
- FormattedTextBox._globalHighlights.add(option);
+ if (!this._curHighlights.has(option)) {
+ this._curHighlights.add(option);
} else {
- FormattedTextBox._globalHighlights.delete(option);
+ this._curHighlights.delete(option);
}
}),
- icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
+ icon: !this._curHighlights.has(option) ? 'highlighter' : 'remove-format',
})
);
const appearance = cm.findByDescription('Appearance...');
@@ -951,20 +901,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
icon: 'expand-arrows-alt',
});
- appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
-
- !Doc.noviceMode &&
- appearanceItems.push({
- description: 'Make Default Layout',
- event: () => {
- if (!this.layoutDoc.isTemplateDoc) {
- MakeTemplate(this.Document);
- }
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
- Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
- },
- icon: 'eye',
- });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
const options = cm.findByDescription('Options...');
@@ -987,14 +923,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
});
- !Doc.noviceMode &&
- optionItems.push({
- description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
- event: () => {
- this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
- },
- icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
- });
optionItems.push({
description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
@@ -1007,31 +935,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
};
- findImageTags = async () => {
- const c = this.ProseRef?.getElementsByTagName('img');
- if (c) {
- for (const i of c) {
- // console.log(canvas.toDataURL());
- // canvas.style.zIndex = '2000000';
- // document.body.appendChild(canvas);
- if (i.className !== 'ProseMirror-separator') this.getImageDesc(i.src);
- }
- }
- };
-
- getImageDesc = async (u: string) => {
- try {
- const hrefBase64 = await imageUrlToBase64(u);
- const response = await gptImageLabel(
- hrefBase64,
- 'Make flashcards out of this text and image with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' + (this.dataDoc.text as RichTextField)?.Text
- );
- AnchorMenu.Instance.transferToFlashcard(response || 'Something went wrong', NumCast(this.dataDoc['x']), NumCast(this.dataDoc['y']));
- } catch (error) {
- console.log('Error', error);
- }
- };
-
animateRes = (resIndex: number, newText: string) => {
if (resIndex < newText.length) {
const marks = this.EditorView?.state.storedMarks ?? [];
@@ -1066,14 +969,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();
}
}
@@ -1099,17 +1002,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
if (link) {
- link[DocData].isDictation = true;
- const audioanchor = Cast(link.link_anchor_2, Doc, null);
- const textanchor = Cast(link.link_anchor_1, Doc, null);
+ link.$isDictation = true;
+ const audioanchor = DocCast(link.link_anchor_2);
+ const textanchor = DocCast(link.link_anchor_1);
if (audioanchor) {
audioanchor.backgroundColor = 'tan';
const audiotag = this.EditorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
- textId: textanchor[Id],
+ textId: textanchor?.[Id] ?? '',
});
- textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
+ textanchor && (textanchor.$title = 'dictation:' + audiotag.attrs.timeCode);
const tr = this.EditorView.state.tr.insert(this.EditorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this.EditorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -1244,10 +1147,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
// 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;
- if (nh) this.layoutDoc._nativeHeight = scrollHeight;
+ resetNativeHeight = action((scrollHeight: number) => {
+ this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight;
+ if (!this.layoutDoc.isTemplateForField && NumCast(this.layoutDoc._nativeHeight)) this.layoutDoc._nativeHeight = scrollHeight;
+ });
+
+ addPlugin = (plugin: Plugin) => {
+ const editorView = this.EditorView;
+ if (editorView) {
+ this._userPlugins.push(plugin);
+ const newState = editorView.state.reconfigure({
+ plugins: [...editorView.state.plugins, plugin],
+ });
+ editorView.updateState(newState);
+ }
};
@computed get tagsHeight() {
@@ -1255,7 +1168,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.
@@ -1265,15 +1178,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
() => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss], xMargin: this.Document.xMargin, yMargin: this.Document.yMargin }),
autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
- this._disposers.highlights = reaction(
- () => Array.from(FormattedTextBox._globalHighlights).slice(),
- highlights => this.updateHighlights(highlights),
- { fireImmediately: true }
- );
- this._disposers.width = reaction(
- () => this._props.PanelWidth(),
- () => this.tryUpdateScrollHeight()
- );
+ this._disposers.highlights = reaction(() => Array.from(this._curHighlights).slice(), this.updateHighlights, { fireImmediately: true });
+ this._disposers.width = reaction(this._props.PanelWidth, this.tryUpdateScrollHeight);
this._disposers.scrollHeight = reaction(
() => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
@@ -1285,7 +1191,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
({ border, sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
- (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
+ (!Array.from(this._curHighlights).includes('Bold Text') || this._props.isSelected()) && //
layoutAutoHeight &&
newHeight &&
(newHeight !== this.layoutDoc.height || border < NumCast(this.layoutDoc.height)) &&
@@ -1294,7 +1200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._props.setHeight?.(newHeight);
}
},
- { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
+ { fireImmediately: !Array.from(this._curHighlights).includes('Bold Text') }
);
this._disposers.links = reaction(
() => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
@@ -1309,8 +1215,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const dataData = this.dataDoc[this.fieldKey];
const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
const dataTime = dataData ? (DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
- const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
- const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
@@ -1349,7 +1255,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
}
this.prepareForTyping();
- if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ if (this._curHighlights.has('Bold Text')) {
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
if (((RichTextMenu.Instance?.view === this.EditorView && this.EditorView) || this.isLabel) && !selected) {
@@ -1365,14 +1271,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),
@@ -1406,7 +1312,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// } catch (err) {
// console.log('Drop failed', err);
// }
- this.addDocument?.(DocCast(this.Document.image));
+ DocCast(this.Document.image) && this.addDocument?.(DocCast(this.Document.image)!);
}
//if (this.Document.image) this.addDocument?.(DocCast(this.Document.image));
@@ -1429,7 +1335,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else if (!separated && node.isBlock) {
text += '\n';
separated = true;
- } else if (node.type.name === 'hard_break') {
+ } else if (node.type.name === schema.nodes.hard_break.name) {
text += '\n';
}
},
@@ -1438,8 +1344,62 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return text;
};
- handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
- const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
+ handlePaste = (view: EditorView, event: ClipboardEvent /* , slice: Slice */): boolean => {
+ return this.doPaste(view, event.clipboardData);
+ };
+ doPaste = (view: EditorView, data: DataTransfer | null) => {
+ const html = data?.getData('text/html');
+ const pdfAnchorId = data?.getData('dash/pdfAnchor');
+ if (html && !pdfAnchorId) {
+ const replaceDivsWithParagraphs = (expr: string) => {
+ // Create a temporary DOM container
+ const container = document.createElement('div');
+ container.innerHTML = expr;
+
+ // Recursive function to process all divs
+ function processDivs(node: HTMLElement) {
+ // Get all div elements in the current node (live collection)
+ const divs = node.getElementsByTagName('div');
+
+ // We need to convert to array because we'll be modifying the DOM
+ const divsArray = Array.from(divs);
+
+ for (const div of divsArray) {
+ // Create replacement paragraph
+ const p = document.createElement('p');
+
+ // Copy all attributes
+ for (const attr of div.attributes) {
+ p.setAttribute(attr.name, attr.value);
+ }
+
+ // Move all child nodes
+ while (div.firstChild) {
+ p.appendChild(div.firstChild);
+ }
+
+ // Replace the div with the paragraph
+ div.parentNode?.replaceChild(p, div);
+
+ // Process any nested divs that were moved into the new paragraph
+ processDivs(p);
+ }
+ }
+
+ // Start processing from the container
+ processDivs(container);
+
+ return container.innerHTML;
+ };
+ const fixedHTML = replaceDivsWithParagraphs(html);
+ // .replace(/<div\b([^>]*)>(.*?)<\/div>/g, '<p$1>$2</p>'); // prettier-ignore
+ this._inDrop = true;
+ view.pasteHTML(html.split('<p').length < 2 ? fixedHTML : html);
+ this._inDrop = false;
+
+ return true;
+ }
+
return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
};
@@ -1490,42 +1450,57 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
_didScroll = false;
_scrollStopper: undefined | (() => void);
+ scrollToSelection = () => {
+ if (this.EditorView && this._ref.current) {
+ const editorView = this.EditorView;
+ const docPos = editorView.coordsAtPos(editorView.state.selection.to);
+ const viewRect = this._ref.current.getBoundingClientRect();
+ const scrollRef = this._scrollRef;
+ const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
+ const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
+ if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
+ const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
+ const scrollPos = scrollRef.scrollTop + shift * this.ScreenToLocalBoxXf().Scale;
+ if (this._focusSpeed !== undefined) {
+ setTimeout(() => {
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ });
+ } else {
+ scrollRef.scrollTo({ top: scrollPos });
+ }
+ this._didScroll = true;
+ }
+ }
+ return true;
+ };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]);
const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
this.EditorView?.destroy();
+ const edState = () => {
+ try {
+ return rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config);
+ } catch {
+ return EditorState.create(config);
+ }
+ };
this._editorView = new EditorView(this.ProseRef, {
- state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
- handleScrollToSelection: editorView => {
- const docPos = editorView.coordsAtPos(editorView.state.selection.to);
- const viewRect = this._ref.current!.getBoundingClientRect();
- const scrollRef = this._scrollRef;
- const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
- const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
- if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
- const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
- const scrollPos = scrollRef.scrollTop + shift * this.ScreenToLocalBoxXf().Scale;
- if (this._focusSpeed !== undefined) {
- setTimeout(() => {
- scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
- });
- } else {
- scrollRef.scrollTo({ top: scrollPos });
- }
- this._didScroll = true;
- }
- return true;
- },
+ state: edState(),
+ handleScrollToSelection: this.scrollToSelection,
dispatchTransaction: this.dispatchTransaction,
nodeViews: FormattedTextBox._nodeViews(this),
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
+ // bcz: major hack! a patch to prosemirror broke scrolling to selection when the selection is not a dom selection
+ // this replaces prosemirror's scrollToSelection function with Dash's
+ (this.EditorView as unknown as { scrollToSelection: unknown }).scrollToSelection = this.scrollToSelection;
const { state } = this._editorView;
if (!rtfField) {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const layoutProto = DocCast(this.layoutDoc.proto);
+ const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
const textAlign = StrCast(this.dataDoc[this.fieldKey + '_align'], StrCast(Doc.UserDoc().textAlign)) || 'left';
if (textAlign !== 'left') {
@@ -1541,34 +1516,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? 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);
FormattedTextBox.SelectOnLoadChar = '';
}
if (this.EditorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
- const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined;
+ const { $from } = this.EditorView.state.selection;
const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
- let { tr } = this.EditorView.state;
- if (selLoadChar) {
- const tr1 = this.EditorView.state.tr.setStoredMarks(storedMarks);
- tr = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1);
- }
- this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size - 1))).setStoredMarks(storedMarks));
+ if (selLoadChar === 'Enter') {
+ splitBlock(this.EditorView.state, (tx3: Transaction) => this.EditorView?.dispatch(tx3.setStoredMarks(storedMarks)));
+ } else if (selLoadChar) {
+ this.EditorView.dispatch(this.EditorView.state.tr.replaceSelectionWith(this.EditorView.state.schema.text(selLoadChar, storedMarks))); // prettier-ignore
+ } else this.EditorView.dispatch(this.EditorView.state.tr.setStoredMarks(storedMarks));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
- console.log(this.EditorView.state);
}
if (selectOnLoad) {
this.EditorView!.focus();
}
if (this._props.isContentActive()) this.prepareForTyping();
if (this.EditorView && FormattedTextBox.PasteOnLoad) {
- const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+ this.doPaste(this.EditorView, FormattedTextBox.PasteOnLoad.clipboardData);
FormattedTextBox.PasteOnLoad = undefined;
- pdfAnchorId && this.addPdfReference(pdfAnchorId);
}
if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
}
@@ -1586,9 +1558,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
componentWillUnmount() {
- if (this._recordingDictation) {
- this._recordingDictation = !this._recordingDictation;
+ if (this.recordingDictation) {
+ this.recordingDictation = !this.recordingDictation;
}
+ removeStyleSheet(this._userStyleSheetElement);
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
@@ -1622,7 +1595,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;
@@ -1652,7 +1625,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
while (clickTarget instanceof HTMLElement && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
const dataset = clickTarget instanceof HTMLElement ? clickTarget?.dataset : undefined;
- if (dataset?.targethrefs)
+ if (dataset?.targethrefs && !dataset.targethrefs.startsWith('/doc'))
window
.open(
dataset?.targethrefs
@@ -1780,8 +1753,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
}
}
@@ -1835,13 +1808,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const { state } = _editorView;
if (!state.selection.empty && e.key === '%') {
- this._rules!.EnteringStyle = true;
+ this._enteringStyle = true;
StopEvent(e);
return;
}
+ if (this._enteringStyle && 'tix!'.includes(e.key)) {
+ const tag = e.key === 't' ? 'todo' : e.key === 'i' ? 'ignore' : e.key === 'x' ? 'disagree' : e.key === '!' ? 'important' : '??';
+ const node = state.selection.$from.nodeAfter;
+ const start = state.selection.from;
+ const end = state.selection.to;
+
+ if (node) {
+ StopEvent(e);
+ _editorView.dispatch(
+ state.tr
+ .removeMark(start, end, schema.marks.user_mark)
+ .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+ .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag, modified: Math.round(Date.now() / 1000 / 60) }))
+ );
+ return;
+ }
+ }
- if (state.selection.empty || !this._rules!.EnteringStyle) {
- this._rules!.EnteringStyle = false;
+ if (state.selection.empty || !this._enteringStyle) {
+ this._enteringStyle = false;
}
for (let i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
@@ -1905,10 +1895,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...
@@ -1917,7 +1907,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);
@@ -1925,17 +1915,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 =>
@@ -1945,7 +1935,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
returnFalse,
emptyFunction,
action(() => {
- this._recordingDictation = !this._recordingDictation;
+ this.recordingDictation = !this.recordingDictation;
})
)
}>
@@ -1953,6 +1943,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;
@@ -1967,6 +1971,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
backgroundColor,
color,
opacity: annotated ? 1 : undefined,
+ transform: `scale(${this.uiBtnScaling})`,
}}>
<FontAwesomeIcon icon="comment-alt" />
</div>
@@ -1980,7 +1985,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
<SidebarAnnos
ref={this._sidebarRef}
{...this._props}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth
@@ -2011,7 +2016,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}
@@ -2028,7 +2033,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>
);
@@ -2076,7 +2081,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) => {
@@ -2086,7 +2091,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
@@ -2103,26 +2108,19 @@ 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(), view._props.PanelHeight() / view.screenToLocalScale()]; // 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} />
) : styleFromLayout?.height === '0px' ? null : (
<div
className="formattedTextBox"
- ref={r => {
- this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
- this._oldWheel = r;
- r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
- }}
+ ref={r => this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel)}
style={{
...(this._props.dontScale
? {}
@@ -2132,7 +2130,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,
@@ -2165,13 +2162,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),
@@ -2185,7 +2182,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/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 6c0eac103..1d790f5bb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -134,20 +134,16 @@ export class FormattedTextBoxComment {
// this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
if (state.selection.$from && hrefs?.length) {
- const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
- const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- // nbef &&
- naft &&
- LinkInfo.SetLinkInfo({
- DocumentView: textBox.DocumentView,
- styleProvider: textBox._props.styleProvider,
- linkSrc: textBox.Document,
- linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
- location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
- hrefs,
- showHeader: true,
- noPreview,
- });
+ LinkInfo.SetLinkInfo({
+ DocumentView: textBox.DocumentView,
+ styleProvider: textBox._props.styleProvider,
+ linkSrc: textBox.Document,
+ linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
+ location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, 0 - 1))),
+ hrefs,
+ showHeader: true,
+ noPreview,
+ });
}
}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 3c84e5a10..7ae1fc202 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,29 +1,30 @@
import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
-import { Schema } from 'prosemirror-model';
+import { MarkType, Node, Schema } from 'prosemirror-model';
import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
+import { Command, EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
import { EditorView } from 'prosemirror-view';
import { ClientUtils } from '../../../../ClientUtils';
-import { Utils } from '../../../../Utils';
+import { numberRange, Utils } from '../../../../Utils';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { GetEffectiveAcl } from '../../../../fields/util';
import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { DocumentView } from '../DocumentView';
import { OpenWhere } from '../OpenWhere';
+import { FormattedTextBox } from './FormattedTextBox';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
-export type KeyMap = { [key: string]: any };
+export type KeyMap = { [key: string]: Command };
-export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+export function updateBullets(tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) {
let mapStyle = assignedMapStyle;
- tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
+ tx2.doc.descendants((node: Node, offset: number /* , index: any */) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
- const { path } = tx2.doc.resolve(offset) as any;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ const resolved = tx2.doc.resolve(offset);
+ let depth = [0, ...numberRange(resolved.depth)].reduce((p, c, idx) => p + (resolved.node(idx).type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
@@ -32,28 +33,29 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle
}
});
return tx2;
-};
+}
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
- const keys: { [key: string]: any } = {};
+export function buildKeymap<S extends Schema<string>>(schema: S, tbox?: FormattedTextBox): KeyMap {
+ const keys: { [key: string]: Command } = {};
- function bind(key: string, cmd: any) {
+ function bind(key: string, cmd: Command) {
keys[key] = cmd;
}
function onKey(): boolean | undefined {
- // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
+ // bcz: hack -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
// eslint-disable-next-line no-restricted-globals
- return props.onKey?.(event, props);
+ return event && tbox?._props.onKey?.(event as unknown as KeyboardEvent, tbox);
}
- const canEdit = (state: any) => {
- const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
- switch (permissions) {
+ const canEdit = (state: EditorState) => {
+ if (!tbox) return true;
+ switch (GetEffectiveAcl(tbox.dataDoc)) {
case AclAugment:
{
- const prevNode = state.selection.$cursor.nodeBefore;
- const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
+ // previously used hack: (state.selection as any).$cursor.nodeBefore;
+ const prevNode = state.selection?.$anchor.nodeBefore;
+ const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : Array.from(prevNode.marks).lastElement()?.attrs.userid;
if (prevUser !== ClientUtils.CurrentUserEmail()) {
return false;
}
@@ -64,7 +66,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
return true;
};
- const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+ const toggleEditableMark = (mark: MarkType) => (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && toggleMark(mark)(state, dispatch);
// History commands
bind('Mod-z', undo);
@@ -84,13 +86,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
bind('Mod-U', toggleEditableMark(schema.marks.underline));
// Commands for lists
- bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+ bind('Ctrl-i', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch));
bind('Ctrl-Tab', () => onKey() || true);
bind('Alt-Tab', () => onKey() || true);
bind('Meta-Tab', () => onKey() || true);
bind('Meta-Enter', () => onKey() || true);
- bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
if (onKey()) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
@@ -101,13 +103,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
+ dispatch?.(tx3);
})
) {
// couldn't sink into an existing list, so wrap in a new one
const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
if (
- !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ !wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
const tx25 = updateBullets(tx2, schema);
const olNode = tx25.doc.nodeAt(range!.start)!;
const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
@@ -115,16 +117,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2)));
- dispatch(tx4);
+ dispatch?.(tx4);
})
) {
console.log('bullet promote fail');
}
}
- return undefined;
+ return false;
});
- bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Shift-Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
if (onKey()) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -134,119 +136,136 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
+ dispatch?.(tx3);
})
) {
console.log('bullet demote fail');
}
- return undefined;
+ return false;
});
// Command to create a new Tab with a PDF of all the command shortcuts
- bind('Mod-/', () => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ bind('Mod-/', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
- props.addDocTab(newDoc, OpenWhere.addRight);
+ tbox?._props.addDocTab(newDoc, OpenWhere.addRight);
+ return false;
});
// Commands to modify BlockType
- bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
- bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
- bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
+ bind('Ctrl->', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapIn(schema.nodes.blockquote)(state, dispatch));
+ bind('Alt-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch));
+ bind('Shift-Ctrl-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch));
- bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Ctrl-m', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
if (canEdit(state)) {
const tr = state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }));
- dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))));
+ dispatch?.(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))));
+ return true;
}
+ return false;
});
for (let i = 1; i <= 6; i++) {
- bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
+ bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch));
}
// Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
+ bind('Mod-_', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ if (canEdit(state)) {
+ dispatch?.(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
// Command to unselect all
- bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
- (document.activeElement as any).blur?.();
+ bind('Escape', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ dispatch?.(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+ (document.activeElement as HTMLElement)?.blur?.();
DocumentView.DeselectAll();
+ return true;
});
bind('Alt-Enter', () => onKey() || true);
bind('Ctrl-Enter', () => onKey() || true);
- bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
+ bind('Cmd-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
bind('Cmd-?', () => {
RTFMarkup.Instance.setOpen(true);
return true;
});
- bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Cmd-e', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
if (!state.selection.empty) {
const mark = state.schema.marks.summarizeInclusive.create();
const tr = state.tr.addMark(state.selection.$from.pos, state.selection.$to.pos, mark);
const content = tr.selection.content();
- tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }));
- dispatch(tr);
+ tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }, undefined, state.selection.$anchor.marks() ?? []));
+ dispatch?.(tr);
}
return true;
});
- bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- const resolved = state.doc.resolve(state.selection.from) as any;
- const { tr } = state;
- if (resolved?.parent.type.name === 'paragraph') {
- tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
+ bind('Cmd-]', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ const {
+ tr,
+ selection: { $from },
+ } = state;
+ if ($from?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'right' }, $from.parent.marks);
} else {
- const node = resolved.nodeAfter;
+ const node = $from.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
- dispatch(tr);
+ dispatch?.(tr);
return true;
});
- bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- const resolved = state.doc.resolve(state.selection.from) as any;
- const { tr } = state;
- if (resolved?.parent.type.name === 'paragraph') {
- tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
+ bind('Cmd-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ const {
+ tr,
+ selection: { $from },
+ } = state;
+ if ($from?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'center' }, $from.parent.marks);
} else {
- const node = resolved.nodeAfter;
+ const node = $from.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
- dispatch(tr);
+ dispatch?.(tr);
return true;
});
- bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- const resolved = state.doc.resolve(state.selection.from) as any;
- const { tr } = state;
- if (resolved?.parent.type.name === 'paragraph') {
- tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
+ bind('Cmd-[', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ const {
+ tr,
+ selection: { $from },
+ } = state;
+ if ($from?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'left' }, $from.parent.marks);
} else {
- const node = resolved.nodeAfter;
+ const node = $from.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
- dispatch(tr);
+ dispatch?.(tr);
return true;
});
- bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ bind('Cmd-f', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
const { tr } = state;
tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
- dispatch(
+ dispatch?.(
tr.setSelection(
new NodeSelection( // select the footnote node to open its display
tr.doc.resolve(
@@ -259,25 +278,25 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
return true;
});
- bind('Ctrl-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
+ bind('Ctrl-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+ dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+ const backspace = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView) => {
if (onKey()) return true;
if (!canEdit(state)) return true;
if (
!deleteSelection(state, (tx: Transaction) => {
- dispatch(updateBullets(tx, schema));
+ dispatch?.(updateBullets(tx, schema));
})
) {
if (
!joinBackward(state, (tx: Transaction) => {
- dispatch(updateBullets(tx, schema));
- if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
+ dispatch?.(updateBullets(tx, schema));
+ if (view?.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
// gets rid of an extra paragraph when joining two list items together.
joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
}
@@ -285,7 +304,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
) {
if (
!selectNodeBackward(state, (tx: Transaction) => {
- dispatch(updateBullets(tx, schema));
+ dispatch?.(updateBullets(tx, schema));
})
) {
return false;
@@ -299,7 +318,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
// newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
// command to break line
- const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
+ const enter = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView, once = true) => {
if (onKey()) return true;
if (!canEdit(state)) return true;
@@ -311,31 +330,31 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
!state.selection.$from.node().content.size &&
trange
) {
- dispatch(state.tr.lift(trange, depth) as any);
+ dispatch?.(state.tr.lift(trange, depth));
return true;
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!newlineInCode(state, dispatch as any)) {
- const olNode = view.state.selection.$anchor.node(-2);
- const liNode = view.state.selection.$anchor.node(-1);
+ if (!newlineInCode(state, dispatch)) {
+ const olNode = view?.state.selection.$anchor.node(-2);
+ const liNode = view?.state.selection.$anchor.node(-1);
// prettier-ignore
if (liNode?.type === schema.nodes.list_item && !liNode.textContent &&
- olNode?.type === schema.nodes.ordered_list && once && view.state.selection.$from.depth === 3)
+ olNode?.type === schema.nodes.ordered_list && once && view?.state.selection.$from.depth === 3)
{
// handles case of hitting enter at then end of a top-level empty list item - the result is to create a paragraph
- for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
+ for (let i = 0; i < 10 && view?.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
} else if (
- !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
+ !splitListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
+ dispatch?.(tx3);
// removes an extra paragraph created when selecting text across two list items or splitting an empty list item
- !once && view.dispatch(view.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));
+ !once && view?.dispatch(view?.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));
})
) {
- if (once && view.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') {
+ if (once && view?.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view?.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') {
// handles case of hitting enter on an empty list item which needs to create a second empty paragraph, then split it by calling enter() again
view.dispatch(view.state.tr.insert(view.state.selection.from, schema.nodes.paragraph.create({})));
enter(view.state, view.dispatch, view, false);
@@ -346,12 +365,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
const tonode = tx3.selection.$to.node();
if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
- dispatch(tx4);
+ dispatch?.(tx4);
}
- if (view.state.selection.$anchor.depth > 0 &&
- view.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item &&
- view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) {
+ if ((view?.state.selection.$anchor.depth ??0) > 0 &&
+ view?.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item &&
+ view?.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) {
// if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items.
enter(view.state, dispatch, view, false);
}
@@ -368,14 +387,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
// Command to create a blank space
bind('Space', () => {
- const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+ const editDoc = tbox?._props.TemplateDataDocument ?? tbox?.Document[DocData];
if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
return false;
});
- bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any));
- bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any));
- bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any));
+ bind('Alt-ArrowUp', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinUp(state, dispatch));
+ bind('Alt-ArrowDown', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinDown(state, dispatch));
+ bind('Mod-BracketLeft', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && lift(state, dispatch));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 758b4035e..c7731e812 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
@@ -148,11 +148,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._activeAlignment = this.getActiveAlignment();
this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox));
this._activeFontFamily = !activeFamilies.length
- ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial')))
+ ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutDataKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial')))
: activeFamilies.length === 1
? String(activeFamilies[0])
: 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutDataKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];
this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
@@ -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);
@@ -381,7 +382,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
});
}
} else if (this.dataDoc) {
- this.dataDoc[`${Doc.LayoutFieldKey(this.dataDoc)}_${fontField}`] = value;
+ this.dataDoc[`${Doc.LayoutDataKey(this.dataDoc)}_${fontField}`] = value;
this.updateMenu(undefined, undefined, undefined, this.dataDoc);
} else {
Doc.UserDoc()[fontField] = value;
@@ -413,20 +414,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.view.focus();
};
- insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) {
- if (state.selection.empty) return false;
- const mark = state.schema.marks.summarize.create();
- const { tr } = state;
- tr.addMark(state.selection.from, state.selection.to, mark);
- const content = tr.selection.content();
- const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
- dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
- return true;
- }
-
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/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index c332c592b..f26a75fe4 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,7 +1,6 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeType } from 'prosemirror-model';
import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -21,7 +20,6 @@ import { schema } from './schema_rts';
export class RichTextRules {
public Document: Doc;
public TextBox: FormattedTextBox;
- public EnteringStyle: boolean = false;
constructor(doc: Doc, textBox: FormattedTextBox) {
this.Document = doc;
this.TextBox = textBox;
@@ -84,9 +82,8 @@ export class RichTextRules {
// Create annotation to a field on the text document
new InputRule(/>::$/, (state, match, start, end) => {
const creator = (doc: Doc) => {
- const textDoc = this.Document[DocData];
- const numInlines = NumCast(textDoc.inlineTextCount);
- textDoc.inlineTextCount = numInlines + 1;
+ const numInlines = NumCast(this.Document.$inlineTextCount);
+ this.Document.$inlineTextCount = numInlines + 1;
const node = state.doc.resolve(start).nodeAfter;
const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
@@ -109,28 +106,25 @@ export class RichTextRules {
}),
// Create annotation to a field on the text document
new InputRule(/>>$/, (state, match, start, end) => {
- const textDoc = this.Document[DocData];
- const numInlines = NumCast(textDoc.inlineTextCount);
- textDoc.inlineTextCount = numInlines + 1;
+ const numInlines = NumCast(this.Document.$inlineTextCount);
+ this.Document.$inlineTextCount = numInlines + 1;
const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
const textDocInline = Docs.Create.TextDocument('', {
- _layout_fieldKey: inlineLayoutKey,
_width: 75,
_height: 35,
- annotationOn: textDoc,
_layout_fitWidth: true,
_layout_autoHeight: true,
- text_fontSize: '9px',
- title: 'inline comment',
});
+ textDocInline.layout_fieldKey = inlineLayoutKey;
+ textDocInline.annotationOn = this.Document[DocData];
textDocInline.title = inlineFieldKey; // give the annotation its own title
textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
- textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+ //textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
textDocInline.isDataDoc = true;
- textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
- textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
- textDoc[inlineFieldKey] = ''; // set a default value for the annotation
+ textDocInline.proto = this.Document[DocData]; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+ this.Document['$' + inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+ this.Document['$' + inlineFieldKey] = ''; // set a default value for the annotation
const node = state.doc.resolve(start).nodeAfter;
const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
@@ -145,9 +139,8 @@ export class RichTextRules {
return replaced;
}),
- // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(/(%d|d)$/, (state, match, start, end) => {
- if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ // set the First-line indent node type for the selection's paragraph
+ new InputRule(/%d$/, (state, match, start, end) => {
const pos = state.doc.resolve(start);
for (let depth = pos.depth; depth >= 0; depth--) {
const node = pos.node(depth);
@@ -160,9 +153,8 @@ export class RichTextRules {
return null;
}),
- // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(/(%h|h)$/, (state, match, start, end) => {
- if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ // set the Hanging indent node type for the current selection's paragraph
+ new InputRule(/%h$/, (state, match, start, end) => {
const pos = state.doc.resolve(start);
for (let depth = pos.depth; depth >= 0; depth--) {
const node = pos.node(depth);
@@ -175,9 +167,8 @@ export class RichTextRules {
return null;
}),
- // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(/(%q|q)$/, (state, match, start, end) => {
- if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ // set the Quoted indent node type for the current selection's paragraph
+ new InputRule(/%q$/, (state, match, start, end) => {
const pos = state.doc.resolve(start);
if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
const { node } = state.selection;
@@ -294,7 +285,8 @@ export class RichTextRules {
editor.dispatch(estate.tr.setSelection(new TextSelection(estate.doc.resolve(start), estate.doc.resolve(end - prefixLength))));
}
- DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+ const tanchor = this.TextBox.getAnchor(true);
+ tanchor && DocUtils.MakeLink(tanchor, target, { link_relationship: 'portal to:portal from' });
const teditor = this.TextBox.EditorView;
if (teditor && selection) {
@@ -319,10 +311,10 @@ export class RichTextRules {
}),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
- // [@{this,doctitle,}.fieldKey]
+ // @{this,doctitle,}.fieldKey{:,=,:=,=:=}value
+ // @{this,doctitle,}.fieldKey
new InputRule(
- /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/,
+ /(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\s/,
(state, match, start, end) => {
const docTitle = match[1].substring(1).replace(/\.$/, '');
const fieldKey = match[2];
@@ -334,18 +326,14 @@ export class RichTextRules {
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
- this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
+ this.Document['$' + fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
} else if (value) {
Doc.SetField(
this.Document,
fieldKey,
assign + value,
Doc.IsDataProto(this.Document) ? true : undefined,
- assign.includes(':=')
- ? undefined
- : (gptval: FieldResult) => {
- (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string;
- }
+ assign.includes(':=') ? undefined : (gptval: FieldResult) => (this.Document[(dataDoc ? '$' : '_') + fieldKey] = gptval as string)
);
if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
}
@@ -361,7 +349,8 @@ export class RichTextRules {
let count = 0; // ignore first return value which will be the notation that chat is pending a result
Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
if (count) {
- const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
+ this.TextBox.EditorView?.pasteText(' ' + (gptval as string), undefined);
+ const tr = this.TextBox.EditorView?.state.tr; //.insertText(' ' + (gptval as string));
tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length))));
RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true);
}
@@ -399,11 +388,11 @@ export class RichTextRules {
new InputRule(/#(@?[a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- // this.Document[DocData]['#' + tag] = '#' + tag;
- const tags = StrListCast(this.Document[DocData].tags);
+ // this.Document[['$#' + tag] = '#' + tag;
+ const tags = StrListCast(this.Document.$tags);
if (!tags.includes(tag)) {
tags.push(tag);
- this.Document[DocData].tags = new List<string>(tags);
+ this.Document.$tags = new List<string>(tags);
this.Document._layout_showTags = true;
}
const fieldView = state.schema.nodes.dashField.create({ fieldKey: tag.startsWith('@') ? tag.replace(/^@/, '') : '#' + tag });
@@ -416,22 +405,6 @@ export class RichTextRules {
// # heading
textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
- // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(/[ti!x]$/, (state, match, start, end) => {
- if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
-
- const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
- const node = state.doc.resolve(start).nodeAfter;
-
- if (node?.marks.findIndex(m => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- return node
- ? state.tr
- .removeMark(start, end, schema.marks.user_mark)
- .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
- .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
- : state.tr;
- }),
-
new InputRule(/%\(/, (state, match, start, end) => {
const node = state.doc.resolve(start).nodeAfter;
const sm = state.storedMarks?.slice() || [];
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 238267f6e..eeb604b57 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,12 +1,11 @@
import { TextSelection } from 'prosemirror-state';
-import { Fragment, Node, Slice } from 'prosemirror-model';
+import { Attrs, Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
+import { EditorView } from 'prosemirror-view';
-interface ISummaryView {}
// currently nothing needs to be rendered for the internal view of a summary.
-// eslint-disable-next-line react/prefer-stateless-function
-export class SummaryViewInternal extends React.Component<ISummaryView> {
+export class SummaryViewInternal extends React.Component<object> {
render() {
return null;
}
@@ -18,30 +17,30 @@ export class SummaryViewInternal extends React.Component<ISummaryView> {
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
dom: HTMLSpanElement; // container for label and value
- root: any;
+ root: ReactDOM.Root;
- constructor(node: any, view: any, getPos: any) {
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
this.dom = document.createElement('span');
this.dom.className = this.className(node.attrs.visibility);
- this.dom.onpointerdown = (e: any) => {
+ this.dom.onpointerdown = (e: PointerEvent) => {
this.onPointerDown(e, node, view, getPos);
};
- this.dom.onkeypress = function (e: any) {
+ this.dom.onkeypress = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeydown = function (e: any) {
+ this.dom.onkeydown = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeyup = function (e: any) {
+ this.dom.onkeyup = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onmousedown = function (e: any) {
+ this.dom.onmousedown = function (e: MouseEvent) {
e.stopPropagation();
};
const js = node.toJSON;
- node.toJSON = function (...args: any[]) {
- return js.apply(this, args);
+ node.toJSON = function (...args: unknown[]) {
+ return js.apply(this, args as []);
};
this.root = ReactDOM.createRoot(this.dom);
@@ -54,18 +53,21 @@ export class SummaryView {
}
selectNode() {}
- updateSummarizedText(start: any, view: any) {
+ updateSummarizedText(start: number, view: EditorView) {
const mtype = view.state.schema.marks.summarize;
const mtypeInc = view.state.schema.marks.summarizeInclusive;
let endPos = start;
const visited = new Set();
- for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
+ const summarized = new Set();
+ const isSummary = (node: Node) => summarized.has(node) || node.marks.find(m => m.type === mtype || m.type === mtypeInc);
+ for (let i = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
// eslint-disable-next-line no-loop-func
view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
+ isSummary(node) && Array.from(node.children).forEach(child => summarized.add(child));
if (node.isLeaf && !visited.has(node) && !skip) {
- if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+ if (summarized.has(node) || isSummary(node)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
} else skip = true;
@@ -75,21 +77,18 @@ export class SummaryView {
return TextSelection.create(view.state.doc, start, endPos);
}
- onPointerDown = (e: any, node: any, view: any, getPos: any) => {
+ onPointerDown = (e: PointerEvent, node: Node, view: EditorView, getPos: () => number | undefined) => {
const visible = !node.attrs.visibility;
- const attrs = { ...node.attrs, visibility: visible };
- let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) {
- // update summarized text and save in attrs
- textSelection = this.updateSummarizedText(getPos() + 1, view);
- attrs.text = textSelection.content();
- attrs.textslice = attrs.text.toJSON();
- }
+ const textSelection = visible //
+ ? TextSelection.create(view.state.doc, (getPos() ?? 0) + 1)
+ : this.updateSummarizedText((getPos() ?? 0) + 1, view); // update summarized text and save in attrs
+ const text = textSelection.content();
+ const attrs = { ...node.attrs, visibility: visible, ...(!visible ? { text, textslice: text.toJSON() } : {}) } as Attrs;
view.dispatch(
view.state.tr
.setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
.replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
- .setNodeMarkup(getPos(), undefined, attrs)
+ .setNodeMarkup(getPos() ?? 0, undefined, attrs)
); // update the attrs
e.preventDefault();
e.stopPropagation();
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ba8e4faed..b7dae1ca3 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -118,6 +118,7 @@ export const marks: { [index: string]: MarkSpec } = {
{
tag: 'span',
getAttrs: dom => {
+ if (!dom.style.fontSize) return false;
return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' };
},
},
@@ -132,16 +133,9 @@ export const marks: { [index: string]: MarkSpec } = {
{
tag: 'span',
getAttrs: dom => {
- const cstyle = getComputedStyle(dom);
- if (cstyle.font) {
- if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
- if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' };
- if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' };
- if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' };
- if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' };
- if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' };
- }
- return { fontFamily: '' };
+ const cstyle = dom.style.fontFamily;
+ if (!cstyle) return false;
+ return { fontFamily: cstyle };
},
},
],
@@ -155,6 +149,7 @@ export const marks: { [index: string]: MarkSpec } = {
{
tag: 'span',
getAttrs: dom => {
+ if (!dom.style.color) return false;
return { color: dom.getAttribute('color') };
},
},
@@ -171,6 +166,7 @@ export const marks: { [index: string]: MarkSpec } = {
{
tag: 'span',
getAttrs: dom => {
+ if (!dom.getAttribute('background-color')) return false;
return { fontHighlight: dom.getAttribute('background-color') };
},
},
@@ -239,13 +235,7 @@ export const marks: { [index: string]: MarkSpec } = {
{
tag: 'span',
getAttrs: p => {
- if (typeof p !== 'string') {
- const style = getComputedStyle(p);
- if (style.textDecoration === 'underline') return null;
- if (p.parentElement?.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement?.outerHTML.indexOf('text-decoration-style: solid') !== -1) {
- return null;
- }
- }
+ if (p.getAttribute('data-summarizeInclusive')) return [[{ style: 'data-summarizeInclusive: true' }]];
return false;
},
},
@@ -255,7 +245,8 @@ export const marks: { [index: string]: MarkSpec } = {
return [
'span',
{
- style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ 'data-summarizeInclusive': 'true',
+ style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210)',
},
];
},
@@ -316,9 +307,9 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
selected: { default: false },
},
- parseDOM: [{ style: 'background: yellow' }],
+ parseDOM: [{ style: 'background: lightGray' }],
toDOM: node => {
- return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }];
+ return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'lightGray'}` }];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 02ded3103..5d34afc8a 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -353,6 +353,7 @@ export const nodes: { [index: string]: NodeSpec } = {
hard_break: {
inline: true,
group: 'inline',
+ marks: '_',
selectable: false,
parseDOM: [{ tag: 'br' }],
toDOM() {
@@ -386,10 +387,6 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
{
- style: 'list-style-type=disc',
- getAttrs: () => ({ mapStyle: 'bullet' }),
- },
- {
tag: 'ol',
getAttrs: dom => {
return {
@@ -443,6 +440,7 @@ export const nodes: { [index: string]: NodeSpec } = {
mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
visibility: { default: true },
},
+ marks: '_',
content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)',
parseDOM: [
{
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 657e689bb..85bd95d15 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -24,8 +24,8 @@ import { PointerHandler } from './imageEditorUtils/PointerHandler';
import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
import { CutMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
import { DocumentView } from '../DocumentView';
-import { DocData } from '../../../../fields/DocSymbols';
import { SettingsManager } from '../../../util/SettingsManager';
+import { Upload } from '../../../../server/SharedMediaTypes';
interface GenerativeFillProps {
imageEditorOpen: boolean;
@@ -397,9 +397,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
if (newImgDoc) {
// set the image to transparent to remove the background / brushstrokes
- const docData = newImgDoc[DocData];
- docData.backgroundColor = 'transparent';
- docData.disableMixBlend = true;
+ newImgDoc.$backgroundColor = 'transparent';
+ newImgDoc.$disableMixBlend = true;
if (firstDoc) setIsFirstDoc(false);
setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
}
@@ -476,7 +475,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean /*, parent?: Doc */): Promise<Doc | undefined> => {
if (!imageRootDoc) return undefined;
const { src } = img;
- const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+ const [result] = (await Networking.PostToServer('/uploadRemoteImage', { sources: [src] })) as Upload.ImageInformation[];
const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
if (firstDoc) {
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts b/src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/ImageMeshTool.ts
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss
new file mode 100644
index 000000000..253f48f77
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.scss
@@ -0,0 +1,24 @@
+/* MeshTransformGrid.scss */
+.meshTransformGrid {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ pointer-events: none; /* Prevents interaction with the grid itself */
+ opacity: 5%;
+}
+
+.grid-line {
+ position: absolute;
+ background-color: rgba(255, 255, 255, 0.6); /* Light grid lines */
+}
+
+.control-point {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ background-color: rgba(255, 255, 255, 1); /* White control points */
+ border-radius: 50%;
+ cursor: pointer;
+ z-index: 10;
+ pointer-events: auto; /* Allows dragging of control points */
+}
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx
new file mode 100644
index 000000000..ee5c597e9
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx
@@ -0,0 +1,109 @@
+import React, { useState, useEffect } from 'react';
+import './MeshTransformGrid.scss';
+
+interface MeshTransformGridProps {
+ imageRef: React.RefObject<HTMLImageElement>; // Reference to the image element
+ gridXSize: number; // Number of X subdivisions
+ gridYSize: number; // Number of Y subdivisions
+ isInteractive: boolean; // Whether control points are interactive (can be dragged)
+}
+
+const MeshTransformGrid: React.FC<MeshTransformGridProps> = ({ imageRef, gridXSize, gridYSize, isInteractive }) => {
+ const [controlPoints, setControlPoints] = useState<any[]>([]);
+
+ // Set up control points based on image size and grid sizes
+ useEffect(() => {
+ if (imageRef.current) {
+ const { width, height, left, top } = imageRef.current.getBoundingClientRect();
+ const newControlPoints = [];
+
+ for (let i = 0; i <= gridYSize; i++) {
+ for (let j = 0; j <= gridXSize; j++) {
+ newControlPoints.push({
+ id: `${i}-${j}`,
+ x: (j * width) / gridXSize + left,
+ y: (i * height) / gridYSize + top,
+ });
+ }
+ }
+
+ setControlPoints(newControlPoints);
+ }
+ }, [imageRef, gridXSize, gridYSize]);
+
+ // Handle dragging of control points
+ const handleDrag = (e: React.MouseEvent, pointId: string) => {
+ if (!isInteractive) return; // Prevent dragging if grid is not interactive
+
+ const { clientX, clientY } = e;
+ const updatedPoints = controlPoints.map((point) => {
+ if (point.id === pointId) {
+ return { ...point, x: clientX, y: clientY };
+ }
+ return point;
+ });
+ setControlPoints(updatedPoints);
+ };
+
+ // Render grid lines between control points
+ const renderGridLines = () => {
+ const lines = [];
+ for (let i = 0; i < controlPoints.length; i++) {
+ const point = controlPoints[i];
+ const nextPoint = controlPoints[i + 1];
+
+ // Horizontal lines
+ if (nextPoint && i % (gridXSize + 1) !== gridXSize) {
+ lines.push({
+ start: { x: point.x, y: point.y },
+ end: { x: nextPoint.x, y: nextPoint.y },
+ });
+ }
+
+ // Vertical lines
+ if (i + gridXSize + 1 < controlPoints.length) {
+ const downPoint = controlPoints[i + gridXSize + 1];
+ lines.push({
+ start: { x: point.x, y: point.y },
+ end: { x: downPoint.x, y: downPoint.y },
+ });
+ }
+ }
+ return lines.map((line, index) => (
+ <div
+ key={index}
+ className="grid-line"
+ style={{
+ position: 'absolute',
+ left: `${line.start.x}px`,
+ top: `${line.start.y}px`,
+ width: `${Math.abs(line.end.x - line.start.x)}px`,
+ height: `${Math.abs(line.end.y - line.start.y)}px`,
+ border: '1px solid rgba(255, 255, 255, 0.6)',
+ }}
+ />
+ ));
+ };
+
+ return (
+ <div className="meshTransformGrid">
+ {renderGridLines()}
+
+ {controlPoints.map((point) => (
+ <div
+ key={point.id}
+ className="control-point"
+ style={{
+ left: `${point.x}px`,
+ top: `${point.y}px`,
+ transform: 'translate(-50%, -50%)',
+ }}
+ draggable={isInteractive} // Only allow dragging if interactive
+ onDrag={(e) => handleDrag(e, point.id)}
+ />
+ ))}
+ </div>
+ );
+};
+
+export default MeshTransformGrid;
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss
new file mode 100644
index 000000000..a72b2de6a
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.scss
@@ -0,0 +1,21 @@
+/* MeshTransformButton.scss */
+.meshTransformBtnContainer {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.grid-line {
+ position: absolute;
+ background-color: rgba(255, 255, 255, 0.6);
+}
+
+.control-point {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ background-color: rgba(255, 255, 255, 1);
+ border-radius: 50%;
+ cursor: pointer;
+ z-index: 10;
+}
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx
new file mode 100644
index 000000000..e580c7070
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx
@@ -0,0 +1,81 @@
+import './MeshTransformButton.scss';
+import * as React from 'react';
+import ReactLoading from 'react-loading';
+import { Button, IconButton, Type } from '@dash/components';
+import { AiOutlineInfo } from 'react-icons/ai';
+import { SettingsManager } from '../../../../util/SettingsManager';
+import MeshTransformGrid from './imageMesh';
+
+interface ButtonContainerProps {
+ onClick: () => Promise<void>;
+ loading: boolean;
+ onReset: () => void;
+ btnText: string;
+ imageWidth: number;
+ imageHeight: number;
+ gridXSize: number; // X subdivisions
+ gridYSize: number; // Y subdivisions
+}
+
+export function MeshTransformButton({ loading, onClick, onReset, btnText, imageWidth, imageHeight, gridXSize, gridYSize }: ButtonContainerProps) {
+ const [showGrid, setShowGrid] = React.useState(false);
+ const [isGridInteractive, setIsGridInteractive] = React.useState(false); // Controls the dragging of control points
+ const imageRef = React.useRef<HTMLImageElement>(null); // Reference to the image element
+
+ const handleGridToggle = () => {
+ if (showGrid) {
+ setShowGrid(false); // Hide the grid
+ setIsGridInteractive(false); // Disable control points manipulation
+ } else {
+ setShowGrid(true); // Show the grid
+ setIsGridInteractive(true); // Enable control points manipulation
+ }
+ };
+
+ return (
+ <div className="meshTransformBtnContainer">
+ <Button text="RESET" type={Type.PRIM} color={SettingsManager.userVariantColor} onClick={onReset} />
+ {loading ? (
+ <Button
+ text={btnText}
+ type={Type.TERT}
+ color={SettingsManager.userVariantColor}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
+ iconPlacement="right"
+ onClick={() => {
+ if (!loading) handleGridToggle(); // Toggle the grid visibility and control points manipulation
+ }}
+ />
+ ) : (
+ <Button
+ text={btnText}
+ type={Type.TERT}
+ color={SettingsManager.userVariantColor}
+ onClick={() => {
+ if (!loading) handleGridToggle(); // Toggle the grid visibility and control points manipulation
+ }}
+ />
+ )}
+
+ {/* The IconButton will toggle the grid */}
+ <IconButton
+ type={Type.SEC}
+ color={SettingsManager.userVariantColor}
+ tooltip="Toggle Grid"
+ icon={<AiOutlineInfo size="16px" />}
+ onClick={handleGridToggle} // Toggle the grid when clicked
+ />
+
+ {/* Only show the grid if `showGrid` is true */}
+ {showGrid && (
+ <MeshTransformGrid
+ imageRef={imageRef}
+ gridXSize={gridXSize}
+ gridYSize={gridYSize}
+ isInteractive={isGridInteractive} // Pass the interactive flag to control point manipulation
+ />
+ )}
+ <img ref={imageRef} src="your-image-source.jpg" alt="Mesh" style={{ width: imageWidth, height: imageHeight }} />
+ </div>
+ );
+}
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/scrapbook/EmbeddedDocView.tsx b/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx
new file mode 100644
index 000000000..e99bf67c7
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx
@@ -0,0 +1,52 @@
+//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
+import * as React from "react";
+import { observer } from "mobx-react";
+import { Doc } from "../../../../fields/Doc";
+import { DocumentView } from "../DocumentView";
+import { Transform } from "../../../util/Transform";
+
+interface EmbeddedDocViewProps {
+ doc: Doc;
+ width?: number;
+ height?: number;
+ slotId?: string;
+}
+
+@observer
+export class EmbeddedDocView extends React.Component<EmbeddedDocViewProps> {
+ render() {
+ const { doc, width = 300, height = 200, slotId } = this.props;
+
+ // Use either an existing embedding or create one
+ let docToDisplay = doc;
+
+ // If we need an embedding, create or use one
+ if (!docToDisplay.isEmbedding) {
+ docToDisplay = Doc.BestEmbedding(doc) || Doc.MakeEmbedding(doc);
+ // Set the container to the slot's ID so we can track it
+ if (slotId) {
+ docToDisplay.embedContainer = `scrapbook-slot-${slotId}`;
+ }
+ }
+
+ return (
+ <DocumentView
+ Document={docToDisplay}
+ renderDepth={0}
+ // Required sizing functions
+ NativeWidth={() => width}
+ NativeHeight={() => height}
+ PanelWidth={() => width}
+ PanelHeight={() => height}
+ // Required state functions
+ isContentActive={() => true}
+ childFilters={() => []}
+ ScreenToLocalTransform={() => new Transform()}
+ // Display options
+ hideDeleteButton={true}
+ hideDecorations={true}
+ hideResizeHandles={true}
+ />
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
new file mode 100644
index 000000000..6cfe9a62c
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -0,0 +1,143 @@
+import { action, makeObservable, observable } from 'mobx';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { emptyFunction } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { CollectionView } from '../../collections/CollectionView';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { DragManager } from '../../../util/DragManager';
+import { RTFCast, StrCast, toList } from '../../../../fields/Types';
+import { undoable } from '../../../util/UndoManager';
+// Scrapbook view: a container that lays out its child items in a grid/template
+export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+ @observable createdDate: string;
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ this.createdDate = this.getFormattedDate();
+
+ // ensure we always have a List<Doc> in dataDoc['items']
+ if (!this.dataDoc[this.fieldKey]) {
+ this.dataDoc[this.fieldKey] = new List<Doc>();
+ }
+ this.createdDate = this.getFormattedDate();
+ this.setTitle();
+ }
+
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(ScrapbookBox, fieldStr);
+ }
+
+ getFormattedDate(): string {
+ return new Date().toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+ }
+
+ @action
+ setTitle() {
+ const title = `Scrapbook - ${this.createdDate}`;
+ if (this.dataDoc.title !== title) {
+ this.dataDoc.title = title;
+
+ const image = Docs.Create.TextDocument('image');
+ image.accepts_docType = DocumentType.IMG;
+ const placeholder = new Doc();
+ placeholder.proto = image;
+ placeholder.original = image;
+ placeholder._width = 250;
+ placeholder._height = 200;
+ placeholder.x = 0;
+ placeholder.y = -100;
+ //placeholder.overrideFields = new List<string>(['x', 'y']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+
+ const summary = Docs.Create.TextDocument('summary');
+ summary.accepts_docType = DocumentType.RTF;
+ summary.accepts_textType = 'one line';
+ const placeholder2 = new Doc();
+ placeholder2.proto = summary;
+ placeholder2.original = summary;
+ placeholder2.x = 0;
+ placeholder2.y = 200;
+ placeholder2._width = 250;
+ //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+ this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2]);
+ }
+ }
+
+ componentDidMount() {
+ this.setTitle();
+ }
+
+ childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
+ return true; // disable dropping documents onto any child of the scrapbook.
+ };
+ rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
+ // Test to see if the dropped doc is dropped on an acceptable location (anywerhe? on a specific box).
+ // const draggedDocs = de.complete.docDragData?.draggedDocuments;
+ return false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.
+ };
+
+ filterAddDocument = (docIn: Doc | Doc[]) => {
+ const docs = toList(docIn);
+ if (docs?.length === 1) {
+ const placeholder = DocListCast(this.dataDoc[this.fieldKey]).find(d =>
+ (d.accepts_docType === docs[0].$type || // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
+ RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type)))
+ ); // prettier-ignore
+
+ if (placeholder) {
+ // ugh. we have to tell the underlying view not to add the Doc so that we can add it where we want it.
+ // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo.
+ setTimeout(
+ undoable(() => {
+ //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field])); // // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+ placeholder.proto = docs[0];
+ }, 'Scrapbook add')
+ );
+ return false;
+ }
+ }
+ return false;
+ };
+
+ render() {
+ return (
+ <div style={{ background: 'beige', width: '100%', height: '100%' }}>
+ <CollectionView
+ {...this._props} //
+ setContentViewBox={emptyFunction}
+ rejectDrop={this.rejectDrop}
+ childRejectDrop={this.childRejectDrop}
+ filterAddDocument={this.filterAddDocument}
+ />
+ {/* <div style={{ border: '1px black', borderStyle: 'dotted', position: 'absolute', top: '50%', width: '100%', textAlign: 'center' }}>Drop an image here</div> */}
+ </div>
+ );
+ }
+}
+
+// Register scrapbook
+Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
+ layout: { view: ScrapbookBox, dataField: 'items' },
+ options: {
+ acl: '',
+ _height: 200,
+ _xMargin: 10,
+ _yMargin: 10,
+ _layout_fitWidth: false,
+ _layout_autoHeight: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ _freeform_fitContentsToBox: true,
+ defaultDoubleClick: 'ignore',
+ systemIcon: 'BsImages',
+ },
+});
diff --git a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
new file mode 100644
index 000000000..ad1d308e8
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { observer } from "mobx-react-lite";
+// Import the Doc type from your actual module.
+import { Doc } from "../../../../fields/Doc";
+
+export interface ScrapbookContentProps {
+ doc: Doc;
+}
+
+// A simple view that displays a document's title and content.
+// Adjust how you extract the text if your Doc fields are objects.
+export const ScrapbookContent: React.FC<ScrapbookContentProps> = observer(({ doc }) => {
+ // If doc.title or doc.content are not plain strings, convert them.
+ const titleText = doc.title ? doc.title.toString() : "Untitled";
+ const contentText = doc.content ? doc.content.toString() : "No content available.";
+
+ return (
+ <div className="scrapbook-content">
+ <h3>{titleText}</h3>
+ <p>{contentText}</p>
+ </div>
+ );
+});
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss b/src/client/views/nodes/scrapbook/ScrapbookSlot.scss
new file mode 100644
index 000000000..ae647ad36
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookSlot.scss
@@ -0,0 +1,85 @@
+//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
+.scrapbook-slot {
+ position: absolute;
+ background-color: rgba(245, 245, 245, 0.7);
+ border: 2px dashed #ccc;
+ border-radius: 5px;
+ box-sizing: border-box;
+ transition: all 0.2s ease;
+ overflow: hidden;
+
+ &.scrapbook-slot-over {
+ border-color: #4a90e2;
+ background-color: rgba(74, 144, 226, 0.1);
+ }
+
+ &.scrapbook-slot-filled {
+ border-style: solid;
+ border-color: rgba(0, 0, 0, 0.1);
+ background-color: transparent;
+
+ &.scrapbook-slot-over {
+ border-color: #4a90e2;
+ background-color: rgba(74, 144, 226, 0.1);
+ }
+ }
+
+ .scrapbook-slot-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ }
+
+ .scrapbook-slot-placeholder {
+ text-align: center;
+ color: #888;
+ }
+
+ .scrapbook-slot-title {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ .scrapbook-slot-instruction {
+ font-size: 0.9em;
+ font-style: italic;
+ }
+
+ .scrapbook-slot-content {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ }
+
+ .scrapbook-slot-controls {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ z-index: 10;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+
+ .scrapbook-slot-remove-btn {
+ background-color: rgba(255, 255, 255, 0.8);
+ border: 1px solid #ccc;
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-size: 10px;
+
+ &:hover {
+ background-color: rgba(255, 0, 0, 0.1);
+ }
+ }
+ }
+
+ &:hover .scrapbook-slot-controls {
+ opacity: 1;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
new file mode 100644
index 000000000..2c8f93778
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
@@ -0,0 +1,28 @@
+
+//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
+export interface SlotDefinition {
+ id: string;
+ x: number; y: number;
+ defaultWidth: number;
+ defaultHeight: number;
+ }
+
+ export interface SlotContentMap {
+ slotId: string;
+ docId?: string;
+ }
+
+ export interface ScrapbookConfig {
+ slots: SlotDefinition[];
+ contents?: SlotContentMap[];
+ }
+
+ export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
+ slots: [
+ { id: "slot1", x: 10, y: 10, defaultWidth: 180, defaultHeight: 120 },
+ { id: "slot2", x: 200, y: 10, defaultWidth: 180, defaultHeight: 120 },
+ // …etc
+ ],
+ contents: []
+ };
+ \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
new file mode 100644
index 000000000..686917d9a
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
@@ -0,0 +1,25 @@
+// ScrapbookSlotTypes.ts
+export interface SlotDefinition {
+ id: string;
+ title: string;
+ x: number;
+ y: number;
+ defaultWidth: number;
+ defaultHeight: number;
+ }
+
+ export interface ScrapbookConfig {
+ slots: SlotDefinition[];
+ contents?: { slotId: string; docId: string }[];
+ }
+
+ // give it three slots by default:
+ export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
+ slots: [
+ { id: "main", title: "Main Content", x: 20, y: 20, defaultWidth: 360, defaultHeight: 200 },
+ { id: "notes", title: "Notes", x: 20, y: 240, defaultWidth: 360, defaultHeight: 160 },
+ { id: "resources", title: "Resources", x: 400, y: 20, defaultWidth: 320, defaultHeight: 380 },
+ ],
+ contents: [],
+ };
+ \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 9ab5fb1bd..11f35b8ef 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -149,7 +149,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
setIsLoading = action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
- this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
+ this.activeItem && this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
});
@computed get showEaseFunctions() {
@@ -158,7 +158,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed
get currCPoints() {
- return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
+ return EaseFuncToPoints(this.activeItem?.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
}
@computed
@@ -175,7 +175,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocListCast(this.Document[this.presFieldKey]);
}
@computed get tagDocs() {
- return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
+ return this.childDocs.map(doc => DocCast(doc.presentation_targetDoc)!).filter(doc => doc);
}
@computed get itemIndex() {
return NumCast(this.Document._itemIndex);
@@ -187,11 +187,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocCast(this.activeItem?.presentation_targetDoc);
}
public static targetRenderedDoc = (doc: Doc) => {
- const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
+ const targetDoc = DocCast(doc?.presentation_targetDoc);
return targetDoc?.layout_unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
};
@computed get scrollable() {
- if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true;
+ if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc?.type as DocumentType) || this.targetDoc?._type_collection === CollectionViewType.Stacking) return true;
return false;
}
@computed get selectedDocumentView() {
@@ -210,6 +210,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
componentDidMount() {
+ this._props.setContentViewBox?.(this);
this._disposers.pause = reaction(
() => SnappingManager.UserPanned,
() => this.pauseAutoPres()
@@ -272,8 +273,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
stopTempMedia = (targetDocField: FieldResult) => {
- const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
- if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as DocumentType)) {
+ const targetDoc = DocCast(DocCast(targetDocField)?.annotationOn) ?? DocCast(targetDocField);
+ if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc?.type as DocumentType)) {
const targMedia = DocumentView.getDocumentView(targetDoc);
targMedia?.ComponentView?.Pause?.();
}
@@ -294,13 +295,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.setIsLoading(true);
const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
const currSlideProperties = gptSlideProperties.reduce(
- (prev, key) => { prev[key] = Field.toString(this.activeItem[key]) ?? prev[key]; return prev; },
+ (prev, key) => { prev[key] = Field.toString(this.activeItem?.[key]) ?? prev[key]; return prev; },
slideDefaults); // prettier-ignore
gptTrailSlideCustomization(input, JSON.stringify(currSlideProperties))
.then(res =>
(Object.entries(JSON.parse(res)) as string[][]).forEach(([key, val]) => {
- this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]);
+ this.activeItem && (this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]));
})
)
.catch(e => console.error(e))
@@ -324,7 +325,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].presentation_groupWithUp) > 1;
if (serial) {
this.gotoDocument(nextSelected, this.activeItem, true, async () => {
- const waitTime = NumCast(this.activeItem.presentation_duration);
+ const waitTime = NumCast(this.activeItem?.presentation_duration);
await new Promise<void>(res => {
setTimeout(res, Math.max(0, waitTime));
});
@@ -345,7 +346,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
progressivizedItems = (doc: Doc) => {
const targetList = PresBox.targetRenderedDoc(doc);
if (doc.presentation_indexed !== undefined && targetList) {
- const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
+ const listItems = (Cast(targetList[Doc.LayoutDataKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutDataKey(targetList) + '_annotations']);
return listItems.filter(ldoc => !ldoc.layout_unrendered);
}
return undefined;
@@ -363,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
next = () => {
const progressiveReveal = (first: boolean) => {
const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
- if (presIndexed !== undefined) {
+ if (presIndexed !== undefined && this.activeItem) {
const listItems = this.progressivizedItems(this.activeItem);
const listItemDoc = listItems?.[presIndexed];
if (listItems && listItemDoc) {
@@ -394,7 +395,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.childDocs[this.itemIndex + 1] !== undefined) {
// Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
const slides = DocListCast(this.Document[StrCast(this.presFieldKey, 'data')]);
- const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
+ const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d) ?? d))) : this.itemIndex;
// before moving onto next slide, run the subroutines :)
const currentDoc = this.childDocs[this.itemIndex];
@@ -421,7 +422,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const { activeItem } = this;
let prevSelected = this.itemIndex;
// Functionality for group with up
- let didZoom = activeItem.presentation_movement;
+ let didZoom = activeItem?.presentation_movement;
for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].presentation_groupWithUp; prevSelected--) {
didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presentation_movement : didZoom;
}
@@ -451,7 +452,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.stopTempMedia(from.presentation_targetDoc);
}
// If next slide is audio / video 'Play automatically' then the next slide should be played
- if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.presentation_mediaStart === 'auto') {
+ if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc?.type === DocumentType.AUDIO || this.targetDoc?.type === DocumentType.VID) && this.activeItem?.presentation_mediaStart === 'auto') {
this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
@@ -515,8 +516,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
if (activeFrame !== undefined) {
const frameTime = NumCast(activeItem.presentation_transition, 500);
- const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
- const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
+ const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem?.presentation_targetDoc)?.embedContainer) : DocCast(activeItem.presentation_targetDoc);
+ const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext)?.annotationOn) : acontext;
if (context) {
const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context));
if (ffview?.childDocs) {
@@ -527,15 +528,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if ((pinDataTypes?.dataview && activeItem.config_data !== undefined) || (!pinDataTypes && activeItem.config_data !== undefined)) {
bestTarget._dataTransition = `all ${transTime}ms`;
- const fkey = Doc.LayoutFieldKey(bestTarget);
+ const fkey = Doc.LayoutDataKey(bestTarget);
const setData = bestTargetView?.ComponentView?.setData;
if (setData) setData(activeItem.config_data);
else {
- const bestTargetData = bestTarget[DocData];
- const current = bestTargetData[fkey];
- const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined;
- if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
- bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+ const current = bestTarget['$' + fkey];
+ const hash = bestTarget['$' + fkey] ? stringHash(Field.toString(bestTarget['$' + fkey] as FieldType)) : undefined;
+ if (hash) bestTarget['$' + fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
+ bestTarget['$' + fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
}
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
setTimeout(() => {
@@ -553,7 +553,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.config_clipWidth !== undefined)) {
- const fkey = '_' + Doc.LayoutFieldKey(bestTarget);
+ const fkey = '_' + Doc.LayoutDataKey(bestTarget);
if (bestTarget[fkey + '_clipWidth'] !== activeItem.config_clipWidth) {
bestTarget[fkey + '_clipWidth'] = activeItem.config_clipWidth;
changed = true;
@@ -595,11 +595,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
if (bestTarget.fillColor !== activeItem.config_fillColor) {
- bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
+ bestTarget.$fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
changed = true;
}
if (bestTarget.color !== activeItem.config_color) {
- bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
+ bestTarget.$color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
changed = true;
}
if (bestTarget.width !== activeItem.width) {
@@ -655,7 +655,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.config_annotations !== undefined)) {
- const fkey = Doc.LayoutFieldKey(bestTarget);
+ const fkey = Doc.LayoutDataKey(bestTarget);
const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.layout_unrendered);
const newItems = DocListCast(activeItem.config_annotations).map(doc => {
doc.hidden = false;
@@ -668,11 +668,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return doc;
});
const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
- bestTarget[DocData][fkey + '_annotations'] = newList;
+ bestTarget['$' + fkey + '_annotations'] = newList;
}
if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
changed = true;
- const layoutField = Doc.LayoutFieldKey(bestTarget);
+ const layoutField = Doc.LayoutDataKey(bestTarget);
const transitioned = new Set<Doc>();
StrListCast(activeItem.config_pinLayoutData)
.map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string })
@@ -689,8 +689,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
data.fill && (doc._fillColor = data.fill);
doc._width = data.w;
doc._height = data.h;
- data.data && (doc[DocData].data = field);
- data.text && (doc[DocData].text = tfield);
+ data.data && (doc.$data = field);
+ data.text && (doc.$text = tfield);
Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
}
});
@@ -740,7 +740,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const { activeItem, targetDoc } = this;
const finished = (options: FocusViewOptions) => {
afterNav?.(options);
- targetDoc[Animation] = undefined;
+ targetDoc && (targetDoc[Animation] = undefined);
};
const selViewCache = Array.from(this.selectedArray);
const dragViewCache = Array.from(this._dragArray);
@@ -756,7 +756,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
finished(options);
});
- PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
+ targetDoc && activeItem && PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
};
static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) {
@@ -806,42 +806,44 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
doHideBeforeAfter = () => {
this.childDocs.forEach((doc, index) => {
- const curDoc = Cast(doc, Doc, null);
- const tagDoc = PresBox.targetRenderedDoc(curDoc);
- const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
- let opacity = index === this.itemIndex ? 1 : undefined;
- if (curDoc.presentation_hide) {
- if (index !== this.itemIndex) {
- opacity = 1;
+ const curDoc = DocCast(doc);
+ if (curDoc) {
+ const tagDoc = PresBox.targetRenderedDoc(curDoc) ?? curDoc;
+ const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
+ let opacity = index === this.itemIndex ? 1 : undefined;
+ if (curDoc.presentation_hide) {
+ if (index !== this.itemIndex) {
+ opacity = 1;
+ }
}
- }
- const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
- if (curDoc.presentation_hideBefore && index === hidingIndBef) {
- if (index > this.itemIndex) {
- opacity = 0;
- } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
- opacity = 1;
+ const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
+ if (curDoc.presentation_hideBefore && index === hidingIndBef) {
+ if (index > this.itemIndex) {
+ opacity = 0;
+ } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
+ opacity = 1;
+ }
}
- }
- const hidingIndAft =
- itemIndexes
- .slice()
- .reverse()
- .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
- if (curDoc.presentation_hideAfter && index === hidingIndAft) {
- if (index < this.itemIndex) {
- opacity = 0;
- } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
- opacity = 1;
+ const hidingIndAft =
+ itemIndexes
+ .slice()
+ .reverse()
+ .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
+ if (curDoc.presentation_hideAfter && index === hidingIndAft) {
+ if (index < this.itemIndex) {
+ opacity = 0;
+ } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
+ opacity = 1;
+ }
}
- }
- const hidingInd = itemIndexes.find(item => item === this.itemIndex);
- if (curDoc.presentation_hide && index === hidingInd) {
- if (index === this.itemIndex) {
- opacity = 0;
+ const hidingInd = itemIndexes.find(item => item === this.itemIndex);
+ if (curDoc.presentation_hide && index === hidingInd) {
+ if (index === this.itemIndex) {
+ opacity = 0;
+ }
}
+ opacity !== undefined && (tagDoc.opacity = opacity === 1 ? undefined : opacity);
}
- opacity !== undefined && (tagDoc.opacity = opacity === 1 ? undefined : opacity);
});
};
@@ -940,7 +942,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
initializePresState = (startIndex: number) => {
this.childDocs.forEach((doc, index) => {
- const tagDoc = PresBox.targetRenderedDoc(doc);
+ const tagDoc = PresBox.targetRenderedDoc(doc) ?? doc;
if (doc.presentation_hideBefore && index > startIndex) tagDoc.opacity = 0;
if (doc.presentation_hideAfter && index < startIndex) tagDoc.opacity = 0;
if (doc.presentation_indexed !== undefined && index >= startIndex) {
@@ -969,7 +971,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc.presentation_status = PresStatus.Autoplay;
this.initializePresState(startIndex);
const func = () => {
- const delay = NumCast(this.activeItem.presentation_duration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presentation_transition);
+ const delay = NumCast(this.activeItem?.presentation_duration, this.activeItem?.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem?.presentation_transition);
this._presTimer = setTimeout(() => {
if (this.next() === false) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
this.layoutDoc.presentation_status === PresStatus.Autoplay && func();
@@ -1060,7 +1062,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return !results.some(r => !r);
};
- childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
+ childLayoutTemplate = () => Docs.Create.PresSlideDocument();
removeDocument = (doc: Doc | Doc[]) =>
!toList(doc)
.map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d))
@@ -1077,13 +1079,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@computed get listOfSelected() {
return Array.from(this.selectedArray).map((doc, index) => {
- const curDoc = Cast(doc, Doc, null);
+ const curDoc = DocCast(doc);
+ if (!curDoc) return null;
const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
if (curDoc && curDoc === this.activeItem)
return (
<div key={doc[Id]} className="selectedList-items">
<b>
- {index + 1}. {StrCast(curDoc.title)})
+ {index + 1}. {StrCast(curDoc.title)}
</b>
</div>
);
@@ -1277,7 +1280,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presCollection = collection;
const dv = DocumentView.getDocumentView(presCollection);
this.childDocs.forEach((doc, index) => {
- const tagDoc = PresBox.targetRenderedDoc(doc);
+ const tagDoc = PresBox.targetRenderedDoc(doc) ?? doc;
const srcContext = Cast(tagDoc.embedContainer, Doc, null);
const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
<div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
@@ -1526,17 +1529,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Applies the slide transiiton settings to all docs in the array
@undoBatch
applyTo = (array: Doc[]) => {
- this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
- this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
- this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
- this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
- const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
- array.forEach(curDoc => {
- curDoc.presentation_transition = pt;
- curDoc.presentation_duration = pd;
- curDoc.presentation_hideBefore = ph;
- curDoc.presentation_hideAfter = pa;
- });
+ if (this.activeItem) {
+ this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
+ this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
+ this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
+ this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
+ const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
+ array.forEach(curDoc => {
+ curDoc.presentation_transition = pt;
+ curDoc.presentation_duration = pd;
+ curDoc.presentation_hideBefore = ph;
+ curDoc.presentation_hideAfter = pa;
+ });
+ }
};
@computed get visibilityDurationDropdown() {
@@ -1673,15 +1678,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={() => {
activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
- const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
+ const tagDoc = PresBox.targetRenderedDoc(activeItem) ?? activeItem;
const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0;
// a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
// to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
- let dataField = Doc.LayoutFieldKey(tagDoc);
+ let dataField = Doc.LayoutDataKey(tagDoc);
if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations';
- if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
+ if (DocCast(activeItem.presentation_targetDoc)?.annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
}}>
Enable
@@ -1797,12 +1802,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={() => {
this.updateEffect(elem.effect, false);
this.updateEffectDirection(elem.direction);
- this.updateEffectTiming(this.activeItem, {
- type: SpringType.CUSTOM,
- stiffness: elem.stiffness,
- damping: elem.damping,
- mass: elem.mass,
- });
+ this.activeItem &&
+ this.updateEffectTiming(this.activeItem, {
+ type: SpringType.CUSTOM,
+ stiffness: elem.stiffness,
+ damping: elem.damping,
+ mass: elem.mass,
+ });
}}>
<SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
<div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
@@ -1947,8 +1953,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
formLabelPlacement="left"
closeOnSelect
items={easeItems}
- selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
- setSelectedVal={val => typeof val === 'string' && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
+ selectedVal={this.activeItem?.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
+ setSelectedVal={val => typeof val === 'string' && this.activeItem && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
dropdownType={DropdownType.SELECT}
type={Type.TERT}
/>
@@ -2145,9 +2151,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get mediaOptionsDropdown() {
const { activeItem } = this;
if (activeItem && this.targetDoc) {
- const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
+ const renderTarget = PresBox.targetRenderedDoc(this.activeItem ?? this.targetDoc) ?? this.targetDoc;
const clipStart = NumCast(renderTarget.clipStart);
- const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
+ const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutDataKey(renderTarget) + '_duration']));
const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
return (
<div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
@@ -2699,7 +2705,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presEnd =
!this.layoutDoc.presLoop &&
this.itemIndex === this.childDocs.length - 1 &&
- (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+ (this.activeItem?.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
const inOverlay = Doc.IsInMyOverlay(this.Document);
// Case 1: There are still other frames and should go through all frames before going to next slide
@@ -2892,7 +2898,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presEnd =
!this.layoutDoc.presLoop &&
this.itemIndex === this.childDocs.length - 1 &&
- (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+ (this.activeItem?.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
<div
@@ -2984,16 +2990,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/>
) : null}
</div>
- {/* {
- // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
- <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}>
- <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
- + NEW SLIDE
- </button>
- </Tooltip>
- } */}
</div>
- {/* presbox chatbox */}
{this._chatActive && <div className="presBox-chatbox" />}
</div>
);
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresSlideBox.scss
index 9ac2b5a94..9ac2b5a94 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresSlideBox.scss
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx
index a76805960..3dbb3da88 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresSlideBox.tsx
@@ -7,7 +7,7 @@ import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUt
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -25,16 +25,16 @@ import { returnEmptyDocViewList } from '../../StyleProvider';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { PresBox } from './PresBox';
-import './PresElementBox.scss';
+import './PresSlideBox.scss';
import { PresMovement } from './PresEnums';
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
*/
@observer
-export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
+export class PresSlideBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(PresElementBox, fieldKey);
+ return FieldView.LayoutString(PresSlideBox, fieldKey);
}
private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -52,7 +52,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// the presentation view that renders this slide
@computed get presBoxView() {
- return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as PresBox;
+ return this.DocumentView?.()
+ .containerViewPath?.()
+ .slice()
+ .reverse()
+ .find(dv => dv?.ComponentView instanceof PresBox)?.ComponentView as Opt<PresBox>;
}
// the presentation view document that renders this slide
@@ -63,12 +67,12 @@ 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
@computed get targetDoc() {
- return Cast(this.slideDoc.presentation_targetDoc, Doc, null) || this.slideDoc;
+ return DocCast(this.slideDoc.presentation_targetDoc, this.slideDoc)!;
}
// computes index of this presentation slide in the presBox list
@@ -114,7 +118,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return !this.slideDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
<div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
<DocumentView
- Document={PresBox.targetRenderedDoc(this.slideDoc)}
+ Document={PresBox.targetRenderedDoc(this.slideDoc) ?? this.slideDoc}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
isContentActive={this._props.isContentActive}
@@ -235,10 +239,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.clientY,
undefined,
action(() => {
- Array.from(classesToRestore).forEach(pair => {
- // eslint-disable-next-line prefer-destructuring
- pair[0].className = pair[1];
- });
+ Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
this._dragging = false;
})
);
@@ -348,7 +349,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
/// remove all videos that have been recorded from overlay (leave videso that are being recorded to avoid losing data)
static removeEveryExistingRecordingInOverlay = () => {
- Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresElementBox.videoIsRecorded(DocCast(doc.slides))) //
+ Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresSlideBox.videoIsRecorded(DocCast(doc.slides))) //
.forEach(Doc.RemFromMyOverlay);
};
@@ -363,9 +364,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
showRecording = undoable(
action((activeItem: Doc, iconClick: boolean = false) => {
// remove the overlays on switch *IF* not opened from the specific icon
- if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
+ if (!iconClick) PresSlideBox.removeEveryExistingRecordingInOverlay();
- activeItem.recording && Doc.AddToMyOverlay(DocCast(activeItem.recording));
+ DocCast(activeItem.recording) && Doc.AddToMyOverlay(DocCast(activeItem.recording)!);
}),
'show video recording'
);
@@ -373,7 +374,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
startRecording = undoable(
action((e: React.MouseEvent, activeItem: Doc) => {
e.stopPropagation();
- if (PresElementBox.videoIsRecorded(activeItem)) {
+ if (PresSlideBox.videoIsRecorded(activeItem)) {
// if we already have an existing recording
this.showRecording(activeItem, true);
// // if we already have an existing recording
@@ -382,7 +383,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// we dont have any recording
// Remove every recording that already exists in overlay view
// this is a design decision to clear to focus in on the recoding mode
- PresElementBox.removeEveryExistingRecordingInOverlay();
+ PresSlideBox.removeEveryExistingRecordingInOverlay();
// create and add a recording to the slide
// make recording box appear in the bottom right corner of the screen
@@ -412,7 +413,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
// call this.slideDoc.recurChildren() to get all the children
- // if (iconClick) PresElementBox.showVideo = false;
+ // if (iconClick) PresSlideBox.showVideo = false;
};
@computed
@@ -451,7 +452,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
);
items.push(
- <Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
+ <Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresSlideBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
<div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
<FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
</div>
@@ -621,7 +622,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
-Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, {
- layout: { view: PresElementBox, dataField: 'data' },
- options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
+Docs.Prototypes.TemplateMap.set(DocumentType.PRESSLIDE, {
+ layout: { view: PresSlideBox, dataField: 'data' },
+ options: { acl: '', title: 'presSlide', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true },
});
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 7b18974df..a5bc55221 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
export * from './PresBox';
-export * from './PresElementBox';
+export * from './PresSlideBox';
export * from './PresEnums';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 9aa8fe649..eb6516403 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,24 +1,19 @@
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult } from 'react-color';
-import ReactLoading from 'react-loading';
import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { SettingsManager } from '../../util/SettingsManager';
-import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
-import { ComparisonBox } from '../nodes/ComparisonBox';
import { DocumentView } from '../nodes/DocumentView';
-import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import './AnchorMenu.scss';
import { GPTPopup } from './GPTPopup/GPTPopup';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -73,7 +68,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public MakeTargetToggle: () => void = unimplementedFunction;
public ShowTargetTrail: () => void = unimplementedFunction;
public IsTargetToggler: () => boolean = returnFalse;
- public gptFlashcards: () => void = unimplementedFunction;
public makeLabels: () => void = unimplementedFunction;
public marqueeWidth = 0;
public marqueeHeight = 0;
@@ -98,57 +92,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* Invokes the API with the selected text and stores it in the summarized text.
* @param e pointer down event
*/
- gptSummarize = () => GPTPopup.Instance.generateSummary(this._selectedText);
-
- /*
- * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them.
- */
-
- transferToFlashcard = (text: string, x: number, y: number) => {
- ComparisonBox.createFlashcardDeck(text, 250, 200, 'data_front', 'data_back').then(
- action(newCol => {
- newCol.x = x;
- newCol.y = y;
- newCol.zIndex = 1000;
- this.addToCollection?.(newCol);
- this._loading = false;
- })
- );
- };
-
- /**
- * Creates a GPT drawing based on selected text.
- */
- gptDraw = (e: React.PointerEvent) => {
- try {
- SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation;
- runInAction(() => (this._isLoading = true));
- SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then(
- action(() => {
- this._isLoading = false;
- })
- );
- } catch (err) {
- console.error(err);
- }
+ gptAskAboutSelection = () => {
+ GPTPopup.Instance.askAIAboutSelection(this._selectedText);
+ AnchorMenu.Instance.fadeOut(true);
};
- /**
- * Defines how a GPT drawing should be added to the current document.
- */
- @undoBatch
- createDrawingAnnotation = action((drawing: Doc, opts: DrawingOptions, gptRes: string) => {
- this.AddDrawingAnnotation(drawing);
- const docData = drawing[DocData];
- docData.title = opts.text?.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
- docData.ai_drawing_input = opts.text;
- docData.ai_drawing_complexity = opts.complexity;
- docData.ai_drawing_colored = opts.autoColor;
- docData.ai_drawing_size = opts.size;
- docData.ai_drawing_data = gptRes;
- docData.ai = 'gpt';
- });
-
pointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -225,23 +173,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
{this._selectedText && (
<IconButton
- tooltip="Summarize with AI" //
- onPointerDown={this.gptSummarize}
+ tooltip="Ask AI..." //
+ onPointerDown={this.gptAskAboutSelection}
icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
color={SettingsManager.userColor}
/>
)}
{/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */}
- <IconButton tooltip="Create flashcards" onPointerDown={this.gptFlashcards} icon={<FontAwesomeIcon icon="layer-group" size="lg" />} color={SettingsManager.userColor} />
- <IconButton tooltip="Create labels" onPointerDown={this.makeLabels} icon={<FontAwesomeIcon icon="tag" size="lg" />} color={SettingsManager.userColor} />
- {this._selectedText && (
- <IconButton
- tooltip="Create drawing"
- onPointerDown={e => this.gptDraw(e)}
- icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon="paintbrush" size="lg" />}
- color={SettingsManager.userColor}
- />
- )}
+ {this.makeLabels === unimplementedFunction ? null : <IconButton tooltip="Create labels" onPointerDown={this.makeLabels} icon={<FontAwesomeIcon icon="tag" size="lg" />} color={SettingsManager.userColor} />}
{this._selectedText && RichTextMenu.Instance?.createLinkButton()}
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 1891cfd4c..e8a5235c9 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -50,7 +50,7 @@ interface IAnnotationProps extends FieldViewProps {
}
@observer
export class Annotation extends ObservableReactComponent<IAnnotationProps> {
- constructor(props: any) {
+ constructor(props: IAnnotationProps) {
super(props);
makeObservable(this);
}
@@ -58,7 +58,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
@computed get linkHighlighted() {
const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc);
- return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+ return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)!);
});
return found;
}
@@ -112,6 +112,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined);
background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor));
render() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const forceRenderHack = [this.background(), this.outline(), this.opacity()]; // forces a re-render when these change -- because RegionAnnotation doesn't do this internally..
return (
<div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}>
@@ -121,7 +122,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
.map(([x, y, width, height]) => (
<div
key={'' + x + y + width + height}
- style={{ pointerEvents: this._props.pointerEvents?.() as any }}
+ style={{ pointerEvents: this._props.pointerEvents?.() as Property.PointerEvents }}
onPointerDown={this.onPointerDown}
onContextMenu={this.onContextMenu}
onPointerEnter={() => {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index c8903e09f..bb43291ee 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -9,18 +9,16 @@ $headingHeight: 32px;
.gptPopup-summary-box {
position: fixed;
+ padding-left: 10px;
+ padding-right: 10px;
top: 115px;
left: 75px;
width: 100%;
height: 100%;
top: 0;
left: 0;
- pointer-events: none;
border-top: solid gray 20px;
border-radius: 16px;
- padding: 16px;
- padding-bottom: 0;
- padding-top: 0px;
z-index: 999;
display: flex;
flex-direction: column;
@@ -29,11 +27,13 @@ $headingHeight: 32px;
box-shadow: 0 2px 5px #7474748d;
color: $textgrey;
- .gptPopup-sortBox {
+ .gptPopup-summaryBox-content {
+ padding-right: 16px;
+ padding-left: 16px;
+ position: relative;
+ overflow: hidden;
display: flex;
flex-direction: column;
- height: calc(100% - $inputHeight - $headingHeight);
- pointer-events: all;
}
.summary-heading {
@@ -50,7 +50,6 @@ $headingHeight: 32px;
}
label {
- color: $textgrey;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
@@ -65,7 +64,9 @@ $headingHeight: 32px;
.gptPopup-content-wrapper {
padding-top: 10px;
min-height: 50px;
- height: calc(100% - 32px);
+ white-space: pre-line;
+ overflow: auto;
+ margin-bottom: 10px;
}
.inputWrapper {
@@ -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 4dc45e6a0..c45d8e052 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -31,6 +31,7 @@ import { OpenWhere } from '../../nodes/OpenWhere';
import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
import { ImageField } from '../../../../fields/URLField';
import { List } from '../../../../fields/List';
+import { ComparisonBox } from '../../nodes/ComparisonBox';
export enum GPTPopupMode {
SUMMARY, // summary of seleted document text
@@ -56,7 +57,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
private _dataJson: string = '';
private _documentDescriptions: Promise<string> | undefined; // a cache of the descriptions of all docs in the selected collection. makes it more efficient when asking GPT multiple questions about the collection.
private _sidebarFieldKey: string = '';
- private _textToSummarize: string = '';
+ private _aiReferenceText: string = '';
private _imageDescription: string = '';
private _textToDocMap = new Map<string, Doc>(); // when GPT answers with a doc's content, this helps us find the Doc
private _addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
@@ -79,7 +80,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
};
componentDidUpdate() {
- this._gptProcessing && this.setStopAnimatingResponse(false);
+ //this._gptProcessing && this.setStopAnimatingResponse(false);
}
componentDidMount(): void {
reaction(
@@ -100,14 +101,14 @@ export class GPTPopup extends ObservableReactComponent<object> {
);
}
+ @observable private _showOriginal = true;
+ @observable private _responseText: string = '';
@observable private _conversationArray: string[] = ['Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. '];
@observable private _fireflyArray: string[] = ['Hi! In this pop up, you can ask Firefly to create images. '];
@observable private _chatEnabled: boolean = false;
@action private setChatEnabled = (start: boolean) => (this._chatEnabled = start);
@observable private _gptProcessing: boolean = false;
@action private setGptProcessing = (loading: boolean) => (this._gptProcessing = loading);
- @observable private _responseText: string = '';
- @action private setResponseText = (text: string) => (this._responseText = text);
@observable private _imgUrls: string[][] = [];
@action private setImgUrls = (imgs: string[][]) => (this._imgUrls = imgs);
@observable private _collectionContext: Doc | undefined = undefined;
@@ -209,7 +210,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
generateFireflyImage = (imgDesc: string) => {
const selView = DocumentView.Selected().lastElement();
const selDoc = selView?.Document;
- if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) {
+ if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) {
const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title));
const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc;
return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc)
@@ -286,18 +287,30 @@ export class GPTPopup extends ObservableReactComponent<object> {
/**
* Completes an API call to generate a summary of the specified text
*
- * @param text the text to summarizz
+ * @param text the text to summarize
*/
- generateSummary = (text: string) => {
+ private generateSummary = action((text: string) => {
SnappingManager.SetChatVisible(true);
- this._textToSummarize = text;
- this.setMode(GPTPopupMode.SUMMARY);
+ this._showOriginal = false;
this.setGptProcessing(true);
return gptAPICall(text, GPTCallType.SUMMARY)
- .then(res => this.setResponseText(res || 'Something went wrong.'))
+ .then(action(res => (this._responseText = res || 'Something went wrong.')))
.catch(err => console.error(err))
.finally(() => this.setGptProcessing(false));
- };
+ });
+
+ /**
+ * Completes an API call to generate a summary of the specified text
+ *
+ * @param text the text to summarizz
+ */
+ askAIAboutSelection = action((text: string) => {
+ SnappingManager.SetChatVisible(true);
+ this._aiReferenceText = text;
+ this._responseText = '';
+ this._showOriginal = true;
+ this.setMode(GPTPopupMode.SUMMARY);
+ });
/**
* Completes an API call to generate an analysis of
@@ -306,14 +319,16 @@ export class GPTPopup extends ObservableReactComponent<object> {
generateDataAnalysis = () => {
this.setGptProcessing(true);
return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt)
- .then(res => {
- const json = JSON.parse(res! as string);
- const keys = Object.keys(json);
- this._correlatedColumns = [];
- this._correlatedColumns.push(json[keys[0]]);
- this._correlatedColumns.push(json[keys[1]]);
- this.setResponseText(json[keys[2]] || 'Something went wrong.');
- })
+ .then(
+ action(res => {
+ const json = JSON.parse(res! as string);
+ const keys = Object.keys(json);
+ this._correlatedColumns = [];
+ this._correlatedColumns.push(json[keys[0]]);
+ this._correlatedColumns.push(json[keys[1]]);
+ this._responseText = json[keys[2]] || 'Something went wrong.';
+ })
+ )
.catch(err => console.error(err))
.finally(() => this.setGptProcessing(false));
};
@@ -336,6 +351,24 @@ export class GPTPopup extends ObservableReactComponent<object> {
});
}
};
+ /**
+ * Create Flashcards for the selected text
+ */
+ private createFlashcards = action(
+ () =>
+ this.setGptProcessing(true) &&
+ gptAPICall(this._aiReferenceText, GPTCallType.FLASHCARD, undefined, true)
+ .then(res =>
+ ComparisonBox.createFlashcardDeck(res, 250, 200, 'data_front', 'data_back').then(
+ action(newCol => {
+ newCol.zIndex = 1000;
+ DocumentViewInternal.addDocTabFunc(newCol, OpenWhere.addRight);
+ })
+ )
+ )
+ .catch(console.error)
+ .finally(action(() => (this._gptProcessing = false)))
+ );
/**
* Creates a histogram to show the correlation relationship that was found
@@ -378,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%',
@@ -393,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%',
@@ -412,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%',
@@ -484,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 //
@@ -491,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} />
@@ -518,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>
</>
))}
@@ -528,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>
@@ -536,37 +575,86 @@ export class GPTPopup extends ObservableReactComponent<object> {
summaryBox = () => (
<>
- <div style={{ height: 'calc(100% - 60px)', overflow: 'auto' }}>
- {this.heading('SUMMARY')}
+ <div className="gptPopup-summaryBox-content">
+ <div onClick={action(() => (this._showOriginal = !this._showOriginal))}>{this.heading(this._showOriginal ? 'SELECTION' : 'SUMMARY')}</div>
<div className="gptPopup-content-wrapper">
- {!this._gptProcessing &&
- (!this._stopAnimatingResponse ? (
- <TypeAnimation
- speed={50}
- sequence={[
- this._responseText,
- () => {
- setTimeout(() => this.setStopAnimatingResponse(true), 500);
- },
- ]}
- />
+ {!this._gptProcessing && !this._stopAnimatingResponse && this._responseText ? (
+ <TypeAnimation
+ speed={50}
+ sequence={[
+ this._responseText,
+ () => {
+ setTimeout(() => this.setStopAnimatingResponse(true), 500);
+ },
+ ]}
+ />
+ ) : this._showOriginal ? (
+ this._gptProcessing ? (
+ '...generating cards...'
) : (
- this._responseText
- ))}
+ this._aiReferenceText
+ )
+ ) : (
+ this._responseText || (this._gptProcessing ? '...generating summary...' : '-no ai summary-')
+ )}
</div>
</div>
- {!this._gptProcessing && (
- <div className="btns-wrapper" style={{ position: 'absolute', bottom: 0, width: 'calc(100% - 32px)' }}>
- {this._stopAnimatingResponse ? (
- <>
- <IconButton tooltip="Generate Again" onClick={() => this.generateSummary(this._textToSummarize + ' ')} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} />
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} />
- </>
+ {this._gptProcessing ? null : (
+ <div className="btns-wrapper" style={{ position: 'relative', width: 'calc(100% - 32px)' }}>
+ {this._stopAnimatingResponse || !this._responseText ? (
+ <div style={{ display: 'flex' }}>
+ {!this._showOriginal ? (
+ <>
+ <Button
+ tooltip="Show originally selected text" //
+ text="Selection"
+ onClick={action(() => (this._showOriginal = true))}
+ 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={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ </>
+ ) : (
+ <>
+ <Button
+ tooltip="Show AI summary of original selection text (Shift+Click to regenerate)"
+ text="Summary"
+ onClick={action(e => {
+ if (e.shiftKey) {
+ this.setStopAnimatingResponse(false);
+ this._aiReferenceText += ' ';
+ this._responseText = '';
+ }
+ this.generateSummary(this._aiReferenceText);
+ })}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ <Button
+ tooltip="Create Flashcards" //
+ text="Create Flashcards"
+ onClick={this.createFlashcards}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ </>
+ )}
+ </div>
) : (
<div className="summarizing">
- <span>Summarizing</span>
+ <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>
@@ -611,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>
@@ -640,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} />
@@ -656,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)} />
)}
</>
)}
@@ -665,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.scss b/src/client/views/pdf/PDFViewer.scss
index 030251762..1aab2b853 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -29,7 +29,11 @@
.textLayer {
opacity: unset;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
+ // all these below fix issues with PDFjs
transform: scale(var(--devicePixelRatio));
+ --total-scale-factor: var(--scale-factor); // these 3 are used by PDFjs but not defined. why???
+ --scale-round-x: 1px;
+ --scale-round-y: 1px;
}
[data-main-rotation='90'] {
transform: scale(var(--devicePixelRatio)) rotate(90deg) translateY(-100%);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 167421a4a..fc2567fbc 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,9 +1,10 @@
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
+import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf.mjs';
import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
-import 'pdfjs-dist/webpack.mjs'; // sets the PDF workerSrc
import * as React from 'react';
+import ReactLoading from 'react-loading';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData, Height } from '../../../fields/DocSymbols';
@@ -11,9 +12,10 @@ import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, numberRange } from '../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction } from '../../../Utils';
import { DocUtils } from '../../documents/DocUtils';
import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -28,13 +30,13 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
-import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
-import ReactLoading from 'react-loading';
-import { Transform } from '../../util/Transform';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+if (window?.Worker) GlobalWorkerOptions.workerSrc = 'files/node_modules/pdfjs-dist/build/pdf.worker.min.mjs'; // npm start/etc use copyfiles to copy the worker from the pdfjs-dist package to the public folder
+export * from 'pdfjs-dist/build/pdf.mjs';
interface IViewerProps extends FieldViewProps {
pdfBox: PDFBox;
- Document: Doc;
+ Doc: Doc;
dataDoc: Doc;
layoutDoc: Doc;
fieldKey: string;
@@ -53,7 +55,7 @@ interface IViewerProps extends FieldViewProps {
*/
@observer
export class PDFViewer extends ObservableReactComponent<IViewerProps> {
- static _annotationStyle = addStyleSheet();
+ static _annotationStyle = addStyleSheet().sheet;
constructor(props: IViewerProps) {
super(props);
@@ -112,8 +114,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
() => this._props.layoutDoc._layout_autoHeight,
layoutAutoHeight => {
if (layoutAutoHeight) {
- this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
- this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
+ this._props.layoutDoc._nativeHeight = NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']);
+ this._props.setHeight?.(NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
}
);
@@ -124,7 +126,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
- () => Cast(this._props.Document._layout_curPage, 'number', null),
+ () => Cast(this._props.Doc._layout_curPage, 'number', null),
page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
@@ -132,7 +134,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
componentWillUnmount = () => {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener('copy', this.copy);
+ document.removeEventListener('copy', this.copy, true);
};
copy = (e: ClipboardEvent) => {
@@ -144,6 +146,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
e.clipboardData.setData('dash/pdfAnchor', anchor[DocData][Id]);
}
e.preventDefault();
+ e.stopPropagation();
}
};
@@ -180,7 +183,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
- if (doc !== this._props.Document && mainCont) {
+ if (doc !== this._props.Doc && mainCont) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
@@ -215,11 +218,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
- () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
+ () => Math.abs(NumCast(this._props.Doc._layout_scrollTop)),
pos => {
if (!this._ignoreScroll) {
this._showWaiting && this.setupPdfJsViewer();
- const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
+ const viewTrans = quickScroll?.loc ?? StrCast(this._props.Doc._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -260,16 +263,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
return;
}
- document.removeEventListener('copy', this.copy);
- document.addEventListener('copy', this.copy);
+ document.removeEventListener('copy', this.copy, true);
+ document.addEventListener('copy', this.copy, true);
const eventBus = new PDFJSViewer.EventBus();
eventBus._on('pagesinit', this.pagesinit);
- eventBus._on(
- 'pagerendered',
- action(() => {
- this._showWaiting = false;
- })
- );
+ eventBus._on('pagerendered',action(() => (this._showWaiting = false))); // prettier-ignore
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
@@ -370,9 +368,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
- if ((this._props.Document._freeform_scale || 1) !== 1) return;
+ if ((this._props.Doc._freeform_scale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Doc);
}
if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
this._props.select(false);
@@ -392,25 +390,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
};
- /**
- * Create a flashcard pile based on the selected text of a pdf.
- */
- gptPDFFlashcards = async () => {
- const queryText = this._selectionText;
- this._loading = true;
- try {
- const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
-
- AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y']));
- this._selectionText = '';
- } catch (err) {
- console.error(err);
- }
- this._loading = false;
- };
-
@action
finishMarquee = (/* x?: number, y?: number */) => {
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
this._marqueeref.current?.onTerminateSelection();
@@ -429,7 +411,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
if (sel) {
AnchorMenu.Instance.setSelectedText(sel.toString());
- AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y']));
+ AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc.x), NumCast(this._props.layoutDoc.y));
}
if (sel?.type === 'Range') {
@@ -441,14 +423,14 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
// allows for creating collection
AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
- AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards;
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
AnchorMenu.Instance.AddDrawingAnnotation = this.addDrawingAnnotation;
};
addDrawingAnnotation = (drawing: Doc) => {
- // drawing[DocData].x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX
+ // drawing.x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX
// const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- drawing.y = NumCast(drawing.y) + NumCast(this._props.Document.layout_scrollTop);
+ drawing.y = NumCast(drawing.y) + NumCast(this._props.Doc.layout_scrollTop);
this._props.addDocument?.(drawing);
};
@@ -493,7 +475,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
+ this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Doc);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
@@ -521,7 +503,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
@computed get annotationLayer() {
const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
return (
- <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+ <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Doc), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
{inlineAnnos.map(anno => (
<Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
))}
@@ -536,7 +518,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;
@@ -612,7 +594,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick={this.onClick}
style={{
overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
- height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
+ height: !this._props.Doc._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Doc) : `100%`,
}}>
{this.pdfViewerDiv}
{this.annotationLayer}
@@ -621,12 +603,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{!this._mainCont.current || !this._annotationLayer.current || !this.props.pdfBox.DocumentView ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
- Document={this._props.Document}
+ Document={this._props.Doc}
getPageFromScroll={this.getPageFromScroll}
anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
annotationLayerScaling={() => Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS}
- annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
+ annotationLayerScrollTop={NumCast(this._props.Doc._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
docView={this.props.pdfBox.DocumentView}
screenTransform={this.screenToMarqueeXf}
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index 6f70e96ab..3ad5bc844 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);
const imageURL = `${name}_o.${type}`;
return new Promise((resolve, reject) => {
@@ -60,13 +60,13 @@ export class FaceRecognitionHandler {
* @param imgDoc image with faces
* @returns faceDoc array
*/
- public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutFieldKey(imgDoc)}_annotations`]).filter(doc => doc.face);
+ public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutDataKey(imgDoc)}_annotations`]).filter(doc => doc.face);
/**
* returns a list of all face collection Docs on the current dashboard
* @returns face collection Doc list
*/
- public static UniqueFaces = () => DocListCast(Doc.ActiveDashboard?.[DocData].myUniqueFaces);
+ public static UniqueFaces = () => DocListCast(Doc.ActiveDashboard?.$myUniqueFaces);
/**
* Find a unique face from its name
@@ -87,22 +87,22 @@ export class FaceRecognitionHandler {
* @param faceDoc unique face Doc
* @returns label string
*/
- public static UniqueFaceLabel = (faceDoc: Doc) => StrCast(faceDoc[DocData].face);
+ public static UniqueFaceLabel = (faceDoc: Doc) => StrCast(faceDoc.$face);
- public static SetUniqueFaceLabel = (faceDoc: Doc, value: string) => (faceDoc[DocData].face = value);
+ public static SetUniqueFaceLabel = (faceDoc: Doc, value: string) => (faceDoc.$face = value);
/**
* Returns all the face descriptors associated with a unique face Doc
* @param faceDoc unique face Doc
* @returns face descriptors
*/
- public static UniqueFaceDescriptors = (faceDoc: Doc) => DocListCast(faceDoc[DocData].face_annos).map(face => face.faceDescriptor as List<number>);
+ public static UniqueFaceDescriptors = (faceDoc: Doc) => DocListCast(faceDoc.$face_annos).map(face => face.faceDescriptor as List<number>);
/**
* Returns a list of all face image Docs associated with a unique face Doc
* @param faceDoc unique face Doc
* @returns image Docs
*/
- public static UniqueFaceImages = (faceDoc: Doc) => DocListCast(faceDoc[DocData].face_annos).map(face => DocCast(face.annotationOn, face));
+ public static UniqueFaceImages = (faceDoc: Doc) => DocListCast(faceDoc.$face_annos).map(face => DocCast(face.annotationOn, face));
/**
* Adds a face image to a unique face Doc, adds the unique face Doc to the images list of reognized faces,
@@ -145,8 +145,8 @@ export class FaceRecognitionHandler {
* @returns a unique face Doc
*/
private createUniqueFaceDoc = (dashboard: Doc) => {
- const faceDocNum = NumCast(dashboard[DocData].myUniqueFaces_count) + 1;
- dashboard[DocData].myUniqueFaces_count = faceDocNum; // TODO: improve to a better name
+ const faceDocNum = NumCast(dashboard.$myUniqueFaces_count) + 1;
+ dashboard.$myUniqueFaces_count = faceDocNum; // TODO: improve to a better name
const uniqueFaceDoc = Docs.Create.UniqeFaceDocument({
title: ComputedField.MakeFunction('this.face', undefined, undefined, 'this.face = value') as unknown as string,
@@ -160,10 +160,9 @@ export class FaceRecognitionHandler {
_width: 400,
_height: 100,
});
- const uface = uniqueFaceDoc[DocData];
- uface.face = `Face${faceDocNum}`;
- uface.face_annos = new List<Doc>();
- Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
+ uniqueFaceDoc.$face = `Face${faceDocNum}`;
+ uniqueFaceDoc.$face_annos = new List<Doc>();
+ Doc.MyFaceCollection && Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard[DocData], 'myUniqueFaces', uniqueFaceDoc);
return uniqueFaceDoc;
@@ -208,10 +207,10 @@ export class FaceRecognitionHandler {
} else if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) {
setTimeout(() => this.classifyFacesInImage(imgDoc), 1000);
} else {
- const imgUrl = ImageCast(imgDoc[Doc.LayoutFieldKey(imgDoc)]);
- if (imgUrl && !DocListCast(Doc.MyFaceCollection.examinedFaceDocs).includes(imgDoc[DocData])) {
+ const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]);
+ if (imgUrl && !DocListCast(Doc.MyFaceCollection?.examinedFaceDocs).includes(imgDoc[DocData])) {
// only examine Docs that have an image and that haven't already been examined.
- Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
+ Doc.MyFaceCollection && Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
FaceRecognitionHandler.loadImage(imgUrl).then(
// load image and analyze faces
img => faceapi
@@ -241,7 +240,7 @@ export class FaceRecognitionHandler {
annos.push(faceAnno);
});
- imgDoc[DocData].data_annotations = new List<Doc>(annos);
+ imgDoc.$data_annotations = new List<Doc>(annos);
imgDoc._layout_showTags = annos.length > 0;
return imgDocFaceDescriptions;
})
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index ae0838dd5..ad0f56cb9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
@@ -29,7 +27,7 @@ const MAX_ITERATIONS = 25;
const ERROR = 0.03;
export interface SearchBoxItemProps {
- Document: Doc;
+ Doc: Doc;
searchString: string;
isLinkSearch: boolean;
matchedKeys: string[];
@@ -52,9 +50,7 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
* This method selects a doc by either jumping to it (centering/zooming in on it)
* or opening it in a new tab.
*/
- selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentView.showDocument(doc, { willZoomCentered: true }, finishFunc);
- };
+ selectElement = (doc: Doc, finishFunc: () => void) => DocumentView.showDocument(doc, { willPan: true }, finishFunc);
/**
* @param {Doc} doc - doc of the search result that has been clicked on
@@ -68,7 +64,7 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
});
componentWillUnmount(): void {
- const doc = this._props.Document;
+ const doc = this._props.Doc;
DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
}
@@ -83,23 +79,23 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
render() {
// eslint-disable-next-line no-use-before-define
- const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
- const { title } = this._props.Document;
+ const formattedType = SearchBox.formatType(StrCast(this._props.Doc.type), StrCast(this._props.Doc.type_collection));
+ const { title } = this._props.Doc;
return (
<Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
<div
onClick={
this._props.isLinkSearch
- ? () => this.makeLink(this._props.Document)
+ ? () => this.makeLink(this._props.Doc)
: e => {
- this.onResultClick(this._props.Document);
+ this.onResultClick(this._props.Doc);
e.stopPropagation();
}
}
style={{
fontWeight: Doc.Links(this._props.linkFrom).find(
- link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+ link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Doc) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Doc)
)
? 'bold'
: '',
@@ -183,7 +179,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* (Note: There is no longer a need to press enter to submit a search. Any update to the input
* causes a search to be submitted automatically.)
*/
- _timeout: any = undefined;
+ _timeout: NodeJS.Timeout | undefined = undefined;
onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
this._timeout && clearTimeout(this._timeout);
@@ -222,7 +218,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
.filter(d => d)
// eslint-disable-next-line no-loop-func
.map(async d => {
- const fieldKey = Doc.LayoutFieldKey(d);
+ const fieldKey = Doc.LayoutDataKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
const dataDocs = await DocListCastAsync(data);
@@ -242,7 +238,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* which the first letter is capitalized. This is used when displaying the type on the
* right side of each search result.
*/
- static formatType(type: string, colType: string): String {
+ static formatType(type: string, colType: string): string {
switch (type) {
case DocumentType.PDF : return 'PDF';
case DocumentType.IMG : return 'Img';
@@ -437,7 +433,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
render() {
const isLinkSearch: boolean = this._props.linkSearch;
const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
- const resultsJSX = [] as any[];
+ const resultsJSX = [] as JSX.Element[];
const linkFrom = this._props.linkFrom?.();
let validResults = 0;
@@ -453,7 +449,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
resultsJSX.push(
<SearchBoxItem
key={Document[Id]}
- Document={Document}
+ Doc={Document}
selectItem={action((doc: Doc) => {
this._selectedResult = doc;
})}
@@ -469,7 +465,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
}
});
- // eslint-disable-next-line react/jsx-props-no-spreading
const recommendationsJSX: JSX.Element[] = []; // this._recommendations.map(props => <Recommendation {...props} />);
return (
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index c672bc718..2c69284db 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,8 +1,6 @@
-import { imageUrlToBase64 } from '../../../ClientUtils';
import { Doc, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
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';
@@ -14,23 +12,29 @@ import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, Firefl
const DashDropboxId = '2m86iveqdr9vzsa';
export class DrawingFillHandler {
+ static authorizeDropbox = () => {
+ 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();
+ };
static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc) => {
- const docData = drawing[DocData];
- const tags = StrListCast(docData.tags).map(tag => tag.slice(1));
+ 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 ? undefined : `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
+ });
return DocumentView.GetDocImage(drawing)?.then(imageField => {
- if (imageField) {
+ const href = ImageCast(imageField)?.url.href;
+ if (href) {
const aspectRatio = (drawing.width as number) / (drawing.height as number);
const dims = (() => {
if (aspectRatio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) return FireflyDimensionsMap[FireflyImageDimensions.Widescreen];
@@ -38,44 +42,43 @@ export class DrawingFillHandler {
if (aspectRatio < AspectRatioLimits[FireflyImageDimensions.Portrait]) return FireflyDimensionsMap[FireflyImageDimensions.Portrait];
return FireflyDimensionsMap[FireflyImageDimensions.Square];
})();
- 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[DocData].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(/^[^"]*/, ''))) {
+ return DrawingFillHandler.authorizeDropbox();
+ }
+ 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/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 1cceabed3..4f0cd3978 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -10,9 +10,11 @@ import { INode, parse } from 'svgson';
import { imageUrlToBase64, setupMoveUpEvents } from '../../../ClientUtils';
import { unimplementedFunction } from '../../../Utils';
import { Doc, DocListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { InkData, InkField, InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { PointData } from '../../../pen-gestures/GestureTypes';
+import { Upload } from '../../../server/SharedMediaTypes';
import { Networking } from '../../Network';
import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -26,9 +28,6 @@ import { MarqueeView } from '../collections/collectionFreeForm';
import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from './FireflyConstants';
import './SmartDrawHandler.scss';
-import { Upload } from '../../../server/SharedMediaTypes';
-import { PointData } from '../../../pen-gestures/GestureTypes';
-import { List } from '../../../fields/List';
export interface DrawingOptions {
text?: string;
@@ -153,9 +152,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
this._display = false;
this.ShowRegenerate = true;
this._showEditBox = false;
- const docData = this._selectedDocs[0][DocData];
- this._lastResponse = StrCast(docData.drawingData);
- this._lastInput = { text: StrCast(docData.ai_drawing_input), complexity: NumCast(docData.ai_drawing_complexity), size: NumCast(docData.ai_drawing_size), autoColor: BoolCast(docData.ai_drawing_colored), x: this._pageX, y: this._pageY };
+ const docData = this._selectedDocs[0];
+ this._lastResponse = StrCast(docData.$drawingData);
+ this._lastInput = { text: StrCast(docData.$ai_drawing_input), complexity: NumCast(docData.$ai_drawing_complexity), size: NumCast(docData.$ai_drawing_size), autoColor: BoolCast(docData.$ai_drawing_colored), x: this._pageX, y: this._pageY };
};
/**
@@ -404,7 +403,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
try {
const hrefBase64 = await imageUrlToBase64(hrefComplete);
- const strokes = DocListCast(drawing[DocData].data);
+ const strokes = DocListCast(drawing.$data);
const coords: string[] = [];
strokes.forEach((stroke, i) => {
const inkingStroke = DocumentView.getDocumentView(stroke)?.ComponentView as InkingStroke;
@@ -423,14 +422,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
*/
colorStrokes = undoable((res: string, drawing: Doc) => {
const colorList = res.match(/\{.*?\}/g);
- const strokes = DocListCast(drawing[DocData].data);
+ const strokes = DocListCast(drawing.$data);
colorList?.forEach((colors, index) => {
const strokeAndFill = colors.match(/#[0-9A-Fa-f]{6}/g);
if (strokeAndFill && strokeAndFill.length == 2) {
- strokes[index][DocData].color = strokeAndFill[0];
+ strokes[index].$color = strokeAndFill[0];
const inkStroke = DocumentView.getDocumentView(strokes[index])?.ComponentView as InkingStroke;
const { inkData } = inkStroke.inkScaledData();
- InkingStroke.IsClosed(inkData) ? (strokes[index][DocData].fillColor = strokeAndFill[1]) : (strokes[index][DocData].fillColor = undefined);
+ InkingStroke.IsClosed(inkData) ? (strokes[index].$fillColor = strokeAndFill[1]) : (strokes[index].$fillColor = undefined);
}
});
}, 'color strokes');
@@ -577,6 +576,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
type="text"
autoFocus
value={this._userInput}
+ onPointerDown={e => e.stopPropagation()}
onChange={action(e => this._canInteract && (this._userInput = e.target.value))}
placeholder="Enter item to draw"
onKeyDown={this.handleKeyPress}
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index d5307974f..0e234e966 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -9,7 +9,6 @@ import ReactLoading from 'react-loading';
import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
import { emptyFunction, numberRange } from '../../../Utils';
import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { ImageCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -26,7 +25,7 @@ import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
import './StickerPalette.scss';
interface StickerPaletteProps {
- Document: Doc;
+ Doc: Doc;
}
enum StickerPaletteMode {
@@ -57,10 +56,10 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
if (!doc.savedAsSticker) {
const docView = DocumentView.getDocumentView(doc);
await docView?.ComponentView?.updateIcon?.(true);
- const { clone } = await Doc.MakeClone(doc);
+ const { clone } = Doc.MakeClone(doc);
clone.title = doc.title;
- const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
- Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
+ const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutDataKey(clone)]))?.url?.href;
+ Doc.MyStickers && Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
doc.savedAsSticker = true;
}
};
@@ -133,7 +132,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
this._canInteract = true;
this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
this._gptRes = [];
- this._props.Document[DocData].data = undefined;
+ this._props.Doc.$data = undefined;
});
/**
@@ -143,15 +142,15 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
@undoBatch
generateDrawings = action(() => {
this._isLoading = true;
- const prevDrawings = DocListCast(this._props.Document[DocData].data);
- this._props.Document[DocData].data = undefined;
+ const prevDrawings = DocListCast(this._props.Doc.$data);
+ this._props.Doc.$data = undefined;
SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
this._canInteract = false;
Promise.all(
numberRange(3).map(i => {
return this._showRegenerate
? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput)
- : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
+ : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity || 0, this._opts.size || 0, !!this._opts.autoColor);
})
).then(() => {
this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
@@ -164,8 +163,8 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
@action
addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => {
this._gptRes.push(gptRes);
- drawing[DocData].freeform_fitContentsToBox = true;
- Doc.AddDocToList(this._props.Document, 'data', drawing);
+ drawing.$freeform_fitContentsToBox = true;
+ Doc.AddDocToList(this._props.Doc, 'data', drawing);
};
/**
@@ -174,19 +173,18 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
* presses the "save drawing" button.
*/
saveDrawing = () => {
- const cIndex = NumCast(this._props.Document.carousel_index);
- const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
- const docData = focusedDrawing[DocData];
- docData.title = this._opts.text.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
- docData.ai_drawing_input = this._opts.text;
- docData.ai_drawing_complexity = this._opts.complexity;
- docData.ai_drawing_colored = this._opts.autoColor;
- docData.ai_drawing_size = this._opts.size;
- docData.ai_drawing_data = this._gptRes[cIndex];
- docData.ai = 'gpt';
+ const cIndex = NumCast(this._props.Doc.carousel_index);
+ const focusedDrawing = DocListCast(this._props.Doc.data)[cIndex];
+ focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
+ focusedDrawing.$ai_drawing_input = this._opts.text;
+ focusedDrawing.$ai_drawing_complexity = this._opts.complexity;
+ focusedDrawing.$ai_drawing_colored = this._opts.autoColor;
+ focusedDrawing.$ai_drawing_size = this._opts.size;
+ focusedDrawing.$ai_drawing_data = this._gptRes[cIndex];
+ focusedDrawing.$ai = 'gpt';
focusedDrawing.width = this._opts.size;
- docData.x = this._opts.x;
- docData.y = this._opts.y;
+ focusedDrawing.x = this._opts.x;
+ focusedDrawing.y = this._opts.y;
StickerPalette.addToPalette(focusedDrawing).then(() => this.resetPalette(true));
};
@@ -315,7 +313,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
<>
{this.renderCreateInput()}
{this.renderCreateOptions()}
- {this.renderDoc(this._props.Document, (r: DocumentView) => {
+ {this.renderDoc(this._props.Doc, (r: DocumentView) => {
this._docCarouselView = r;
})}
<div className="palette-buttons">
@@ -329,9 +327,10 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
);
renderPaletteView = () => (
<>
- {this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
- this._docView = r;
- })}
+ {Doc.MyStickers &&
+ this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
+ this._docView = r;
+ })}
<Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode(StickerPaletteMode.create)} />
</>
);
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 7754b0ba2..5d8583873 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -65,7 +65,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;
@@ -92,6 +92,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">
@@ -172,6 +173,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);
@@ -197,9 +199,7 @@ 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 />} />
@@ -250,9 +250,9 @@ export class TopBar extends ObservableReactComponent<object> {
<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/debug/Repl.tsx b/src/debug/Repl.tsx
index 8ac502348..37586fcc0 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -13,7 +13,7 @@ import { makeInterface } from '../fields/Schema';
class Repl extends React.Component {
@observable text: string = '';
- @observable executedCommands: { command: string; result: any }[] = [];
+ @observable executedCommands: { command: string; result: unknown }[] = [];
onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
this.text = e.target.value;
@@ -30,7 +30,7 @@ class Repl extends React.Component {
if (!script.compiled) {
this.executedCommands.push({ command: this.text, result: 'Compile Error' });
} else {
- const result = script.run({ makeInterface }, err => this.executedCommands.push({ command: this.text, result: err.message || err }));
+ const result = script.run({ makeInterface }, err => this.executedCommands.push({ command: this.text, result: err }));
result.success && this.executedCommands.push({ command: this.text, result: result.result });
}
this.text = '';
@@ -40,7 +40,7 @@ class Repl extends React.Component {
@computed
get commands() {
return this.executedCommands.map(command => (
- <div style={{ marginTop: '5px' }}>
+ <div key={command.command} style={{ marginTop: '5px' }}>
<p>{command.command}</p>
{/* <pre>{JSON.stringify(command.result, null, 2)}</pre> */}
<pre>{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}</pre>
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index f0a851ce6..5db201c72 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -39,6 +39,6 @@ export class DateField extends ObjectField {
}
// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function d(...dateArgs: any[]) {
- return new DateField(new (Date as any)(...dateArgs));
+ScriptingGlobals.add(function d(...dateArgs: ConstructorParameters<typeof Date>) {
+ return new DateField(new Date(...dateArgs));
});
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index dc4a5a011..ba94f0504 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,7 +17,7 @@ import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScr
import { InkEraserTool, InkInkTool, InkTool } from './InkField';
import { List } from './List';
import { ObjectField, serverOpType } from './ObjectField';
-import { PrefetchProxy, ProxyField } from './Proxy';
+import { PrefetchProxy } from './Proxy';
import { FieldId, RefField } from './RefField';
import { RichTextField } from './RichTextField';
import { listSpec } from './Schema';
@@ -43,11 +43,11 @@ 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 cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
const valFunc = (field: FieldType): string => {
const res =
field instanceof ComputedField && showComputedValue
@@ -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) {
@@ -131,13 +133,13 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
- return Cast(field, listSpec('number'), defaultVal);
+ return Cast(field, listSpec('number'), defaultVal)!;
}
export function StrListCast(field: FieldResult, defaultVal: string[] = []) {
- return Cast(field, listSpec('string'), defaultVal);
+ return Cast(field, listSpec('string'), defaultVal)!;
}
export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) {
- return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[];
+ return Cast(field, listSpec(Doc), defaultVal)!.filter(d => d instanceof Doc);
}
export enum aclLevel {
@@ -174,13 +176,27 @@ export function updateCachedAcls(doc: Doc) {
}
if (doc.proto instanceof Promise) {
- doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+ doc.proto.then(proto => DocCast(proto) && updateCachedAcls(DocCast(proto)!));
return doc.proto;
}
}
return undefined;
}
+/**
+ * computes a field name for where to store and expanded template Doc
+ * The format is layout_[ROOT_TEMPLATE_NMAE]_[ROOT_TEMPLATE_CHILD_NAME]_...
+ * @param template the template (either a root or a root child Doc)
+ * @param layoutFieldKey the fieldKey of the container of the template
+ * @returns field key to store expanded template Doc
+ */
+export function expandedFieldName(template: Doc, layoutFieldKey?: string) {
+ const layout_key = !layoutFieldKey?.endsWith(']')
+ ? 'layout' // layout_SOMETHING = SOMETHING => layout_[SOMETHING] = [SOMETHING]
+ : layoutFieldKey; // prettier-ignore
+ const tempTitle = '[' + StrCast(template.title).replace(/^\[(.*)\]$/, '$1') + ']';
+ return `${layout_key}_${tempTitle}`; // prettier-ignore
+}
@scriptingGlobal
@Deserializable('Doc', (obj: unknown) => updateCachedAcls(obj as Doc), ['id'])
export class Doc extends RefField {
@@ -215,7 +231,7 @@ export class Doc extends RefField {
public static AddLink: (link: Doc, checkExists?: boolean) => void;
public static DeleteLink: (link: Doc) => void;
public static Links: (link: Doc | undefined) => Doc[];
- public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined;
+ public static getOppositeAnchor: (linkDoc: Doc | undefined, anchor: Doc | undefined) => Doc | undefined;
// KeyValueBox SetField (defined there)
public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean;
// UserDoc "API"
@@ -237,7 +253,7 @@ export class Doc extends RefField {
public static get MyPublishedDocs() { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore
public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
- public static get MyStickers() { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
+ public static get MyStickers() { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
public static get MyLightboxDrawings() { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore
public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
@@ -264,22 +280,28 @@ export class Doc extends RefField {
public static set ActiveDashboard(val: Opt<Doc>) { Doc.UserDoc().activeDashboard = val; } // prettier-ignore
public static get MyFilterHotKeys() { return DocListCast(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter)?.data).filter(key => key.toolType !== "-opts-"); } // prettier-ignore
public static RemFromFilterHotKeys(doc: Doc) {
- return Doc.RemoveDocFromList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+ return (filters => filters && Doc.RemoveDocFromList(filters, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
}
public static AddToFilterHotKeys(doc: Doc) {
- return Doc.AddDocToList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+ return (btns => btns && Doc.AddDocToList(btns, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
}
public static IsInMyOverlay(doc: Doc) { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
- public static AddToMyOverlay(doc: Doc) { return Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
- public static RemFromMyOverlay(doc: Doc) { return Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
- public static AddToMyPublished(doc: Doc) {
- doc[DocData].title_custom = true;
- doc[DocData].layout_showTitle = 'title';
- Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
- public static RemFromMyPublished(doc: Doc){
- doc[DocData].title_custom = false;
- doc[DocData].layout_showTitle = undefined;
- Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+ public static AddToMyOverlay(doc: Doc) {
+ return Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+ } // : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+ public static RemFromMyOverlay(doc: Doc) {
+ return Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+ } // : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+ public static AddToMyPublished(doc: Doc) {
+ doc.$title_custom = true;
+ doc.$layout_showTitle = 'title';
+ Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+ } // : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+ public static RemFromMyPublished(doc: Doc) {
+ doc.$title_custom = false;
+ doc.$layout_showTitle = undefined;
+ Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+ } // : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
public static IsComicStyle(doc?: Doc) { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
constructor(id?: FieldId, forceSave?: boolean) {
@@ -320,10 +342,11 @@ export class Doc extends RefField {
UpdatingFromServer,
Width,
'__LAYOUT__',
+ '__DATA__',
];
},
getOwnPropertyDescriptor: (target, prop) => {
- if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
+ if (prop.toString() === '__DATA__' || prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
return {
@@ -400,23 +423,23 @@ export class Doc extends RefField {
public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
public get [DocData](): Doc {
+ return this[SelfProxy].__DATA__;
+ }
+ @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(DocCast(self[DocLayout].rootDocument, self)!);
}
- @computed get __LAYOUT__(): Doc | undefined {
+ @computed get __LAYOUT__(): Doc {
const self = this[SelfProxy];
const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
if (templateLayoutDoc) {
- let renderFieldKey: string = '';
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
- if (typeof layoutField === 'string') {
- [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
- } else {
- return Cast(layoutField, Doc, null);
+ if (typeof layoutField !== 'string') {
+ return DocCast(layoutField, self)!;
}
- return Cast(self[renderFieldKey + '_layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
+ return DocCast(self[expandedFieldName(templateLayoutDoc)], templateLayoutDoc)!;
}
- return undefined;
+ return self;
}
public async [HandleUpdate](diff: { $set: { [key: string]: FieldType } } | { $unset?: unknown }) {
@@ -591,7 +614,7 @@ export namespace Doc {
// return the doc's proto, but rather recursively searches through the proto inheritance chain
// and returns the document who's proto is undefined or whose proto is marked as a data doc ('isDataDoc').
export function GetProto(doc: Doc): Doc {
- const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc));
+ const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc)!);
return proto === doc ? proto : Doc.GetProto(proto);
}
export function GetDataDoc(doc: Doc): Doc {
@@ -624,8 +647,8 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
- const key = fieldKey || Doc.LayoutFieldKey(listDoc);
- const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+ const key = fieldKey || Doc.LayoutDataKey(listDoc);
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
const ind = list.indexOf(doc);
if (ind !== -1) {
@@ -641,8 +664,8 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
- const key = fieldKey || Doc.LayoutFieldKey(listDoc);
- const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+ const key = fieldKey || Doc.LayoutDataKey(listDoc);
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
if (!allowDuplicates) {
const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
@@ -680,41 +703,36 @@ export namespace Doc {
return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
}
+ /**
+ * Makes an embedding of a Doc. This Doc shares the data portion of the origiginal Doc.
+ * If the copied Doc has no prototype, then instead of copying the Doc, this just creates
+ * a new Doc that is a delegate of the original Doc.
+ * @param doc Doc to embed
+ * @param id id to use for embedded Doc
+ * @returns a new Doc that is an embedding of the original 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[DocData].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;
}
export function BestEmbedding(doc: Doc) {
- const dataDoc = doc[DocData];
- const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
- const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
+ const availableEmbeddings = Doc.GetEmbeddings(doc);
+ const bestEmbedding = [...(doc[DocData] !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
bestEmbedding && Doc.AddEmbedding(doc, doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
// this lists out all the tag ids that can be in a RichTextField that might contain document ids.
// if a document is cloned, we need to make sure to clone all of these referenced documents as well;
- export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId'];
- export async function makeClone(
- doc: Doc,
- cloneMap: Map<string, Doc>,
- linkMap: Map<string, Doc>,
- rtfs: { copy: Doc; key: string; field: RichTextField }[],
- exclusions: string[],
- pruneDocs: Doc[],
- cloneLinks: boolean,
- cloneTemplates: boolean
- ): Promise<Doc> {
+ const FindDocsInRTF = new RegExp(/(audioId|textId|anchorId|docId)"\s*:\s*"(.*?)"/g);
+
+ export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], pruneDocs: Doc[], cloneLinks: boolean, cloneTemplates: boolean): Doc {
if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
return doc;
}
@@ -722,81 +740,59 @@ export namespace Doc {
const copy = new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
const filter = [...exclusions, ...StrListCast(doc.cloneFieldFilter)];
- await Promise.all(
- Object.keys(doc).map(async key => {
- if (filter.includes(key)) return;
- const assignKey = (val: Opt<FieldType>) => {
- copy[key] = val;
- };
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (objField: ObjectField) => {
- const list = await Cast(doc[key], listSpec(Doc));
- const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
- if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)));
- assignKey(new List<Doc>(clones));
- } else {
- assignKey(ObjectField.MakeCopy(objField));
- if (objField instanceof RichTextField) {
- if (DocsInTextFieldIds.some(id => objField.Data.includes(`"${id}":`))) {
- const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g');
- const rawdocids = objField.Data.match(docidsearch);
- const docids = rawdocids?.map((str: string) =>
- DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str)
- .replace(/"/g, '')
- .trim()
- );
- const results = docids && (await DocServer.GetRefFields(docids));
- const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results.get(rkey)));
- rdocs?.map(d => d && Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
- rtfs.push({ copy, key, field: objField });
- }
- }
- }
- };
- const docAtKey = doc[key];
+ Object.keys(doc)
+ .filter(key => !filter.includes(key))
+ .map(key => {
+ const assignKey = (val: Opt<FieldType>) => (copy[key] = val);
+
if (key === 'author') {
- assignKey(ClientUtils.CurrentUserEmail());
- } else if (docAtKey instanceof Doc) {
- if (pruneDocs.includes(docAtKey)) {
- // prune doc and do nothing
- } else if (
- !Doc.IsSystem(docAtKey) &&
- (key.includes('layout[') ||
- 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));
- } else {
- assignKey(docAtKey);
+ return assignKey(ClientUtils.CurrentUserEmail());
+ }
+ const cfield = ComputedField.DisableCompute(() => doc[key]);
+ if (cfield instanceof ComputedField) {
+ return assignKey(cfield[Copy]());
+ }
+ const field = doc[key];
+ if (field instanceof Doc) {
+ const doCopy = () => Doc.IsSystem(field) ||
+ !( key.startsWith('layout') ||
+ ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
+ (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()) ); // prettier-ignore
+ return !pruneDocs.includes(field) &&
+ assignKey(doCopy()
+ ? field //
+ : Doc.makeClone(field, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); // prettier-ignore
+ }
+ if (field instanceof RichTextField) {
+ rtfs.push({ copy, key, field });
+ let docId: string | undefined;
+ while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+ const docCopy = DocServer.GetCachedRefField(docId);
+ docCopy && Doc.makeClone(docCopy, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates);
}
- } else if (field instanceof RefField) {
- assignKey(field);
- } else if (cfield instanceof ComputedField) {
- assignKey(cfield[Copy]());
- } else if (field instanceof ObjectField) {
- await copyObjectField(field);
- } else if (field instanceof Promise) {
- // eslint-disable-next-line no-debugger
- debugger; // This shouldn't happen...
- } else {
- assignKey(field);
+ return assignKey(ObjectField.MakeCopy(field));
}
- })
- );
- Array.from(doc[DirectLinks]).forEach(async link => {
+ if (field instanceof ObjectField) {
+ if (DocListCast(field).length) {
+ return assignKey(new List<Doc>(DocListCast(field).map(d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates))));
+ }
+ return assignKey(ObjectField.MakeCopy(field)); // otherwise just copy the field
+ }
+ if (!(field instanceof Promise)) return assignKey(field);
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happen...
+ });
+ Array.from(doc[DirectLinks]).forEach(link => {
if (
cloneLinks ||
- ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id])) &&
- (cloneMap.has(DocCast(link.link_anchor_2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id])))
+ ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id] ?? '')) &&
+ (cloneMap.has(DocCast(link.link_anchor_2)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id] ?? '')))
) {
- linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
+ linkMap.set(link[Id], Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
}
});
copy.cloneOf = doc;
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc.title));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc.title));
if (Doc.Get(copy, 'title', true) && !(cfield instanceof ComputedField)) copy.title = '>:' + doc.title;
cloneMap.set(doc[Id], copy);
@@ -823,25 +819,27 @@ export namespace Doc {
return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneTemplates, cloneMap));
}
- export async function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
+ /**
+ * Copies a Doc and all of the Docs that it references. This is a deep copy of the Doc.
+ * However, the additional flags allow you to control whether to copy links and templates.
+ * @param doc Doc to clone
+ * @param cloneLinks whether to clone links to this Doc
+ * @param cloneTemplates whether to clone the templates used by this Doc
+ * @param cloneMap a map from the Doc ids of the original Doc to the new Docs
+ * @returns a clone of the original Doc
+ */
+ export function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<string, Doc>();
const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
- const clone = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
+ const clone = Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], DocCast(doc.embedContainer) ? [DocCast(doc.embedContainer)!] : [], cloneLinks, cloneTemplates);
const repaired = new Set<Doc>();
const linkedDocs = Array.from(linkMap.values());
linkedDocs.forEach(link => Doc.AddLink?.(link, true));
rtfMap.forEach(({ copy, key, field }) => {
- const replacer = (match: string, attr: string, id: string /* , offset: any, string: any */) => {
- const mapped = cloneMap.get(id);
- return attr + '"' + (mapped ? mapped[Id] : id) + '"';
- };
- const replacer2 = (match: string, href: string, id: string /* , offset: any, string: any */) => {
- const mapped = cloneMap.get(id);
- return href + (mapped ? mapped[Id] : id);
- };
+ const replacer = (match: string, attr: string, id: string) => attr + '":"' + (cloneMap.get(id)?.[Id] ?? id) + '"';
+ const replacer2 = (match: string, href: string, id: string) => href + (cloneMap.get(id)?.[Id] ?? id);
const re = new RegExp(`(${Doc.localServerPath()})([^"]*)`, 'g');
- const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => `"${exp}":`).join('|') + ')"([^"]+)"', 'g');
- copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text);
+ copy[key] = new RichTextField(field.Data.replace(FindDocsInRTF, replacer).replace(re, replacer2), field.Text);
});
const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
@@ -849,34 +847,40 @@ export namespace Doc {
}
const _pendingMap = new Set<string>();
- //
- // Returns an expanded template layout for a target data document if there is a template relationship
- // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
- // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
- export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
+ /**
+ * Returns an expanded template layout for a target data document if there is a template relationship
+ * between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
+ * of the original layout while allowing for individual layout properties to be overridden in the expanded layout
+ * @param templateLayoutDoc a rendering template Doc
+ * @param targetDoc the Doc that the render template will be applied to
+ * @param layoutFieldKey the accumulated layoutFieldKey for the container of this expanded template
+ * @returns a Doc to use to render the targetDoc in the style of the template layout
+ */
+ export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, layoutFieldKey?: string) {
// nothing to do if the layout isn't a template or we don't have a target that's different than the template
if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
return templateLayoutDoc;
}
- const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutFieldKey(templateLayoutDoc)); // the field that the template renders
+ const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutDataKey(templateLayoutDoc)); // the field that the template renders
+
// First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
// using the template layout doc's id as the field key.
// If it doesn't find the expanded layout, then it makes a delegate of the template layout and
// saves it on the data doc indexed by the template layout's id.
//
- const expandedLayoutFieldKey = templateField + '_layout[' + templateLayoutDoc[Id] + ']';
+ const expandedLayoutFieldKey = expandedFieldName(templateLayoutDoc, layoutFieldKey);
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(
@@ -885,7 +889,7 @@ export namespace Doc {
const dataDoc = Doc.GetProto(targetDoc);
newLayoutDoc.rootDocument = targetDoc;
newLayoutDoc.embedContainer = targetDoc;
- newLayoutDoc.resolvedDataDoc = dataDoc;
+ newLayoutDoc.cloneOnCopy = true;
newLayoutDoc.acl_Guest = SharingPermissions.Edit;
if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as List<Doc>)?.length) {
dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
@@ -902,87 +906,78 @@ export namespace Doc {
return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
}
- // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
- // otherwise, it just returns the childDoc
- export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
+ /**
+ * Returns a layout and data Doc pair to use to render the specified childDoc of the container.
+ * if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
+ * otherwise, only a layout is returned since that will contain the data as its prototype.
+ * @param containerDoc the template container (that may be nested within a template, the root of a template, or not a template)
+ * @param containerDataDoc the template container's data doc (if the container is nested within the template)
+ * @param childDoc the doc to render
+ * @param layoutFieldKey the accumulated layoutFieldKey for the container of the doc being rendered
+ * @returns a layout Doc to render and an optional data Doc if the layout is a template
+ */
+ export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc, layoutFieldKey?: string) {
if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
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, layoutFieldKey), data };
}
- export function FindReferences(infield: Doc | List<Doc>, references: Set<Doc>, system: boolean | undefined) {
- if (infield instanceof Promise) return;
- if (!(infield instanceof Doc)) {
- infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
- return;
+ /**
+ * Recursively travels through all the metadata reachable from the Doc to find all referenced Docs
+ * @param doc Doc to search
+ * @param references all Docs reachable from the Doc
+ * @param system whether to include system Docs
+ * @returns all Docs reachable from the Doc
+ */
+ export function FindReferences(doc: Doc | List<Doc> | undefined, references: Set<Doc>, system: boolean | undefined) {
+ if (!doc || doc instanceof Promise) {
+ return references;
+ }
+ if (!(doc instanceof Doc)) {
+ doc?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ return references;
}
- const doc = infield as Doc;
if (references.has(doc)) {
- references.add(doc);
- return;
+ return references;
+ }
+ if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) {
+ return references;
}
- const excludeLists = [Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc);
- if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return;
+
references.add(doc);
- Object.keys(doc).forEach(key => {
- if (key === 'proto') {
- if (doc.proto instanceof Doc) {
- Doc.FindReferences(doc.proto, references, system);
+ Object.keys(doc)
+ .filter(key => key !== 'author' && !(ComputedField.DisableCompute(() => FieldValue(doc[key])) instanceof ComputedField))
+ .map(key => doc[key])
+ .forEach(field => {
+ if (field instanceof List) {
+ return ![Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc) && Doc.FindReferences(field, references, system);
}
- } else {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
- if (field instanceof RefField) {
- if (field instanceof Doc) {
- if (key === 'myLinkDatabase') {
- field instanceof Doc && references.add(field);
- // skip docs that have been closed and are scheduled for garbage collection
- } else {
- Doc.FindReferences(field, references, system);
- }
- }
- } else if (cfield instanceof ComputedField) {
- /* empty */
- } else if (field instanceof ObjectField) {
- if (field instanceof Doc) {
- Doc.FindReferences(field, references, system);
- } else if (field instanceof List) {
- !excludeLists && Doc.FindReferences(field, references, system);
- } else if (field instanceof ProxyField) {
- if (key === 'myLinkDatabase') {
- field instanceof Doc && references.add(field);
- // skip docs that have been closed and are scheduled for garbage collection
- } else {
- Doc.FindReferences(field.value, references, system);
- }
- } else if (field instanceof PrefetchProxy) {
- Doc.FindReferences(field.value, references, system);
- } else if (field instanceof RichTextField) {
- const re = /"docId"\s*:\s*"(.*?)"/g;
- let match: string[] | null;
- while ((match = re.exec(field.Data)) !== null) {
- const urlString = match[1];
- if (urlString) {
- const rdoc = DocServer.GetCachedRefField(urlString);
- if (rdoc) {
- references.add(rdoc);
- Doc.FindReferences(rdoc, references, system);
- }
- }
- }
+ if (field instanceof Doc) {
+ return Doc.FindReferences(field, references, system);
+ }
+ if (field instanceof RichTextField) {
+ let docId: string | undefined;
+ while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+ Doc.FindReferences(DocServer.GetCachedRefField(docId), references, system);
}
- } else if (field instanceof Promise) {
- // eslint-disable-next-line no-debugger
- debugger; // This shouldn't happend...
}
- }
- });
+ });
+ return references;
}
+ /**
+ * Copies a Doc by copying its embedding (and optionally its prototype). Values within the Doc are copied except for Docs which are
+ * sipmly referenced (except if they're marked to be copied - eg., template layout Docs)
+ * @param doc Doc to copy
+ * @param copyProto whether to copy the Docs proto
+ * @param copyProtoId the id to use for the proto if copied
+ * @param retitle whether to retitle the copy by adding a copy number to the title
+ * @returns the copied Doc
+ */
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = runInAction(() => new Doc(copyProtoId, true));
updateCachedAcls(copy);
@@ -995,24 +990,18 @@ export namespace Doc {
copy[key] = Doc.MakeCopy(doc.proto, false);
}
} else {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
- if (field instanceof RefField) {
- copy[key] = field;
- } else if (cfield instanceof ComputedField) {
- copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript);
- } else if (field instanceof ObjectField) {
- const docAtKey = doc[key];
- copy[key] =
- docAtKey instanceof Doc && (key.includes('layout[') || docAtKey.cloneOnCopy)
- ? new ProxyField(Doc.MakeCopy(docAtKey)) // copy the expanded render template
- : ObjectField.MakeCopy(field);
- } else if (field instanceof Promise) {
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : doc[key];
+ if (field instanceof Promise) {
// eslint-disable-next-line no-debugger
debugger; // This shouldn't happend...
- } else {
- copy[key] = field;
}
+ copy[key] = (() => {
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
+ if (cfield instanceof ComputedField) return cfield[Copy]();
+ if (field instanceof Doc) return field.cloneOnCopy ? Doc.MakeCopy(field) : field; // copy the expanded render template
+ if (field instanceof ObjectField) return ObjectField.MakeCopy(field);
+ return field;
+ })();
}
});
if (copyProto) {
@@ -1028,6 +1017,14 @@ export namespace Doc {
return copy;
}
+ /**
+ * Makes a delegate of a prototype Doc. Delegates inherit all of the properties of the
+ * prototype Doc, but can add new properties or mask existing prototype properties.
+ * @param doc prototype Doc to make a delgate of
+ * @param id id to use for delegate
+ * @param title title to use for delegate
+ * @returns a new Doc that is a delegate of the original Doc
+ */
export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
@@ -1063,14 +1060,14 @@ export namespace Doc {
return ndoc;
}
- let _applyCount: number = 0;
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
const proto = new Doc();
+ const applyCount = NumCast(templateDoc.dragFactory_count);
proto.author = ClientUtils.CurrentUserEmail();
const target = Doc.MakeDelegate(proto);
const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
- const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
+ const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + applyCount + ')');
target.layout_fieldKey = targetKey; //this and line above
applied && (Doc.GetProto(applied).type = templateDoc.type);
return applied;
@@ -1080,7 +1077,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);
@@ -1097,13 +1094,13 @@ export namespace Doc {
//
export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>, keepFieldKey = false): boolean {
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+ const metadataFieldKey = keepFieldKey ? Doc.LayoutDataKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutDataKey(templateField);
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
!keepFieldKey && (templateField.title = metadataFieldKey);
- const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
+ const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutDataKey(templateField)];
// move any data that the template field had been rendering over to the template doc so that things will still be rendered
// when the template field is adjusted to point to the new metadatafield key.
// note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
@@ -1113,10 +1110,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;
}
@@ -1149,58 +1146,94 @@ export namespace Doc {
@observable _searchQuery: string = '';
}
- // 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[`${StrCast(layout.isTemplateForField, 'data')}_layout[` + layout[Id] + ']'], Doc, null);
- return overrideLayout || doc[DocLayout] || doc;
- }
- export function SetLayout(doc: Doc, layout: Doc | string) {
- doc[StrCast(doc.layout_fieldKey, 'layout')] = layout;
+ /**
+ * The layout Doc containing the view layout information - this will be :
+ * a) the Doc being rendered itself unless
+ * b) a template Doc stored in the field sepcified by Doc's layout_fieldKey
+ * c) or the specifeid 'template' Doc;
+ * If a template is specified, it will be expanded to create an instance specific to the rendered doc;
+ * @param doc the doc to render
+ * @param template a Doc to use as a template for the layout
+ * @returns
+ */
+ export function LayoutDoc(doc: Doc, template?: Doc): Doc {
+ const expandedTemplate = template && Cast(doc[expandedFieldName(template)], Doc, null);
+ return expandedTemplate || doc[DocLayout];
}
+ /**
+ * The JSX or Doc value defining how to render the Doc.
+ * @param doc Doc to render
+ * @returns a JSX string or Doc that describes how to render the Doc
+ */
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={'([^']+)'}/);
- return match?.[1] || ''; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+ /**
+ * The field key of the Doc where the primary Data can be found to render the Doc.
+ * eg., for an image, this is likely to be the 'data' field which contains an image url,
+ * and for a text doc, this is likely to be the 'text' field ontaingin the text of the doc.
+ * @param doc Doc to render
+ * @param templateLayoutString optional JSX string that specifies the doc's data field key
+ * @returns field key where data is stored on Doc
+ */
+ export function LayoutDataKey(doc: Doc, templateLayoutString?: string): string {
+ const match = StrCast(templateLayoutString || doc[DocLayout].layout).match(/fieldKey={'([^']+)'}/);
+ return match?.[1] || '';
}
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
}
export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
- return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
+ return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
}
export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
if (!doc) return 0;
const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width
- const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
+ const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
return NumCast(doc._nativeHeight, nheight || dheight);
}
+
+ export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
+ return !doc ? 0 : NumCast(doc._outpaintingWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_outpaintingWidth'], useWidth ? NumCast(doc._width) : 0));
+ }
+
+ export function OutpaintingHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
+ if (!doc) return 0;
+ const oheight = (Doc.OutpaintingWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height);
+ const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_outpaintingHeight'], useHeight ? NumCast(doc._height) : 0);
+ return NumCast(doc._outpaintingHeight, oheight || dheight);
+ }
+
+ export function SetOutpaintingWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_outpaintingWidth'] = width;
+ }
+
+ export function SetOutpaintingHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_outpaintingHeight'] = height;
+ }
+
export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
- doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_nativeWidth'] = width;
}
export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
- doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_nativeHeight'] = height;
}
const manager = new UserDocData();
- export function SearchQuery(): string {
+ export function SearchQuery() {
return manager._searchQuery;
}
export function SetSearchQuery(query: string) {
- runInAction(() => {
- manager._searchQuery = query;
- });
+ manager._searchQuery = query;
}
- export function UserDoc(): Doc {
+ export function UserDoc() {
return manager._user_doc;
}
- export function SharingDoc(): Doc {
+ export function SharingDoc() {
return Doc.MySharedDocs;
}
- export function LinkDBDoc(): Doc {
- return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
+ export function LinkDBDoc() {
+ return DocCast(Doc.UserDoc().myLinkDatabase);
}
export function SetUserDoc(doc: Doc) {
return (manager._user_doc = doc);
@@ -1225,7 +1258,7 @@ export namespace Doc {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate) return doc;
const result = brushManager.SearchMatchDoc.get(doc);
const num = Math.abs(result?.searchMatch || 0) + 1;
- runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
+ result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num });
return doc;
}
export function ClearSearchMatches() {
@@ -1298,12 +1331,12 @@ 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;
// want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
- if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
+ if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = true;
}
});
}
@@ -1315,23 +1348,13 @@ export namespace Doc {
highlightedDocs.delete(doc[DocData]);
doc[Highlight] = doc[DocData][Highlight] = false;
doc[Animation] = undefined;
- if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
+ if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = false;
});
});
}
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) {
@@ -1352,7 +1375,7 @@ export namespace Doc {
export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: readonly number[], modifiers?: 'remove') {
if (!container) return;
- const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []);
+ const childFiltersByRanges = StrListCast(container._childFiltersByRanges);
for (let i = 0; i < childFiltersByRanges.length; i += 3) {
if (childFiltersByRanges[i] === key) {
@@ -1360,7 +1383,7 @@ export namespace Doc {
break;
}
}
- if (range !== undefined) {
+ if (range) {
childFiltersByRanges.push(key);
childFiltersByRanges.push(range[0].toString());
childFiltersByRanges.push(range[1].toString());
@@ -1415,7 +1438,7 @@ export namespace Doc {
});
}
export function readDocRangeFilter(doc: Doc, key: string) {
- const childFiltersByRanges = Cast(doc._childFiltersByRanges, listSpec('string'), []);
+ const childFiltersByRanges = StrListCast(doc._childFiltersByRanges);
for (let i = 0; i < childFiltersByRanges.length; i += 3) {
if (childFiltersByRanges[i] === key) {
return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
@@ -1450,8 +1473,9 @@ export namespace Doc {
DocServer.GetRefFields(docids).then(async fieldlist => {
const list = Array.from(fieldlist.values())
.map(d => DocCast(d))
- .filter(d => d);
- const docs = clone ? (await Promise.all(Doc.MakeClones(list, false, false))).map(res => res.clone) : list;
+ .filter(d => d)
+ .map(d => d!);
+ const docs = clone ? Doc.MakeClones(list, false, false).map(res => res.clone) : list;
if (ptx !== undefined && pty !== undefined && newPoint !== undefined) {
const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0;
const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0;
@@ -1471,16 +1495,16 @@ export namespace Doc {
* @returns
*/
export function getDescription(doc: Doc) {
- const curDescription = StrCast(doc[DocData][Doc.LayoutFieldKey(doc) + '_description']);
+ const curDescription = StrCast(doc['$' + Doc.LayoutDataKey(doc) + '_description']);
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.LayoutDataKey(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.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]);
default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
}}); // prettier-ignore
- return docText(doc).then(text => (doc[DocData][Doc.LayoutFieldKey(doc) + '_description'] = text));
+ return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text));
}
// prettier-ignore
@@ -1517,7 +1541,6 @@ export namespace Doc {
case DocumentType.MAP: return 'map-marker-alt';
case DocumentType.DATAVIZ: return 'chart-bar';
case DocumentType.EQUATION: return 'calculator';
- case DocumentType.SIMULATION: return 'rocket';
case DocumentType.CONFIG: return 'folder-closed';
default:
}
@@ -1729,12 +1752,12 @@ export function IdToDoc(id: string) {
return DocCast(DocServer.GetCachedRefField(id));
}
// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function idToDoc(id: string): Doc {
+ScriptingGlobals.add(function idToDoc(id: string): Doc | undefined {
return IdToDoc(id);
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renameEmbedding(doc: Doc) {
- return StrCast(doc[DocData].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) {
@@ -1783,7 +1806,7 @@ ScriptingGlobals.add(function docCastAsync(doc: FieldResult): FieldResult<Doc> {
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function activePresentationItem() {
const curPres = Doc.ActivePresentation;
- return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
+ return curPres && DocListCast(curPres[Doc.LayoutDataKey(curPres)])[NumCast(curPres._itemIndex)];
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: string, modifiers: 'match' | 'check' | 'x' | 'remove') {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 22bbcb9ab..ba03c0d38 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -255,7 +255,7 @@ export class ListImpl<T extends FieldType> extends ObjectField {
if (fields) {
this[SelfProxy].push(...fields);
}
- // eslint-disable-next-line no-constructor-return
+
return list; // need to return the proxy here, otherwise we don't get any of our list handler functions
}
@@ -317,8 +317,8 @@ export class ListImpl<T extends FieldType> extends ObjectField {
// declare List as a type so you can use it in type declarations, e.g., { l: List, ...}
export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-// decalre List as a value so you can invoke 'new' on it, e.g., new List<Doc>() (since List<T> IS ListImpl<T>, we can safely cast the 'new' return value to return List<T>)
-// eslint-disable-next-line no-redeclare
+// declare List as a value so you can invoke 'new' on it, e.g., new List<Doc>() (since List<T> IS ListImpl<T>, we can safely cast the 'new' return value to return List<T>)
+// eslint-disable-next-line no-use-before-define
export const List: { new <T extends FieldType>(fields?: T[]): List<T> } = ListImpl as unknown as { new <T extends FieldType>(fields?: T[]): List<T> };
ScriptingGlobals.add('List', List);
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 48c336e60..21b9fe89b 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -16,9 +16,26 @@ function deserializeProxy(field: serializedProxyType) {
}
@Deserializable('proxy', (obj: unknown) => deserializeProxy(obj as serializedProxyType))
export class ProxyField<T extends Doc> extends ObjectField {
+ @serializable(primitive())
+ readonly fieldId: string = '';
+
+ private failed = false;
+
+ // This getter/setter and nested object thing is
+ // because mobx doesn't play well with observable proxies
+ @observable.ref
+ private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined };
+ private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
+ return this._cache;
+ }
+ private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
+ runInAction(() => (this._cache = { ...val }));
+ }
+
constructor();
constructor(value: T);
constructor(fieldId: string);
+ constructor(value: T | string);
constructor(value?: T | string) {
super();
if (typeof value === 'string') {
@@ -30,13 +47,12 @@ export class ProxyField<T extends Doc> extends ObjectField {
}
}
- [ToValue](/* doc: any */) {
+ [ToValue]() {
return ProxyField.toValue(this);
}
[Copy]() {
- if (this.cache.field) return new ProxyField<T>(this.cache.field);
- return new ProxyField<T>(this.fieldId);
+ return new ProxyField<T>(this.cache.field ?? this.fieldId);
}
[ToJavascriptString]() {
@@ -46,27 +62,9 @@ export class ProxyField<T extends Doc> extends ObjectField {
return Field.toScriptString(this[ToValue]()?.value as T); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
}
[ToString]() {
- return Field.toString(this[ToValue]()?.value);
+ return Field.toString(this[ToValue]()?.value as T);
}
- @serializable(primitive())
- readonly fieldId: string = '';
-
- // This getter/setter and nested object thing is
- // because mobx doesn't play well with observable proxies
- @observable.ref
- private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined };
- private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
- return this._cache;
- }
- private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
- runInAction(() => {
- this._cache = { ...val };
- });
- }
-
- private failed = false;
-
@computed get value(): T | undefined | FieldWaiting<T> {
if (this.cache.field) return this.cache.field;
if (this.failed) return undefined;
@@ -94,32 +92,19 @@ export class ProxyField<T extends Doc> extends ObjectField {
return field;
}
}
-
-// eslint-disable-next-line no-redeclare, @typescript-eslint/no-namespace
export namespace ProxyField {
let useProxy = true;
- export function DisableProxyFields() {
+ export function DisableDereference<T>(fn: () => T) {
useProxy = false;
- }
-
- export function EnableProxyFields() {
- useProxy = true;
- }
-
- export function WithoutProxy<T>(fn: () => T) {
- DisableProxyFields();
try {
return fn();
} finally {
- EnableProxyFields();
+ useProxy = true;
}
}
export function toValue(value: { value: unknown }) {
- if (useProxy) {
- return { value: value.value };
- }
- return undefined;
+ return useProxy ? { value: value.value } : undefined;
}
}
@@ -129,5 +114,6 @@ function prefetchValue(proxy: PrefetchProxy<Doc>) {
}
@scriptingGlobal
-@Deserializable('prefetch_proxy', (obj:unknown) => prefetchValue(obj as PrefetchProxy<Doc>))
+// eslint-disable-next-line no-use-before-define
+@Deserializable('prefetch_proxy', (obj: unknown) => prefetchValue(obj as PrefetchProxy<Doc>))
export class PrefetchProxy<T extends Doc> extends ProxyField<T> {}
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 63ae61c2f..68a3737bf 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -25,7 +25,7 @@ export class RichTextField extends ObjectField {
}
Empty() {
- return !(this.Text || this.Data.toString().includes('dashField') || this.Data.toString().includes('align'));
+ return !(this.Text || this.Data.toString().includes('dashField') || this.Data.toString().includes('dashDoc') || this.Data.toString().includes('align'));
}
[Copy]() {
@@ -42,7 +42,8 @@ export class RichTextField extends ObjectField {
return this.Text;
}
- // AARAV ADD=
+ // AARAV ADD
+
static ToProsemirrorDoc = (content: Record<string, unknown>[], selection: Record<string, unknown>) => ({
doc: {
type: 'doc',
@@ -86,7 +87,33 @@ export class RichTextField extends ObjectField {
{ type: 'text', anchor: 2 + plaintext.length - (selectBack ?? 0), head: 2 + plaintext.length }
);
+ // AARAV ADD
+
+ // takes in text segments instead of single text field
+ private static ToProsemirrorSegmented = (textSegments: { text: string; styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string } }[], imgDocId?: string, selectBack?: number) =>
+ RichTextField.ToProsemirrorDoc(
+ textSegments.map(seg => ({
+ type: 'paragraph', // Each segment becomes its own paragraph
+ content: [...RichTextField.ToProsemirrorTextContent(seg.text, seg.styles), ...(imgDocId ? RichTextField.ToProsemirrorDashDocContent(imgDocId) : [])],
+ })),
+ (textLen => ({
+ type: 'text',
+ anchor: textLen - (selectBack ?? 0),
+ head: textLen,
+ }))(2 * textSegments.length + textSegments.map(seg => seg.text).join('').length - 1)
+ // selection/doc end = text length + 2 for each paragraph. subtract 1 to set selection inside of end of last paragraph
+ );
+
+ // AARAV ADD ||
+
public static textToRtf(text: string, imgDocId?: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }, selectBack?: number) {
return new RichTextField(JSON.stringify(RichTextField.ToProsemirror(text, imgDocId, styles, selectBack)), text);
}
+
+ // AARAV ADD
+ public static textToRtfFormat(textSegments: { text: string; styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string } }[], imgDocId?: string, selectBack?: number) {
+ return new RichTextField(JSON.stringify(RichTextField.ToProsemirrorSegmented(textSegments, imgDocId, selectBack)), textSegments.map(seg => seg.text).join(''));
+ }
+
+ // AARAV ADD
}
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 42dd0d432..e16073ff4 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -20,6 +20,7 @@ import { Doc, Opt } from './Doc';
import { Id } from './FieldSymbols';
import { RichTextField } from './RichTextField';
import { Cast, StrCast } from './Types';
+import { Upload } from '../server/SharedMediaTypes';
export namespace RichTextUtils {
const delimiter = '\n';
@@ -127,7 +128,7 @@ export namespace RichTextUtils {
return { baseUrl: embeddedObject.imageProperties!.contentUri! };
});
- const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems });
+ const uploads = (await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems })) as Upload.FileInformation[];
if (uploads.length !== mediaItems.length) {
throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' });
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 89e5cda8d..cdd278d67 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -1,6 +1,3 @@
-/* eslint-disable guard-for-in */
-/* eslint-disable no-restricted-syntax */
-/* eslint-disable no-redeclare */
/* eslint-disable no-use-before-define */
import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types';
import { Doc, FieldType } from './Doc';
@@ -16,7 +13,6 @@ type AllToInterface<T extends Interface[]> = {
export const emptySchema = createSchema({});
export const Document = makeInterface(emptySchema);
-// eslint-disable-next-line no-redeclare
export type Document = makeInterface<[typeof emptySchema]>;
export interface InterfaceFunc<T extends Interface[]> {
@@ -36,9 +32,10 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
const proto = new Proxy(
{},
{
- get(target: any, prop, receiver) {
+ get(target: unknown, prop, receiver) {
const field = receiver.doc?.[prop];
if (prop in schema) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const desc = prop === 'proto' ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
if (typeof desc === 'object' && 'defaultVal' in desc && 'type' in desc) {
// defaultSpec
@@ -59,7 +56,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
}
return field;
},
- set(target: any, prop, value, receiver) {
+ set(target: unknown, prop, value, receiver) {
receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in acls
return true;
},
@@ -77,10 +74,10 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
const type = schema[key];
Object.defineProperty(proto, key, {
get() {
- return Cast(this.__doc[key], type as any);
+ return Cast(this.__doc[key], type as never);
},
set(setValue) {
- const value = Cast(setValue, type as any);
+ const value = Cast(setValue, type as never);
if (value !== undefined) {
this.__doc[key] = value;
return;
@@ -89,7 +86,7 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
},
});
}
- return function (doc: any) {
+ return function (doc: unknown) {
if (!(doc instanceof Doc)) {
throw new Error("Currently wrapping a schema in another schema isn't supported");
}
@@ -101,18 +98,18 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } {
- return undefined as any;
+ return undefined as never;
// (schema as any).proto = Doc;
// return schema as any;
}
export function listSpec<U extends ToConstructor<FieldType>>(type: U): ListSpec<ToType<U>> {
- return { List: type as any }; // TODO Types
+ return { List: type as never }; // TODO Types
}
export function defaultSpec<T extends ToConstructor<FieldType>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
return {
- type: type as any,
+ type: type as never,
defaultVal,
};
}
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index b294ee8c6..37b65684b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -149,7 +149,6 @@ export class ScriptField extends ObjectField {
...params,
},
transformer,
- typecheck: false,
editable: true,
addReturn: addReturn,
capturedVariables,
@@ -189,14 +188,12 @@ export class ScriptField extends ObjectField {
export class ComputedField extends ScriptField {
static undefined = '__undefined';
static useComputed = true;
- static DisableComputedFields() { this.useComputed = false; } // prettier-ignore
- static EnableComputedFields() { this.useComputed = true; } // prettier-ignore
- static WithoutComputed<T>(fn: () => T) {
- this.DisableComputedFields();
+ static DisableCompute<T>(fn: () => T) {
+ this.useComputed = false;
try {
return fn();
} finally {
- this.EnableComputedFields();
+ this.useComputed = true;
}
}
@@ -210,6 +207,7 @@ export class ComputedField extends ScriptField {
this._lastComputedResult =
this._cachedResult ??
computedFn(() =>
+ ((val) => val instanceof Array ? new List<number>(val) : val)(
this.script.compiled &&
this.script.run(
{
@@ -220,7 +218,7 @@ export class ComputedField extends ScriptField {
_readOnly_: true,
},
console.log
- ).result as FieldResult
+ ).result as FieldResult)
)(); // prettier-ignore
return this._lastComputedResult;
};
@@ -238,7 +236,8 @@ export class ComputedField extends ScriptField {
public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
if (!doc[`${fieldKey}_indexed`]) {
const flist = new List<number>(numberRange(curTimecode + 1).map(emptyFunction) as unknown as number[]);
- flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
+ if (Cast(doc[fieldKey], 'number', null) === undefined) delete flist[curTimecode];
+ else flist[curTimecode] = Cast(doc[fieldKey], 'number', null)!;
doc[`${fieldKey}_indexed`] = flist;
}
const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, ${defaultVal})`, {}, true, {});
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 474882959..ba2e9bb6f 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,5 +1,6 @@
import { DateField } from './DateField';
import { Doc, FieldType, FieldResult, Opt } from './Doc';
+import { InkField } from './InkField';
import { List } from './List';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
@@ -8,7 +9,7 @@ import { ScriptField } from './ScriptField';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
// eslint-disable-next-line no-use-before-define
-export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: never[]) => T;
export type DefaultFieldConstructor<T extends FieldType> = {
type: ToConstructor<T>;
@@ -17,7 +18,7 @@ export type DefaultFieldConstructor<T extends FieldType> = {
// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
export type ListSpec<T extends FieldType> = { List: ToConstructor<T> };
-export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any);
+export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => never);
export type ToType<T extends InterfaceValue> = T extends 'string'
? string
@@ -31,9 +32,9 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
T extends DefaultFieldConstructor<infer _U>
? never
- : T extends { new (...args: any[]): List<FieldType> }
+ : T extends { new (...args: never[]): List<FieldType> }
? never
- : T extends { new (...args: any[]): infer R }
+ : T extends { new (...args: never[]): infer R }
? R
: T extends (doc?: Doc) => infer R
? R
@@ -41,44 +42,40 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
export interface Interface {
[key: string]: InterfaceValue;
- // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
export type ToInterface<T extends Interface> = {
[P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
};
-// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
-
-export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
-export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
-export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
+export type Head<T extends unknown[]> = T extends [unknown, ...unknown[]] ? T[0] : Interface;
+export type Tail<T extends unknown[]> = ((...t: T) => unknown) extends (_: unknown, ...tail: infer TT) => unknown ? TT : [];
+export type HasTail<T extends unknown[]> = T extends [] | [unknown] ? false : true;
// TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
-export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T;
+export type WithoutRefField<T extends FieldType> = T extends RefField ? unknown : T;
export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
-export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-// eslint-disable-next-line no-redeclare
-export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
-// eslint-disable-next-line no-redeclare
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>> | undefined;
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>> | undefined;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
if (field instanceof Promise) {
- return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
+ return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as ToType<T>) as ToType<T>) : defaultVal === null ? undefined : defaultVal;
}
if (field !== undefined && !(field instanceof Promise)) {
if (typeof ctor === 'string') {
- // eslint-disable-next-line valid-typeof
if (typeof field === ctor) {
return field as ToType<T>;
}
} else if (typeof ctor === 'object') {
if (field instanceof List) {
- return field as any;
+ return field as ToType<T>;
}
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
} else if (field instanceof (ctor as any)) {
return field as ToType<T>;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
} else if (field instanceof ProxyField && field.value instanceof (ctor as any)) {
return field.value as ToType<T>;
}
@@ -86,63 +83,36 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
return defaultVal === null ? undefined : defaultVal;
}
-export function toList(doc: Doc | Doc[]) {
- return doc instanceof Doc ? [doc] : doc;
-}
+export function toList(doc: Doc | Doc[]) { return doc instanceof Doc ? [doc] : doc; } // prettier-ignore
export function DocCast(field: FieldResult, defaultVal?: Doc) {
- const doc = Cast(field, Doc, null);
- return doc && !(doc instanceof Promise) ? doc : (defaultVal as Doc);
-}
-
-export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
- return Cast(field, 'number', defaultVal);
-}
-
-export function StrCast(field: FieldResult, defaultVal: string | null = '') {
- return Cast(field, 'string', defaultVal);
-}
-
-export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) {
- return Cast(field, 'boolean', defaultVal);
-}
-export function DateCast(field: FieldResult) {
- return Cast(field, DateField, null);
-}
-export function RTFCast(field: FieldResult) {
- return Cast(field, RichTextField, null);
-}
-
-export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) {
- return Cast(field, ScriptField, defaultVal);
-}
-export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) {
- return Cast(field, CsvField, defaultVal);
-}
-export function WebCast(field: FieldResult, defaultVal: WebField | null = null) {
- return Cast(field, WebField, defaultVal);
-}
-export function VideoCast(field: FieldResult, defaultVal: VideoField | null = null) {
- return Cast(field, VideoField, defaultVal);
-}
-export function AudioCast(field: FieldResult, defaultVal: AudioField | null = null) {
- return Cast(field, AudioField, defaultVal);
-}
-export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) {
- return Cast(field, PdfField, defaultVal);
-}
-export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) {
- return Cast(field, ImageField, defaultVal);
+ return ((doc: Doc | undefined) => (doc && !(doc instanceof Promise) ? doc : defaultVal))(Cast(field, Doc, null));
+}
+export function NumCast (field: FieldResult, defaultVal: number | null = 0) { return Cast(field, 'number', defaultVal)!; } // prettier-ignore
+export function StrCast (field: FieldResult, defaultVal: string | null = '') { return Cast(field, 'string', defaultVal)!; } // prettier-ignore
+export function BoolCast (field: FieldResult, defaultVal: boolean | null = false) { return Cast(field, 'boolean', defaultVal)!; } // prettier-ignore
+export function DateCast (field: FieldResult, defaultVal: DateField | null = null) { return Cast(field, DateField, defaultVal); } // prettier-ignore
+export function RTFCast (field: FieldResult, defaultVal: RichTextField | null = null){ return Cast(field, RichTextField, defaultVal); } // prettier-ignore
+export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { return Cast(field, ScriptField, defaultVal); } // prettier-ignore
+export function CsvCast (field: FieldResult, defaultVal: CsvField | null = null) { return Cast(field, CsvField, defaultVal); } // prettier-ignore
+export function WebCast (field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } // prettier-ignore
+export function VideoCast (field: FieldResult, defaultVal: VideoField | null = null) { return Cast(field, VideoField, defaultVal); } // prettier-ignore
+export function AudioCast (field: FieldResult, defaultVal: AudioField | null = null) { return Cast(field, AudioField, defaultVal); } // prettier-ignore
+export function PDFCast (field: FieldResult, defaultVal: PdfField | null = null) { return Cast(field, PdfField, defaultVal); } // prettier-ignore
+export function ImageCast (field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } // prettier-ignore
+export function InkCast (field: FieldResult, defaultVal: InkField | null = null) { return Cast(field, InkField, defaultVal); } // prettier-ignore
+
+export function ImageCastToNameType(field: FieldResult, defaultVal: ImageField | null = null) {
+ const href = ImageCast(field, defaultVal)?.url.href;
+ return href ? [href.replace(/.[^.]*$/, ''), href.split('.').lastElement()] : ['', ''];
}
export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) {
- const href = ImageCast(field, defaultVal)?.url.href;
- return href ? `${href.split('.')[0]}${suffix}.${href.split('.')[1]}` : 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>;
-// eslint-disable-next-line no-redeclare
export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
-// eslint-disable-next-line no-redeclare
export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
return field instanceof Promise || field === undefined ? defaultValue : field;
}
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 3a83e7ca0..8dedb0be7 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -17,9 +17,7 @@ export abstract class URLField extends ObjectField {
readonly url: URL;
constructor(urlVal: string);
- // eslint-disable-next-line @typescript-eslint/no-shadow
constructor(urlVal: URL);
- // eslint-disable-next-line @typescript-eslint/no-shadow
constructor(urlVal: URL | string) {
super();
this.url =
@@ -50,6 +48,7 @@ export abstract class URLField extends ObjectField {
}
[Copy](): this {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
return new (this.constructor as any)(this.url);
}
}
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 33764aca5..799fa3758 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -241,19 +241,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
dataDoc[DirectLinks].forEach(link => distributeAcls(key, acl, link, visited, !!allowUpgrade));
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
+ DocListCast(dataDoc[Doc.LayoutDataKey(dataDoc)]).forEach(d => {
distributeAcls(key, acl, d, visited, !!allowUpgrade);
d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
+ DocListCast(dataDoc[Doc.LayoutDataKey(dataDoc) + '_annotations']).forEach(d => {
distributeAcls(key, acl, d, visited, !!allowUpgrade);
d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
- Object.keys(target) // share expanded layout templates (eg, for presElementBox'es )
- .filter(lkey => lkey.includes('layout[') && DocCast(target[lkey]))
- .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey]), visited, !!allowUpgrade));
+ Object.keys(target) // share expanded layout templates (eg, for PresSlideBox'es )
+ .filter(lkey => lkey.includes('layout_[') && DocCast(target[lkey]))
+ .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey])!, visited, !!allowUpgrade));
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
@@ -303,23 +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('_')) {
- 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('$')) {
+ target.__DATA__[prop.substring(1)] = 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)) {
@@ -351,6 +351,7 @@ export function getter(target: Doc | ListImpl<FieldType>, prop: string | symbol,
case DocAcl : return target[DocAcl];
case $mobx: return target.__fieldTuples[prop];
case DocLayout: return target.__LAYOUT__;
+ case DocData: return target.__DATA__;
case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
// eslint-disable-next-line no-fallthrough
default :
@@ -362,6 +363,8 @@ export function getter(target: Doc | ListImpl<FieldType>, prop: string | symbol,
const layoutProp = prop.startsWith('_') ? prop.substring(1) : undefined;
if (layoutProp && target.__LAYOUT__) return (target.__LAYOUT__ as Doc)[layoutProp];
+ const dataProp = prop.startsWith('$') ? prop.substring(1) : undefined;
+ if (dataProp && target.__DATA__) return (target.__DATA__ as Doc)[dataProp];
return getFieldImpl(target, layoutProp ?? prop, proxy);
}
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e75ede9df..e934e635b 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -6,6 +6,7 @@ import * as path from 'path';
import { DashUserModel } from '../authentication/DashUserModel';
import { DashUploadUtils } from '../DashUploadUtils';
import { _error, _invalid, _success, Method } from '../RouteManager';
+import { Upload } from '../SharedMediaTypes';
import { Directory, filesDirectory } from '../SocketData';
import ApiManager, { Registration } from './ApiManager';
@@ -70,54 +71,44 @@ export default class FireflyManager extends ApiManager {
);
uploadImageToDropbox = (fileUrl: string, user: DashUserModel | undefined, dbx = new Dropbox({ accessToken: user?.dropboxToken || '' })) =>
- new Promise<string | Error>((res, rej) =>
+ new Promise<string>((resolve, reject) => {
fs.readFile(path.join(filesDirectory, `${Directory.images}/${path.basename(fileUrl)}`), undefined, (err, contents) => {
if (err) {
- console.log('Error: ', err);
- rej();
- } else {
- dbx.filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
- .then(response => {
- dbx.filesGetTemporaryLink({ path: response.result.path_display ?? '' })
- .then(link => res(link.result.link))
- .catch(e => res(new Error(e.toString())));
- })
- .catch(e => {
+ return reject(new Error('Error reading file:' + err.message));
+ }
+
+ const uploadToDropbox = (dropboxClient: Dropbox) =>
+ dropboxClient
+ .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
+ .then(response =>
+ dropboxClient
+ .filesGetTemporaryLink({ path: response.result.path_display ?? '' })
+ .then(link => resolve(link.result.link))
+ .catch(linkErr => reject(new Error('Failed to get temporary link: ' + linkErr.message)))
+ )
+ .catch(uploadErr => {
if (user?.dropboxRefresh) {
- console.log('*********** try refresh dropbox for: ' + user.email + ' ***********');
- this.refreshDropboxToken(user).then(token => {
- if (!token) {
- console.log('Dropbox error: cannot refresh token');
- res(new Error(e.toString()));
- } else {
- const dbxNew = new Dropbox({ accessToken: user.dropboxToken || '' });
- dbxNew
- .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
- .then(response => {
- dbxNew
- .filesGetTemporaryLink({ path: response.result.path_display ?? '' })
- .then(link => res(link.result.link))
- .catch(linkErr => res(new Error(linkErr.toString())));
- })
- .catch(uploadErr => {
- console.log('Dropbox error:', uploadErr);
- res(new Error(uploadErr.toString()));
- });
- }
- });
+ console.log('Attempting to refresh Dropbox token for user:', user.email);
+ this.refreshDropboxToken(user)
+ .then(token => {
+ if (!token) return reject(new Error('Failed to refresh Dropbox token.' + user.email));
+
+ const dbxNew = new Dropbox({ accessToken: token });
+ uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message)));
+ })
+ .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message)));
} else {
- console.log('Dropbox error:', e);
- res(new Error(e.toString()));
+ reject(new Error('Dropbox error: ' + uploadErr.message));
}
});
- }
- })
- );
+
+ uploadToDropbox(dbx);
+ });
+ });
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 =>
@@ -290,44 +281,116 @@ export default class FireflyManager extends ApiManager {
register({
method: Method.POST,
subscription: '/queryFireflyImageFromStructure',
+ secureHandler: ({ req, res }) =>
+ new Promise<void>(resolver =>
+ (req.body.styleUrl
+ ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel)
+ : Promise.resolve(undefined)
+ )
+ .then(styleUrl =>
+ this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel)
+ .then(dropboxStructureUrl =>
+ ({ styleUrl, structureUrl: dropboxStructureUrl })
+ )
+
+ )
+ .then(uploads =>
+ this.generateImageFromStructure(
+ req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl
+ ).then(images =>
+ Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url))))
+ .then(dashImages =>
+ (dashImages.every(img => img instanceof Error))
+ ? _invalid(res, dashImages[0]!.message)
+ : _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error))))
+ )
+ )
+ )
+ .catch(e => {
+ _invalid(res, e.message);
+ resolver();
+ })
+ ), // prettier-ignore
+ });
+
+ register({
+ method: Method.POST,
+ subscription: '/outpaintImage',
secureHandler: ({ req, res }) =>
- new Promise<void>(resolver => {
- (req.body.styleUrl ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel) : Promise.resolve(undefined))
- .then(styleUrl => {
- if (styleUrl instanceof Error) {
- _invalid(res, styleUrl.message);
- throw new Error('Error uploading images to dropbox');
- }
- this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel)
- .then(dropboxStructureUrl => {
- if (dropboxStructureUrl instanceof Error) {
- _invalid(res, dropboxStructureUrl.message);
- throw new Error('Error uploading images to dropbox');
- }
- return { styleUrl, structureUrl: dropboxStructureUrl };
- })
- .then(uploads =>
- this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl)
- .then(images => {
- Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url))))
- .then(dashImages => {
- if (dashImages.every(img => img instanceof Error)) _invalid(res, dashImages[0]!.message);
- else _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error))));
- })
- .then(resolver);
- })
- .catch(e => {
- _invalid(res, e.message);
- resolver();
+ new Promise<void>(resolver =>
+ this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel)
+ .then(uploadUrl =>
+ this.getBearerToken()
+ .then(tokenResponse => tokenResponse?.json())
+ .then((tokenData: { access_token: string }) =>
+ fetch('https://firefly-api.adobe.io/v3/images/expand', {
+ method: 'POST',
+ headers: [
+ ['Content-Type', 'application/json'],
+ ['Accept', 'application/json'],
+ ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
+ ['Authorization', `Bearer ${tokenData.access_token}`],
+ ],
+ body: JSON.stringify({
+ image: {
+ source: { url: uploadUrl },
+ },
+ size: {
+ width: Math.round(req.body.newDimensions.width),
+ height: Math.round(req.body.newDimensions.height),
+ },
+ prompt: req.body.prompt ?? '',
+ numVariations: 1,
+ placement: {
+ inset: {
+ left: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2),
+ top: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2),
+ right: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2),
+ bottom: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2),
+ },
+ alignment: {
+ horizontal: req.body.halignment || 'center',
+ vertical: req.body.valignment || 'center',
+ },
+ },
+ }),
+ })
+ .then(expandResp => expandResp?.json())
+ .then(expandData => {
+ if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
+ console.error('Firefly validation error:', expandData);
+ _error(res, expandData.message ?? 'Failed to generate image');
+ } else {
+ return DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
+ .then((info: Upload.ImageInformation | Error) => {
+ if (info instanceof Error) {
+ _invalid(res, info.message);
+ } else {
+ _success(res, { url: info.accessPaths.agnostic.client });
+ }
+ })
+ .catch(uploadErr => {
+ console.error('DashUpload Error:', uploadErr);
+ _error(res, 'Failed to upload generated image.');
+ });
+ }
})
- );
- })
- .catch(() => {
- /* do nothing */
+ )
+ )
+ .catch(e => {
+ _invalid(res, e.message);
resolver();
- });
- }),
+ })
+ ),
});
+
+ /* register({
+ method: Method.POST
+ subscription: '/queryFireflyOutpaint',
+ secureHandler: ({req, res}) =>
+ this.outpaintImage()
+ })*/
+
register({
method: Method.POST,
subscription: '/queryFireflyImage',
@@ -354,23 +417,6 @@ export default class FireflyManager extends ApiManager {
)
),
});
- register({
- method: Method.POST,
- subscription: '/expandImage',
- secureHandler: ({ req, res }) =>
- this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl =>
- uploadUrl instanceof Error
- ? _invalid(res, uploadUrl.message)
- : this.expandImage(uploadUrl, req.body.prompt).then(text => {
- if (text.error_code) _error(res, text.message);
- else
- DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
- if (info instanceof Error) _invalid(res, info.message);
- else _success(res, info);
- });
- })
- ),
- });
// construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox
// https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox
diff --git a/src/server/ApiManagers/FlashcardManager.ts b/src/server/ApiManagers/FlashcardManager.ts
new file mode 100644
index 000000000..fd7c42437
--- /dev/null
+++ b/src/server/ApiManagers/FlashcardManager.ts
@@ -0,0 +1,161 @@
+/**
+ * @file FlashcardManager.ts
+ * @description This file defines the FlashcardManager class, responsible for managing API routes
+ * related to flashcard creation and manipulation. It provides functionality for handling file processing,
+ * running Python scripts in a virtual environment, and managing dependencies.
+ */
+
+import { spawn } from 'child_process';
+import * as fs from 'fs';
+import * as path from 'path';
+import { Method } from '../RouteManager';
+import ApiManager, { Registration } from './ApiManager';
+
+/**
+ * Runs a Python script using the provided virtual environment and passes file and option arguments.
+ * @param {string} venvPath - Path to the virtual environment.
+ * @param {string} scriptPath - Path to the Python script.
+ * @param {string} [file] - Optional file to pass to the Python script.
+ * @param {string} [drag] - Optional argument to control drag mode.
+ * @param {string} [smart] - Optional argument to control smart mode.
+ * @returns {Promise<string>} - Resolves with the output from the Python script, or rejects on error.
+ */
+function runPythonScript(venvPath: string, scriptPath: string, file?: string, drag?: string, smart?: string): Promise<string> {
+ return new Promise((resolve, reject) => {
+ const pythonPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'python.exe') : path.join(venvPath, 'bin', 'python3');
+
+ const tempFilePath = path.join(__dirname, `temp_data.txt`); // Unique temp file name
+
+ if (file) {
+ // Write the raw file data to the temp file without conversion
+ fs.writeFileSync(tempFilePath, file, 'utf8');
+ }
+
+ const pythonProcess = spawn(
+ pythonPath,
+ [scriptPath, file ? tempFilePath : undefined, drag, smart].filter(arg => arg !== undefined)
+ );
+
+ let pythonOutput = '';
+ let stderrOutput = '';
+
+ pythonProcess.stdout.on('data', data => {
+ pythonOutput += data.toString();
+ });
+
+ pythonProcess.stderr.on('data', data => {
+ stderrOutput += data.toString();
+ });
+
+ pythonProcess.on('close', code => {
+ if (code === 0) {
+ resolve(pythonOutput);
+ } else {
+ reject(`Python process exited with code ${code}: ${stderrOutput}`);
+ }
+ });
+ });
+}
+
+/**
+ * Installs Python dependencies using pip in the specified virtual environment.
+ * @param {string} venvPath - Path to the virtual environment.
+ * @param {string} requirementsPath - Path to the requirements.txt file.
+ * @returns {Promise<void>} - Resolves when dependencies are successfully installed, rejects on failure.
+ */
+function installDependencies(venvPath: string, requirementsPath: string): Promise<void> {
+ return new Promise((resolve, reject) => {
+ const pipPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'pip.exe') : path.join(venvPath, 'bin', 'pip3');
+
+ const installProcess = spawn(pipPath, ['install', '-r', requirementsPath]);
+
+ installProcess.stdout.on('data', data => {
+ console.log(`pip stdout: ${data}`);
+ });
+
+ installProcess.stderr.on('data', data => {
+ console.error(`pip stderr: ${data}`);
+ });
+
+ installProcess.on('close', code => {
+ if (code !== 0) {
+ reject(`Failed to install dependencies. Exit code: ${code}`);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Creates a new Python virtual environment.
+ * @param {string} venvPath - Path to the virtual environment that will be created.
+ * @returns {Promise<void>} - Resolves when the virtual environment is successfully created, rejects on failure.
+ */
+function createVirtualEnvironment(venvPath: string): Promise<void> {
+ return new Promise((resolve, reject) => {
+ const createVenvProcess = spawn('python3', ['-m', 'venv', venvPath]);
+
+ createVenvProcess.on('close', code => {
+ if (code !== 0) {
+ reject(`Failed to create virtual environment. Exit code: ${code}`);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Manages the creation of the virtual environment, installation of dependencies, and running of the Python script.
+ * @param {string} [file] - Optional file data to be processed by the Python script.
+ * @param {string} [drag] - Optional argument controlling drag mode.
+ * @param {string} [smart] - Optional argument controlling smart mode.
+ * @returns {Promise<string>} - Resolves with the Python script output, or rejects on failure.
+ */
+async function manageVenvAndRunScript(file?: string, drag?: string, smart?: string): Promise<string> {
+ const venvPath = path.join(__dirname, '../flashcard/venv'); // Virtual environment path
+ const requirementsPath = path.join(__dirname, '../flashcard/requirements.txt');
+ const pythonScriptPath = path.join(__dirname, '../flashcard/labels.py');
+ console.log('venvPath:', venvPath);
+
+ // Check if the virtual environment exists
+ if (!fs.existsSync(path.join(venvPath, 'bin', 'python3')) && !fs.existsSync(path.join(venvPath, 'Scripts', 'python.exe'))) {
+ await createVirtualEnvironment(venvPath);
+
+ await installDependencies(venvPath, requirementsPath);
+ }
+
+ return runPythonScript(venvPath, pythonScriptPath, file, drag, smart);
+}
+
+/**
+ * FlashcardManager class responsible for managing API routes related to flashcard functionality.
+ * It initializes API routes for handling YouTube subscriptions and label creation using a Python backend.
+ */
+export default class FlashcardManager extends ApiManager {
+ /**
+ * Initializes the API routes for the FlashcardManager class.
+ * @param {Registration} register - The registration function for defining API routes.
+ */
+ protected initialize(register: Registration): void {
+ register({
+ method: Method.POST,
+ subscription: '/labels',
+ secureHandler: async ({ req, res }) => {
+ const { file, drag, smart } = req.body;
+
+ try {
+ // Run the Python process
+ const result = await manageVenvAndRunScript(file, drag, smart);
+ res.status(200).send({ result });
+ } catch (error) {
+ console.error('Error initiating document creation:', error);
+ res.status(500).send({
+ error: 'Failed to initiate document creation',
+ });
+ }
+ },
+ });
+ }
+}
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/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 891316b80..8688ec049 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -213,9 +213,9 @@ export class DashSessionAgent extends AppliedSessionAgent {
// indicate success or failure
mainLog(`${error === null ? green('successfully dispatched') : red('failed to dispatch')} ${zipName} to ${cyan(to)}`);
error && mainLog(red(error.message));
- } catch (error: any) {
+ } catch (error: unknown) {
mainLog(red('unable to dispatch zipped backup...'));
- mainLog(red(error.message));
+ mainLog(red((error as { message: string }).message));
}
}
}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index a2747257a..f76371b0d 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -221,9 +221,7 @@ export namespace DashUploadUtils {
const parseExifData = async (source: string) => {
const image = await request.get(source, { encoding: null });
const { /* data, */ error } = await new Promise<{ data: ExifData; error: string | undefined }>(resolve => {
- new ExifImage({ image }, (exifError, data) => {
- resolve({ data, error: exifError?.message });
- });
+ new ExifImage({ image }, (exifError, data) => resolve({ data, error: exifError?.message }));
});
return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
};
@@ -295,7 +293,7 @@ export namespace DashUploadUtils {
try {
// Compute the native width and height ofthe image with an npm module
- const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl);
+ const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl).catch(() => ({ width: 0, height: 0 }));
// Bundle up the information into an object
return {
source,
@@ -307,7 +305,6 @@ export namespace DashUploadUtils {
...results,
};
} catch (e: unknown) {
- console.log(e);
return new Error(e ? e.toString?.() : 'unkown error');
}
};
@@ -450,14 +447,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;
diff --git a/src/server/index.ts b/src/server/index.ts
index 1f9af9ee0..3b77359ec 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -4,6 +4,7 @@ import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
import { logExecution } from './ActionUtilities';
import AssistantManager from './ApiManagers/AssistantManager';
+import FlashcardManager from './ApiManagers/FlashcardManager';
import DataVizManager from './ApiManagers/DataVizManager';
import DeleteManager from './ApiManagers/DeleteManager';
import DownloadManager from './ApiManagers/DownloadManager';
@@ -72,6 +73,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage
new GeneralGoogleManager(),
/* new GooglePhotosManager(), */ new DataVizManager(),
new AssistantManager(),
+ new FlashcardManager(),
new FireflyManager(),
];
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index dbfabed51..abcec13c0 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -11,6 +11,7 @@ declare module 'bezier-curve';
declare module 'fit-curve';
declare module 'iink-js';
declare module 'pdfjs-dist/web/pdf_viewer';
+declare module 'pdfjs-dist/build/pdf.mjs';
declare module 'react-jsx-parser';
declare module 'type_decls.d';
declare module 'standard-http-error';