aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/DocServer.ts67
-rw-r--r--src/client/apis/gpt/GPT.ts33
-rw-r--r--src/client/documents/Documents.ts128
-rw-r--r--src/client/util/CaptureManager.tsx2
-rw-r--r--src/client/util/CurrentUserUtils.ts81
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts2
-rw-r--r--src/client/util/LinkFollower.ts24
-rw-r--r--src/client/util/LinkManager.ts29
-rw-r--r--src/client/util/RTFMarkup.tsx4
-rw-r--r--src/client/util/Scripting.ts11
-rw-r--r--src/client/util/UndoManager.ts21
-rw-r--r--src/client/views/ContextMenu.tsx6
-rw-r--r--src/client/views/DashboardView.tsx2
-rw-r--r--src/client/views/DocComponent.tsx6
-rw-r--r--src/client/views/DocumentButtonBar.tsx2
-rw-r--r--src/client/views/DocumentDecorations.scss2
-rw-r--r--src/client/views/DocumentDecorations.tsx106
-rw-r--r--src/client/views/EditableView.tsx2
-rw-r--r--src/client/views/FieldsDropdown.tsx1
-rw-r--r--src/client/views/GlobalKeyHandler.ts8
-rw-r--r--src/client/views/InkControlPtHandles.tsx89
-rw-r--r--src/client/views/InkingStroke.tsx20
-rw-r--r--src/client/views/LightboxView.scss1
-rw-r--r--src/client/views/LightboxView.tsx23
-rw-r--r--src/client/views/MainView.scss6
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/client/views/MarqueeAnnotator.tsx22
-rw-r--r--src/client/views/ObservableReactComponent.tsx3
-rw-r--r--src/client/views/PreviewCursor.tsx12
-rw-r--r--src/client/views/PropertiesButtons.tsx4
-rw-r--r--src/client/views/PropertiesView.scss5
-rw-r--r--src/client/views/PropertiesView.tsx10
-rw-r--r--src/client/views/SidebarAnnos.tsx4
-rw-r--r--src/client/views/StyleProvider.scss1
-rw-r--r--src/client/views/StyleProvider.tsx30
-rw-r--r--src/client/views/TemplateMenu.tsx2
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx13
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx11
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx46
-rw-r--r--src/client/views/collections/CollectionMenu.tsx12
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss47
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx102
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx45
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx12
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx13
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx6
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx21
-rw-r--r--src/client/views/collections/CollectionSubView.tsx21
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx16
-rw-r--r--src/client/views/collections/TabDocView.tsx3
-rw-r--r--src/client/views/collections/TreeView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx81
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx10
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss9
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx19
-rw-r--r--src/client/views/global/globalScripts.ts87
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx2
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx2
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.tsx108
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.tsx295
-rw-r--r--src/client/views/nodes/AudioBox.tsx14
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx24
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx8
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx100
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss63
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx224
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx7
-rw-r--r--src/client/views/nodes/DocumentView.scss7
-rw-r--r--src/client/views/nodes/DocumentView.tsx62
-rw-r--r--src/client/views/nodes/EquationBox.tsx13
-rw-r--r--src/client/views/nodes/FieldView.tsx1
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx35
-rw-r--r--src/client/views/nodes/ImageBox.tsx101
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx7
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/LinkBox.tsx99
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx6
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx6
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx794
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx4
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx8
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.scss322
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx4
-rw-r--r--src/client/views/nodes/VideoBox.tsx20
-rw-r--r--src/client/views/nodes/WebBox.tsx71
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss33
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx136
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx7
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss15
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx273
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts96
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx290
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts61
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts16
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts37
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx6
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx13
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx2
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx15
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx152
-rw-r--r--src/client/views/pdf/PDFViewer.tsx2
-rw-r--r--src/client/views/topbar/TopBar.tsx6
111 files changed, 2678 insertions, 2289 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 6217cf04b..321572071 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -7,10 +7,10 @@ import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
import { ObjectField } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
import { DocCast, StrCast } from '../fields/Types';
-import { Socket } from '../../node_modules/socket.io/dist/index';
//import MobileInkOverlay from '../mobile/MobileInkOverlay';
+import { io, Socket } from 'socket.io-client';
import { emptyFunction, Utils } from '../Utils';
-import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
import { DocumentType } from './documents/DocumentTypes';
import { LinkManager } from './util/LinkManager';
import { SerializationHelper } from './util/SerializationHelper';
@@ -32,6 +32,24 @@ import { SerializationHelper } from './util/SerializationHelper';
export namespace DocServer {
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
+ socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
+ }
+ export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
+ //log('Emit', message.Name, args, false);
+ socket.emit(message.Message, args);
+ }
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
+ //log('Emit', message.Name, args, false);
+ if (fn) {
+ socket.emit(message.Message, args, Utils.loggingCallback('Receiving', fn, message.Name));
+ } else {
+ return new Promise<any>(res => socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name)));
+ }
+ }
+
export function FindDocByTitle(title: string) {
const foundDocId =
title &&
@@ -65,10 +83,10 @@ export namespace DocServer {
cacheDocumentIds = newCacheUpdate;
// print out cached docs
- console.log('Set cached docs = ');
+ Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
- 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(Utils.prepend('/setCacheDocumentIds'), {
body: {
@@ -132,20 +150,20 @@ export namespace DocServer {
export namespace Mobile {
export function dispatchGesturePoints(content: GestureContent) {
- Utils.Emit(_socket, MessageStore.GesturePoints, content);
+ DocServer.Emit(_socket, MessageStore.GesturePoints, content);
}
export function dispatchOverlayTrigger(content: MobileInkOverlayContent) {
// _socket.emit("dispatchBoxTrigger");
- Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
+ DocServer.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
}
export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPositionContent) {
- Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
+ DocServer.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
}
export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) {
- Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content);
+ DocServer.Emit(_socket, MessageStore.MobileDocumentUpload, content);
}
}
@@ -171,7 +189,8 @@ export namespace DocServer {
_cache = {};
USER_ID = identifier;
protocol = protocol.startsWith('https') ? 'wss' : 'ws';
- _socket = require('socket.io-client')(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+ _socket = io(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+ _socket.on("connect_error", (err:any) => console.log(err));
// io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
_GetCachedRefField = _GetCachedRefFieldImpl;
@@ -184,11 +203,11 @@ export namespace DocServer {
* Whenever the server sends us its handshake message on our
* websocket, we use the above function to return the handshake.
*/
- Utils.AddServerHandler(_socket, MessageStore.Foo, onConnection);
- Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
- Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+ DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
+ DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
// // mobile ink overlay socket events to communicate between mobile view and desktop view
// _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
@@ -252,7 +271,7 @@ export namespace DocServer {
* all documents in the database.
*/
export function deleteDatabase() {
- Utils.Emit(_socket, MessageStore.DeleteAll, {});
+ DocServer.Emit(_socket, MessageStore.DeleteAll, {});
}
}
@@ -275,7 +294,7 @@ export namespace DocServer {
// synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
// field for the given ids. This returns a promise, which, when resolved, indicates the the JSON serialized version of
// the field has been returned from the server
- const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
+ const getSerializedField = DocServer.EmitCallback(_socket, MessageStore.GetRefField, id);
// when the serialized RefField has been received, go head and begin deserializing it into an object.
// Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
@@ -339,15 +358,15 @@ export namespace DocServer {
}
export async function getYoutubeChannels() {
- return await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
+ return await DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
}
export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
- Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.SearchVideo, userInput: videoTitle }, callBack);
+ DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.SearchVideo, userInput: videoTitle }, callBack);
}
export function getYoutubeVideoDetails(videoIds: string, callBack: (videoDetails: any[]) => void) {
- Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack);
+ DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack);
}
/**
@@ -397,7 +416,7 @@ export namespace DocServer {
// the fields have been returned from the server
console.log('Requesting ' + requestedIds.length);
setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
- const serializedFields = await Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
+ const serializedFields = await DocServer.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
// Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all
@@ -502,7 +521,7 @@ export namespace DocServer {
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.CreateField, initialState);
+ Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
}
let _CreateField: (field: RefField) => void = errorFunc;
@@ -522,7 +541,7 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
let _UpdateField: (id: string, diff: any) => void = errorFunc;
@@ -559,11 +578,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteField, id);
+ Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteFields, ids);
+ Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index fb51278ae..30194f9f8 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,9 +1,11 @@
import { ClientOptions, OpenAI } from 'openai';
+import { ChatCompletionMessageParam } from 'openai/resources';
enum GPTCallType {
SUMMARY = 'summary',
COMPLETION = 'completion',
EDIT = 'edit',
+ DATA = 'data',
}
type GPTCallOpts = {
@@ -13,10 +15,16 @@ type GPTCallOpts = {
prompt: string;
};
+/**
+ * Replace completions (deprecated) with chat
+ */
+
const callTypeMap: { [type: string]: GPTCallOpts } = {
- summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
- edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
- completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
+ // newest model: gpt-4
+ summary: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' },
+ edit: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' },
+ completion: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." },
+ data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
};
/**
@@ -25,7 +33,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
* @param inputText Text to process
* @returns AI Output
*/
-const gptAPICall = async (inputText: string, callType: GPTCallType) => {
+const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => {
if (callType === GPTCallType.SUMMARY) inputText += '.';
const opts: GPTCallOpts = callTypeMap[callType];
try {
@@ -34,13 +42,21 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
dangerouslyAllowBrowser: true,
};
const openai = new OpenAI(configuration);
- const response = await openai.completions.create({
+
+ let usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
+ let messages: ChatCompletionMessageParam[] = [
+ { role: 'system', content: usePrompt },
+ { role: 'user', content: inputText },
+ ];
+
+ const response = await openai.chat.completions.create({
model: opts.model,
max_tokens: opts.maxTokens,
temperature: opts.temp,
- prompt: `${opts.prompt}${inputText}`,
+ messages,
});
- return response.choices[0].text;
+ const content = response.choices[0].message.content;
+ return content;
} catch (err) {
console.log(err);
return 'Error connecting with API.';
@@ -60,8 +76,7 @@ const gptImageCall = async (prompt: string, n?: number) => {
n: n ?? 1,
size: '1024x1024',
});
- return response.data.map((data: any) => data.url);
- // return response.data.data[0].url;
+ return response.data.map(data => data.url);
} catch (err) {
console.error(err);
return;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1d7c73306..b160379df 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { action, reaction, runInAction } from 'mobx';
import { basename } from 'path';
+import { OmitKeys, Utils } from '../../Utils';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
import { DocData, Initializing } from '../../fields/DocSymbols';
@@ -13,30 +14,29 @@ import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
-import { inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { SharingPermissions, inheritParentAcls } from '../../fields/util';
import { Upload } from '../../server/SharedMediaTypes';
-import { OmitKeys, Utils } from '../../Utils';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
+import { YoutubeBox } from '../apis/youtube/YoutubeBox';
import { DragManager, dropActionType } from '../util/DragManager';
import { FollowLinkScript } from '../util/LinkFollower';
import { LinkManager } from '../util/LinkManager';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoable, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionView } from '../views/collections/CollectionView';
+import { UndoManager, undoable } from '../util/UndoManager';
import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
import { AudioBox, media_state } from '../views/nodes/AudioBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
+import { OpenWhere } from '../views/nodes/DocumentView';
import { EquationBox } from '../views/nodes/EquationBox';
import { FieldViewProps } from '../views/nodes/FieldView';
import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
import { ImageBox } from '../views/nodes/ImageBox';
import { KeyValueBox } from '../views/nodes/KeyValueBox';
@@ -52,15 +52,15 @@ import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
import { ScriptingBox } from '../views/nodes/ScriptingBox';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { PresBox } from '../views/nodes/trails/PresBox';
-import { PresElementBox } from '../views/nodes/trails/PresElementBox';
import { VideoBox } from '../views/nodes/VideoBox';
import { WebBox } from '../views/nodes/WebBox';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { PresBox } from '../views/nodes/trails/PresBox';
+import { PresElementBox } from '../views/nodes/trails/PresElementBox';
import { SearchBox } from '../views/search/SearchBox';
import { CollectionViewType, DocumentType } from './DocumentTypes';
-import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
-import { OpenWhere } from '../views/nodes/DocumentView';
-const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
+const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
class EmptyBox {
@@ -204,6 +204,7 @@ export class DocumentOptions {
overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false);
overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', 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);
_dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
latitude?: NUMt = new NumInfo('latitude coordinate', false);
@@ -253,9 +254,9 @@ export class DocumentOptions {
dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
_undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
- _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title', false);
- _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
- _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
+ _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
+ _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
+ _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
_lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
_lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
@@ -275,6 +276,7 @@ export class DocumentOptions {
layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected');
layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
layout_borderRounding?: string;
+ _layout_borderRounding?: STRt = new StrInfo('amount of rounding to document view corners');
_layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false);
_layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
_layout_reflowVertical?: BOOLt = new BoolInfo('permit vertical resizing with content "reflow"');
@@ -282,6 +284,7 @@ export class DocumentOptions {
layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)');
_layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
+ _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');
@@ -317,6 +320,7 @@ export class DocumentOptions {
_label_minFontSize?: NUMt = new NumInfo('minimum font size for labelBoxes', false);
_label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
+ stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke');
mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)');
@@ -916,8 +920,6 @@ export namespace Docs {
title,
type,
isBaseProto: true,
- x: 0,
- y: 0,
_width: 300,
'acl-Guest': SharingPermissions.View,
...(template.options || {}),
@@ -1016,8 +1018,8 @@ export namespace Docs {
}
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- const imgField = url instanceof ImageField ? url : new ImageField(url);
- return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+ const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
+ return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
}
export function PresDocument(options: DocumentOptions = {}) {
@@ -1205,10 +1207,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id, '', undefined, undefined, true);
}
- export function HTMLMarkerDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id);
- }
-
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
return InstanceFromProto(
Prototypes.get(DocumentType.COL),
@@ -1519,6 +1517,9 @@ export namespace DocUtils {
return linkDoc;
});
+ const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1';
+ const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2';
+
return makeLink(
Docs.Create.LinkDocument(
source,
@@ -1532,7 +1533,10 @@ export namespace DocUtils {
link_displayLine: linkSettings.link_displayLine,
link_relationship: linkSettings.link_relationship,
link_description: linkSettings.link_description,
+ x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any,
+ y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any,
link_autoMoveAnchors: true,
+ _lockedPosition: true,
_layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
_layout_showTitle: '',
// _layout_showCaption: 'link_description',
@@ -1593,7 +1597,7 @@ export namespace DocUtils {
}
});
items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
- items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
+ items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
}
return doc;
}
@@ -1776,13 +1780,23 @@ export namespace DocUtils {
subitems: userDocList,
icon: 'file',
});
- } // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
+ }
+
+ // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
+
+ /**
+ * Applies a template to a Doc and logs the action with the UndoManager
+ * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon').
+ * Alternatively, the signature can be omitted and the template can be provided.
+ * @param doc the Doc to apply the template to.
+ * @param creator a function that will create the template if it doesn't exist
+ * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide)
+ * @param template the template to use (optional if templateSignature is provided)
+ * @returns doc
+ */
+ export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) {
const batch = UndoManager.StartBatch('makeCustomViewClicked');
- runInAction(() => {
- doc.layout_fieldKey = 'layout_' + templateSignature;
- createCustomView(doc, creator, templateSignature, docLayoutTemplate);
- });
+ createCustomView(doc, creator, templateSignature || StrCast(template?.title), template);
batch.end();
return doc;
}
@@ -1791,10 +1805,12 @@ export namespace DocUtils {
const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data);
const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data);
const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
+ const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data);
const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
const allTemplates = iconViews
.concat(templBtns)
.concat(noteTypes)
+ .concat(userTypes)
.concat(clickFuncs)
.map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
.filter(doc => doc.isTemplateDoc);
@@ -1806,6 +1822,7 @@ export namespace DocUtils {
}
export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
const templateName = templateSignature.replace(/\(.*\)/, '');
+ doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title);
docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
const customName = 'layout_' + templateSignature;
@@ -1938,6 +1955,31 @@ export namespace DocUtils {
return dd;
}
+ export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+ if (Upload.isImageInformation(result)) {
+ const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+ const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+ proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+ proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto['data-nativeOrientation']) >= 5) {
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ }
+ proto.data_exif = JSON.stringify(result.exifData?.data);
+ proto.data_contentSize = result.contentSize;
+ // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+ const latitude = result.exifData?.data?.GPSLatitude;
+ const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+ const longitude = result.exifData?.data?.GPSLongitude;
+ const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+ if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+ proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+ proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+ }
+ }
+ }
+
async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
if (result instanceof Error) {
alert(`Upload failed: ${result.message}`);
@@ -1949,28 +1991,7 @@ export namespace DocUtils {
if (doc) {
const proto = Doc.GetProto(doc);
proto.text = result.rawText;
- if (Upload.isImageInformation(result)) {
- const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
- const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
- proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto['data-nativeOrientation']) >= 5) {
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- }
- proto.data_exif = JSON.stringify(result.exifData?.data);
- proto.data_contentSize = result.contentSize;
- // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
- const latitude = result.exifData?.data?.GPSLatitude;
- const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
- const longitude = result.exifData?.data?.GPSLongitude;
- const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
- if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
- proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
- proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
- }
- }
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
if (Upload.isVideoInformation(result)) {
proto.data_duration = result.duration;
}
@@ -1997,7 +2018,6 @@ export namespace DocUtils {
_layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
_layout_fitWidth: true,
_layout_autoHeight: true,
- _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
}),
});
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 2e13aff2f..8451357ef 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -2,9 +2,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { addStyleSheet } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
-import { addStyleSheet } from '../../Utils';
import { LightboxView } from '../views/LightboxView';
import { MainViewModal } from '../views/MainViewModal';
import './CaptureManager.scss';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index bbee18707..081115879 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -121,9 +121,9 @@ export class CurrentUserUtils {
static setupNoteTemplates(doc: Doc, field="template_notes") {
const tempNotes = DocCast(doc[field]);
const reqdTempOpts:DocumentOptions[] = [
- { title: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
- { title: "Idea", backgroundColor: "pink", icon: "lightbulb" },
- { title: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
+ { 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 reqdNoteList = [...reqdTempOpts.map(opts => {
const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
@@ -220,34 +220,26 @@ export class CurrentUserUtils {
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
- const headerBtnHgt = 10;
- const headerTemplate = (opts:DocumentOptions) => {
- const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Untitled Header",
- 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._headerHeight||0}px)'/>` +
- " <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' backgroundColor='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
- ` <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._layout_autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’} >Metadata</HTMLdiv>` +
- "</HTMLdiv>"
- }, "header");
// "<div style={'height:100%'}>" +
- // " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
- // " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
+ // " <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)'/>" +
// "</div>";
- MakeTemplate(Doc.GetProto(header));
- return header;
- }
- const slideView = (opts:DocumentOptions) => {
- const slide = Docs.Create.MultirowDocument(
+ 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>
+ </HTMLdiv>`
+ }, "header"));
+ const slideView = (opts:DocumentOptions) =>
+ MakeTemplate(Docs.Create.MultirowDocument(
[
Docs.Create.MulticolumnDocument([], { title: "hero", _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: "Untitled Slide View"});
-
- MakeTemplate(Doc.GetProto(slide));
- return slide;
- }
+ ], {...opts, title: "Slide View Template"}));
const plotlyApi = () => {
var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
if (!plotly) {
@@ -321,9 +313,9 @@ export class CurrentUserUtils {
{type:"text",text:" "},
{type:"text",text:"Minerals in my tap water"},
{type:"text",text:"\n \"Calcium\" : "},
- {type:"dashField",attrs:{fieldKey:"calcium",docId:"","hideKey":false,editable:true}},
+ {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:true,hideValue:false,editable:true}},
{type:"text",text:"\n \"Potassium\" : "},
- {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,editable:true}},
+ {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:true,hideValue:false,editable:true}},
{type:"text",text:"\n \"Magnesium\" : 10.01"}
]}
]},
@@ -349,6 +341,7 @@ pie title Minerals in my tap water
creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
}[] = [
{key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }},
+ {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }},
{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
{key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }},
{key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
@@ -356,14 +349,14 @@ pie title Minerals in my tap water
{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: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }},
- {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
+ {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, _layout_fitWidth: true, }},
{key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
{key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }},
{key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
{key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
- {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: 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: "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, 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, }},
@@ -386,6 +379,7 @@ pie title Minerals in my tap water
{ 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 iamge", 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)},
@@ -723,8 +717,6 @@ pie title Minerals in my tap water
{ title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, 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 click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, 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: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
@@ -733,7 +725,7 @@ pie title Minerals in my tap water
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
{ title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
{ title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
- { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
@@ -764,6 +756,7 @@ pie title Minerals in my tap water
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.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:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
+ { title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
{ title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} },
];
@@ -772,7 +765,7 @@ pie title Minerals in my tap water
static schemaTools():Button[] {
return [
{title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
- {title: "1 Line", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+ {title: "1 Line", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
{title: "DataViz", toolTip: "Turn Schema Table into Data Visualization Doc", btnType: ButtonType.ClickButton, icon: "chart-bar", scripts:{ onClick: '{ datavizFromSchema()'} }, ];
}
@@ -780,12 +773,18 @@ pie title Minerals in my tap water
return [
{ title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }},
{ title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}},
- { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
+ { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
static videoTools() {
return [
- { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton, icon: "camera", scripts: { onClick: '{ return videoSnapshot(); }' }},
+ { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton, icon: "camera", scripts: { onClick: '{ return videoSnapshot(); }' }},
+ ];
+ }
+ static imageTools() {
+ return [
+ { title: "Pixels",toolTip: "Set Native Pixel Sizze", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }},
+ { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }},
];
}
static contextMenuTools():Button[] {
@@ -810,13 +809,14 @@ pie title Minerals in my tap water
{ title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
{ title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
{ title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_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: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_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: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_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: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
{ title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
];
}
/// initializes a context menu button for the top bar context menu
- static setupContextMenuButton(params:Button, btnDoc?:Doc) {
+ static setupContextMenuButton(params:Button, btnDoc?:Doc, btnContainer?:Doc) {
const reqdOpts:DocumentOptions = {
...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
color: Colors.WHITE, isSystem: true,
@@ -824,6 +824,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
};
const reqdFuncs:{[key:string]:any} = {
...params.funcs,
@@ -833,15 +834,15 @@ pie title Minerals in my tap water
static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
- const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
+ const menuBtnDoc = DocListCast(menuDoc?.data).find( doc => doc.title === params.title);
const subMenu = params.subMenu;
if (!subMenu) { // button does not have a sub menu
- return this.setupContextMenuButton(params, menuBtnDoc);
+ return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
}
// linear view
const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
- linearView_SubMenu: true, linearView_Expandable: true};
+ linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a38a330da..40d28c690 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -59,7 +59,7 @@ export class DocumentManager {
private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
if (doc) {
- const dv = this.getDocumentView(doc);
+ const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
this._viewRenderedCbs.push({ doc, func });
if (dv) {
this.callAddViewFuncs(dv);
@@ -262,7 +262,7 @@ export class DocumentManager {
return res(this.getDocumentView(docContextPath[0])!);
}
options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
if (options.openLocation === OpenWhere.lightbox) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index aa0f77c72..9627c5df2 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -28,7 +28,7 @@ import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
import { DocData } from '../../fields/DocSymbols';
-const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
+const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
export enum dropActionType {
embed = 'embed', // create a new embedding of the dragged document for the new location
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 20261859c..6c0bf3242 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -110,18 +110,18 @@ export class LinkFollower {
movedTarget = true;
}
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) {
- target.x = moveTo[0];
- movedTarget = true;
- }
- if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
- target.y = moveTo[1];
- movedTarget = true;
- }
- if (movedTarget) setTimeout(doFollow);
- else doFollow(true);
- } else doFollow(true);
+ }
+ const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
+ if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ target.x = moveTo[0];
+ movedTarget = true;
+ }
+ if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ target.y = moveTo[1];
+ movedTarget = true;
+ }
+ if (movedTarget) setTimeout(doFollow);
+ else doFollow(true);
} else {
allFinished();
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index cf16c4d6d..8972bf705 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -152,10 +152,12 @@ export class LinkManager {
}
}
public deleteLink(linkDoc: Doc) {
- return Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
+ const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
+ linkDoc[DocData].link_anchor_1 = linkDoc[DocData].link_anchor_2 = undefined;
+ return ret;
}
public deleteAllLinksOnAnchor(anchor: Doc) {
- LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
+ LinkManager.Instance.relatedLinker(anchor).forEach(LinkManager.Instance.deleteLink);
}
public getAllRelatedLinks(anchor: Doc) {
@@ -165,7 +167,8 @@ export class LinkManager {
return anchor ? Array.from(anchor[DocData][DirectLinks]) : [];
} // finds all links that contain the given anchor
- relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
+ computedRelatedLinks = (anchor: Doc, processed: Doc[]): Doc[] => {
+ if (Doc.IsSystem(anchor)) return [];
if (!anchor || anchor instanceof Promise || Doc.GetProto(anchor) instanceof Promise) {
console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
return [];
@@ -174,7 +177,19 @@ 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 any));
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']);
- return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks));
+ return Array.from(
+ annos.reduce((set, anno) => {
+ if (!processed.includes(anno)) {
+ processed.push(anno);
+ this.computedRelatedLinks(anno, processed).forEach(link => set.add(link));
+ }
+ return set;
+ }, new Set<Doc>(dirLinks))
+ );
+ };
+
+ relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
+ return this.computedRelatedLinks(anchor, [anchor]);
}, true);
// returns map of group type to anchor's links in that group type
@@ -209,10 +224,10 @@ export class LinkManager {
const a1 = DocCast(linkDoc.link_anchor_1);
const a2 = DocCast(linkDoc.link_anchor_2);
if (linkDoc.link_matchEmbeddings) {
- return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+ return [a2, a2?.annotationOn].includes(anchor) ? '2' : '1';
}
- if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
- if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
+ if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
+ if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
// const a1 = DocCast(linkDoc.link_anchor_1);
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 315daad42..35b1579df 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -30,7 +30,7 @@ export class RTFMarkup extends React.Component<{}> {
return (
<div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
- <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
+ <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
</p>
<p>
@@ -132,7 +132,7 @@ export class RTFMarkup extends React.Component<{}> {
render() {
return (
<MainViewModal
- dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }}
+ dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
contents={this.cheatSheet}
isDisplayed={this.isOpen}
interactive={true}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index f5e162d16..422e708bc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -88,15 +88,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
}
const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
- if (batch) {
- batch.end();
- }
-
+ batch?.end();
return { success: true, result };
} catch (error) {
- if (batch) {
- batch.end();
- }
+ batch?.end();
onError?.(script + ' ' + error);
return { success: false, error, result: errorVal };
}
@@ -255,7 +250,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const outputText = host.readFile('file.js');
const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
-
+ if (script.startsWith('@')) options.typecheck = true; // need the compilation to fail so that the script will return itself as a string (instead of nothing)
const result = Run(outputText, paramNames, diagnostics, script, options);
if (options.globals) {
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 421855bf3..857ca852f 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,5 @@
import { observable, action, runInAction } from 'mobx';
-import { Field } from '../../fields/Doc';
+import { Doc, Field } from '../../fields/Doc';
import { RichTextField } from '../../fields/RichTextField';
import { Without } from '../../Utils';
@@ -97,13 +97,14 @@ export namespace UndoManager {
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- console.log(
- ' '.slice(0, batchCounter.get()) +
- 'UndoEvent : ' +
- event.prop +
- ' = ' +
- (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
- );
+ Doc.MyDockedBtns.linearView_IsOpen &&
+ console.log(
+ ' '.slice(0, batchCounter.get()) +
+ 'UndoEvent : ' +
+ event.prop +
+ ' = ' +
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+ );
currentBatch.push(event);
tempEvents?.push(event);
}
@@ -171,7 +172,7 @@ export namespace UndoManager {
}
export function StartBatch(batchName: string): Batch {
- console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+ Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
runInAction(() => batchCounter.set(batchCounter.get() + 1));
if (currentBatch === undefined) {
currentBatch = [];
@@ -181,7 +182,7 @@ export namespace UndoManager {
const EndBatch = action((batchName: string, cancel: boolean = false) => {
runInAction(() => batchCounter.set(batchCounter.get() - 1));
- console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+ Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
if (batchCounter.get() === 0 && currentBatch?.length) {
if (!cancel) {
undoStack.push(currentBatch);
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8f4e43978..ca877b93e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
this._width = DivWidth(r);
this._height = DivHeight(r);
}
+ this._searchRef.current?.focus();
})}
style={{
display: this._display ? '' : 'none',
left: this.pageX,
- ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+ ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
background: SettingsManager.userBackgroundColor,
color: SettingsManager.userColor,
}}>
@@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
const item = this.flatItems[this._selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
- } else if (this._searchString.startsWith(this._defaultPrefix)) {
+ } else {
+ //if (this._searchString.startsWith(this._defaultPrefix)) {
this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
}
this.closeMenu();
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 146ac5b01..a4f598d1a 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -377,7 +377,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
- dashboardDoc['pane-count'] = 1;
+ dashboardDoc.pane_count = 1;
freeformDoc.embedContainer = dashboardDoc;
dashboardDoc.myOverlayDocs = new List<Doc>();
dashboardDoc.myPublishedDocs = new List<Doc>();
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 18e7ab12a..de4df1830 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -5,17 +5,17 @@ import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
+import { RefField } from '../../fields/RefField';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
+import { DragManager } from '../util/DragManager';
import { ObservableReactComponent } from './ObservableReactComponent';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
import { PinProps } from './nodes/trails';
-import { RefField } from '../../fields/RefField';
-import { DragManager } from '../util/DragManager';
/**
* Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index d65e0b406..15ce4c15f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -416,7 +416,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
)}
{DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
- {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>}
+ <div className="documentButtonBar-button">{this.templateButton}</div>
{!SelectionManager.Views?.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>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ac0ef054c..239c0a977 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -14,7 +14,7 @@ $resizeHandler: 8px;
height: 30;
width: 30;
right: -40;
- bottom: -40;
+ bottom: -20;
//top: calc(50% - 15px);
position: absolute;
pointer-events: all;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fb9f0fc1..2a44a9739 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,6 @@ import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
import { InkField } from '../../fields/InkField';
-import { RichTextField } from '../../fields/RichTextField';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
@@ -33,7 +32,9 @@ import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { Colors } from './global/globalEnums';
import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { identity } from 'lodash';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@observable _showNothing = true;
@observable private _accumulatedTitle = '';
- @observable private _titleControlString: string = '#title';
+ @observable private _titleControlString: string = '$title';
@observable private _editingTitle = false;
@observable private _hidden = false;
@observable private _isRotating: boolean = false;
@@ -73,11 +74,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
DocumentDecorations.Instance = this;
document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
action(e => {
+ let inputting = false;
+ if (this._titleControlString.startsWith('$')) {
+ const titleFieldKey = this._titleControlString.substring(1);
+ if (SelectionManager.Views[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
+ inputting = true;
+ }
+ }
const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
const {x,y} = Utils.rotPt(e.clientX - center.x,
e.clientY - center.y,
NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
- (this._showNothing = !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
+ (this._showNothing = !inputting && !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
(this.Bounds.x > center.x+x || this.Bounds.r < center.x+x ||
this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
})); // prettier-ignore
@@ -111,9 +119,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@action
titleBlur = () => {
- if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
+ if (this._accumulatedTitle.startsWith('$')) {
this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith('#')) {
+ } else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
}
@@ -124,33 +132,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
SelectionManager.Views.forEach(d => {
if (titleFieldKey === 'title') {
d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
- if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
- Doc.RemFromMyPublished(d.Document);
- }
- if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
- Doc.AddToMyPublished(d.Document);
- }
- }
- //@ts-ignore
- const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
-
- if (titleField.toString().startsWith('<this>')) {
- const title = titleField.toString().replace(/<this>\.?/, '');
- const curKey = Doc.LayoutFieldKey(d.Document);
- if (curKey !== title) {
- if (title) {
- if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
- d.Document.layout_fieldKey = `layout_${title}`;
- d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
- d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
- }
- } else {
- d.Document.layout_fieldKey = undefined;
- }
- }
- } else {
- Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
}
+ KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
}),
'edit title'
);
@@ -181,7 +164,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
e => this.onBackgroundMove(true, e),
emptyFunction,
action(e => {
- !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
})
@@ -354,8 +338,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
- seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
- seldocview.Document.rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+ seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+ seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
};
@action
@@ -368,8 +352,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
(e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean,
action(e => (this._isRotating = false)), // upEvent
- action(e => (seldocview.Document.rotation_centerX = seldocview.Document.rotation_centerY = 0))
+ action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)),
+ true
); // prettier-ignore
+ e.stopPropagation();
};
@action
@@ -441,9 +427,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
- this._resizeHdlId = e.currentTarget.className;
+ const id = (this._resizeHdlId = e.currentTarget.className);
+ const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0;
const bounds = e.currentTarget.getBoundingClientRect();
- this._offset = { x: this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX, y: this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY };
+ this._offset = {
+ x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, //
+ y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad,
+ };
this._resizeUndo = UndoManager.StartBatch('drag resizing');
this._snapPt = { x: e.pageX, y: e.pageY };
SelectionManager.Views.forEach(docView => docView.CollectionFreeFormView?.dragStarting(false, false));
@@ -622,11 +612,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
get selectionTitle(): string {
if (SelectionManager.Views.length === 1) {
const selected = SelectionManager.Views[0];
- if (this._titleControlString.startsWith('=')) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })?.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
- }
- if (this._titleControlString.startsWith('#')) {
- return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+ if (this._titleControlString.startsWith('$')) {
+ return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
}
return this._accumulatedTitle;
}
@@ -638,7 +625,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
if (lastView) {
const invXf = lastView.screenToContentsTransform().inverse();
const seldoc = lastView.layoutDoc;
- const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
+ const loccenter = Utils.rotPt(NumCast(seldoc._rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc._rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
}
return this._rotCenter;
@@ -731,21 +718,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
) : null;
const titleArea = this._editingTitle ? (
- <input
- ref={this._keyinput}
- className="documentDecorations-title"
- type="text"
- name="dynbox"
- autoComplete="on"
- value={hideTitle ? '' : this._accumulatedTitle}
- onBlur={action((e: React.FocusEvent) => {
- this._editingTitle = false;
- !hideTitle && this.titleBlur();
- })}
- onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
- onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
- onPointerDown={e => e.stopPropagation()}
- />
+ <>
+ {r - x < 150 ? null : <span>{this._titleControlString + ':'}</span>}
+ <input
+ ref={this._keyinput}
+ className="documentDecorations-title"
+ type="text"
+ name="dynbox"
+ autoComplete="on"
+ value={hideTitle ? '' : this._accumulatedTitle}
+ onBlur={action((e: React.FocusEvent) => {
+ this._editingTitle = false;
+ this.titleBlur();
+ })}
+ onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+ onPointerDown={e => e.stopPropagation()}
+ />
+ </>
) : (
<div className="documentDecorations-title" key="title">
{hideTitle ? null : (
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 4508d00a7..85e893e19 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -121,6 +121,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+ 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();
@@ -232,7 +233,6 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
onPointerDown: this.stopPropagation,
onClick: this.stopPropagation,
onPointerUp: this.stopPropagation,
- onKeyPress: this.stopPropagation,
value: this._props.autosuggestProps.value,
// @ts-ignore
onChange: this._props.autosuggestProps.onChange,
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 5638d34c6..6a5c2cb4c 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
.forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
- console.log(options);
return (
<Select
styles={{
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 667d8dbb0..2f64ea28c 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -13,18 +13,18 @@ import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
import { MainView } from './MainView';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { OpenWhereMod } from './nodes/DocumentView';
-import { AnchorMenu } from './pdf/AnchorMenu';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { AnchorMenu } from './pdf/AnchorMenu';
const modifiers = ['control', 'meta', 'shift', 'alt'];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 31b13d2c8..edc6b404b 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { ControlPoint, InkData, PointData } from '../../fields/InkField';
@@ -13,6 +13,7 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { SnappingManager } from '../util/SnappingManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
export interface InkControlProps {
inkDoc: Doc;
@@ -24,10 +25,15 @@ export interface InkControlProps {
}
@observer
-export class InkControlPtHandles extends React.Component<InkControlProps> {
+export class InkControlPtHandles extends ObservableReactComponent<InkControlProps> {
@observable private _overControl = -1;
get docView() {
- return this.props.inkView.DocumentView?.();
+ return this._props.inkView.DocumentView?.();
+ }
+
+ constructor(props: InkControlProps) {
+ super(props);
+ makeObservable(this);
}
componentDidMount() {
@@ -42,51 +48,51 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
*/
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
- const ptFromScreen = this.props.inkView.ptFromScreen;
+ const ptFromScreen = this._props.inkView.ptFromScreen;
if (ptFromScreen) {
const order = controlIndex % 4;
- const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
- const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
- const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
+ const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
+ const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this._props.inkCtrlPoints.length;
+ const brokenIndices = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'));
const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1;
- const origInk = this.props.inkCtrlPoints.slice();
+ const origInk = this._props.inkCtrlPoints.slice();
setupMoveUpEvents(
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
return false;
}),
action(() => {
- if (this.props.inkView.controlUndo && this.docView) {
+ if (this._props.inkView.controlUndo && this.docView) {
InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
- const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+ const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
if (doubleTap || e.button === 2) {
if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
if (brokenIndices) brokenIndices.push(controlIndex);
- else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
+ else this._props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
} else {
if (brokenIndices?.includes(equivIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
}
if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
}
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
}
this.changeCurrPoint(controlIndex);
}),
@@ -126,14 +132,14 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
render() {
// Accessing the current ink's data and extracting all control points.
- const scrData = this.props.screenCtrlPoints;
+ const scrData = this._props.screenCtrlPoints;
const sreenCtrlPoints: ControlPoint[] = [];
for (let i = 0; i <= scrData.length - 4; i += 4) {
sreenCtrlPoints.push({ ...scrData[i], I: i });
sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
}
- const inkData = this.props.inkCtrlPoints;
+ const inkData = this._props.inkCtrlPoints;
const inkCtrlPts: ControlPoint[] = [];
for (let i = 0; i <= inkData.length - 4; i += 4) {
inkCtrlPts.push({ ...inkData[i], I: i });
@@ -141,23 +147,23 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
}
const closed = InkingStroke.IsClosed(inkData);
- const nearestScreenPt = this.props.nearestScreenPt();
+ const nearestScreenPt = this._props.nearestScreenPt();
const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle');
const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => {
- const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
+ const broken = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
return (
<Tag
key={control.I.toString() + scale}
- x={control.X - this.props.screenSpaceLineWidth * 2 * scale}
- y={control.Y - this.props.screenSpaceLineWidth * 2 * scale}
+ x={control.X - this._props.screenSpaceLineWidth * 2 * scale}
+ y={control.Y - this._props.screenSpaceLineWidth * 2 * scale}
cx={control.X}
cy={control.Y}
- r={this.props.screenSpaceLineWidth * 2 * scale}
- opacity={this.props.inkView.controlUndo ? 0.35 : 1}
- height={this.props.screenSpaceLineWidth * 4 * scale}
- width={this.props.screenSpaceLineWidth * 4 * scale}
- strokeWidth={this.props.screenSpaceLineWidth / 2}
+ r={this._props.screenSpaceLineWidth * 2 * scale}
+ opacity={this._props.inkView.controlUndo ? 0.35 : 1}
+ height={this._props.screenSpaceLineWidth * 4 * scale}
+ width={this._props.screenSpaceLineWidth * 4 * scale}
+ strokeWidth={this._props.screenSpaceLineWidth / 2}
stroke={Colors.MEDIUM_BLUE}
fill={broken ? Colors.MEDIUM_BLUE : color}
onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)}
@@ -170,7 +176,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
};
return (
<svg>
- {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this.props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
+ {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
{sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
</svg>
);
@@ -185,10 +191,15 @@ export interface InkEndProps {
endPt: () => PointData;
}
@observer
-export class InkEndPtHandles extends React.Component<InkEndProps> {
+export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
@observable _overStart: boolean = false;
@observable _overEnd: boolean = false;
+ constructor(props: InkEndProps) {
+ super(props);
+ makeObservable(this);
+ }
+
_throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter
@action
dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
@@ -198,7 +209,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
e,
action(e => {
if (this._throttle++ % 2 !== 0) return false;
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
const p1 = pt1();
const p2 = pt2();
@@ -216,8 +227,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
}),
action(() => {
SnappingManager.SetIsDragging(false);
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
}),
returnFalse
@@ -228,9 +239,9 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => (
<circle
key={key}
- cx={pt.X}
- cy={pt.Y}
- r={this.props.screenSpaceLineWidth * 2}
+ cx={pt?.X}
+ cy={pt?.Y}
+ r={this._props.screenSpaceLineWidth * 2}
fill={this._overStart ? '#aaaaaa' : '#99999977'}
stroke={'#00007777'}
strokeWidth={0}
@@ -242,8 +253,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
);
return (
<svg>
- {hdl('start', this.props.startPt(), e => this.dragRotate(e, this.props.startPt, this.props.endPt))}
- {hdl('end', this.props.endPt(), e => this.dragRotate(e, this.props.endPt, this.props.startPt))}
+ {hdl('start', this._props.startPt(), e => this.dragRotate(e, this._props.startPt, this._props.endPt))}
+ {hdl('end', this._props.endPt(), e => this.dragRotate(e, this._props.endPt, this._props.startPt))}
</svg>
);
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 122e5c4c3..51baaa23e 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,7 +34,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
-import { ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
import { Colors } from './global/globalEnums';
import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
@@ -44,9 +44,9 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { PinProps, PresBox } from './nodes/trails';
import { StyleProp } from './StyleProvider';
-const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@observer
-export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() implements ViewBoxInterface {
+export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(InkingStroke, fieldStr);
@@ -87,7 +87,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() impleme
});
if (anchor) {
anchor.backgroundColor = 'transparent';
- // /* addAsAnnotation &&*/ this.addDocument(anchor);
+ addAsAnnotation && this.addDocument(anchor);
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.Document);
return anchor;
}
@@ -217,7 +217,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() impleme
* factor for converting between ink and screen space.
*/
inkScaledData = () => {
- const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, 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;
@@ -446,7 +446,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() impleme
{clickableLine(this.onPointerDown, isInkMask)}
{isInkMask ? null : inkLine}
</svg>
- {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+ {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
<div
className="inkStroke-text"
style={{
@@ -464,7 +464,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() impleme
yPadding={10}
xPadding={10}
fieldKey="text"
- dontRegisterView={true}
+ //dontRegisterView={true}
noSidebar={true}
dontScale={true}
isContentActive={this._props.isContentActive}
@@ -488,6 +488,9 @@ export function SetActiveInkColor(value: string) {
export function SetActiveIsInkMask(value: boolean) {
ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
}
+export function SetActiveInkHideTextLabels(value: boolean) {
+ ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
export function SetActiveFillColor(value: string) {
ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
}
@@ -515,6 +518,9 @@ export function ActiveFillColor(): string {
export function ActiveIsInkMask(): boolean {
return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
}
+export function ActiveInkHideTextLabels(): boolean {
+ return BoolCast(ActiveInkPen().activeInkHideTextLabels, false);
+}
export function ActiveArrowStart(): string {
return StrCast(ActiveInkPen()?.activeArrowStart, '');
}
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 9a9b8a437..6da5c0338 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -67,6 +67,7 @@
z-index: 1000;
.lightboxView-contents {
position: absolute;
+ color: black;
}
.lightboxView-navBtn-frame {
position: absolute;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index c7ff7ce47..ef4b5b4ca 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -4,25 +4,26 @@ import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
import { InkTool } from '../../fields/InkField';
import { Cast, NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, Utils } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
+import { GestureOverlay } from './GestureOverlay';
+import './LightboxView.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
import { TabDocView } from './collections/TabDocView';
-import { GestureOverlay } from './GestureOverlay';
-import './LightboxView.scss';
import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
-import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
-import { ObservableReactComponent } from './ObservableReactComponent';
-import { SnappingManager } from '../util/SnappingManager';
interface LightboxViewProps {
PanelWidth: number;
@@ -34,7 +35,12 @@ const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_s
type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore
@observer
export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
- public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView&& (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore
+ /**
+ * Determines whether a DocumentView is descendant of the lightbox view
+ * @param view
+ * @returns true if a DocumentView is descendant of the lightbox view
+ */
+ public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView && (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore
public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
static Instance: LightboxView;
private _path: {
@@ -243,10 +249,10 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
height: this.lightboxHeight(),
clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
}}>
<GestureOverlay isActive={true}>
<DocumentView
+ key={this._doc.title + this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
Document={this._doc}
PanelWidth={this.lightboxWidth}
@@ -257,6 +263,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
styleProvider={DefaultStyleProvider}
ScreenToLocalTransform={this.lightboxScreenToLocal}
renderDepth={0}
+ suppressSetHeight={this._doc._layout_fitWidth ? true : false}
containerViewPath={returnEmptyDoclist}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 28a0f7750..e204759ab 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -7,12 +7,6 @@ body {
overscroll-behavior-x: none;
}
-h1,
-.h1 {
- // reverts change to h1 made by normalize.css
- font-size: 36px;
-}
-
.dash-tooltip {
font-size: 11px;
padding: 2px;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 677114e2d..2fb588e36 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,6 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import 'normalize.css';
import * as React from 'react';
import '../../../node_modules/browndash-components/dist/styles/global.min.css';
import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
@@ -58,7 +57,7 @@ import { AudioBox } from './nodes/AudioBox';
import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
-import { ImageBox } from './nodes/ImageBox';
+import { ImageBox, ImageEditorData as ImageEditor } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
@@ -72,7 +71,7 @@ import { PresBox } from './nodes/trails';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { TopBar } from './topbar/TopBar';
-const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -977,6 +976,7 @@ export class MainView extends ObservableReactComponent<{}> {
);
}
+ lightboxMaxBorder = [200, 50];
render() {
return (
<div
@@ -1039,11 +1039,11 @@ export class MainView extends ObservableReactComponent<{}> {
<RichTextMenu />
{/* <InkTranscription /> */}
{this.snapLines}
- <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
<OverlayView />
<GPTPopup key="gptpopup" />
<SchemaCSVPopUp key="schemacsvpopup" />
- <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
+ <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
{/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
</div>
);
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index bd6be2519..9fa20f642 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps {
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
- finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
+ finishMarquee: (x?: number, y?: number) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
highlightDragSrcColor?: string;
@@ -86,9 +86,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
return marqueeAnno;
}
- const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
+ const textRegionAnno = Docs.Create.ConfigDocument({
annotationOn: this.props.Document,
text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
+ text_html: this.props.selectionText() as any,
backgroundColor: 'transparent',
presentation_duration: 2100,
presentation_transition: 500,
@@ -168,7 +169,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
@action
public onInitiateSelection(down: number[]) {
- console.log('DOWN = ' + down[0] + ' ' + down[1]);
this._width = this._height = 0;
this._start = this.getTransformedScreenPt(down);
@@ -241,6 +241,13 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
}
@action
+ onMove = (pt: number[]) => {
+ const movLoc = this.getTransformedScreenPt(pt);
+ this._width = movLoc.x - this._start.x;
+ this._height = movLoc.y - this._start.y;
+ };
+
+ @action
onSelectMove = (e: PointerEvent) => {
const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
this._width = movLoc.x - this._start.x;
@@ -248,9 +255,12 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
//e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged. if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
};
- @action
onSelectEnd = (e: PointerEvent) => {
e.stopPropagation();
+ this.onEnd(e.clientX, e.clientY);
+ };
+ @action
+ onEnd = (x: number, y: number) => {
const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
if (!this.isEmpty && marqueeStyle) {
@@ -266,9 +276,9 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
copy.style.height = parseInt(marqueeStyle.height.toString().replace('px', '')) / scale + 'px';
(copy as any).marqueeing = true;
MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this.top) || 0);
- AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+ AnchorMenu.Instance.jumpTo(x, y);
}
- this.props.finishMarquee(this.isEmpty ? e.clientX : undefined, this.isEmpty ? e.clientY : undefined, e);
+ this.props.finishMarquee(this.isEmpty ? x : undefined, this.isEmpty ? y : undefined);
this._width = this._height = 0;
};
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 9b2b00903..394d1eae2 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,8 +14,7 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
makeObservable(this);
}
componentDidUpdate(prevProps: Readonly<T>): void {
- Object.keys(prevProps).forEach(action(pkey =>
- (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+ Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
}
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index a94c18295..4b7771f27 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -18,7 +18,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
return PreviewCursor._instance;
}
- _onKeyPress?: (e: KeyboardEvent) => void;
+ _onKeyDown?: (e: KeyboardEvent) => void;
_getTransform?: () => Transform;
_addDocument?: (doc: Doc | Doc[]) => boolean;
_addLiveTextDoc?: (doc: Doc) => void;
@@ -32,7 +32,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
makeObservable(this);
PreviewCursor._instance = this;
this._clickPoint = observable([0, 0]);
- document.addEventListener('keydown', this.onKeyPress);
+ document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('paste', this.paste, true);
}
@@ -120,7 +120,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
};
@action
- onKeyPress = (e: KeyboardEvent) => {
+ onKeyDown = (e: KeyboardEvent) => {
// Mixing events between React and Native is finicky.
//if not these keys, make a textbox if preview cursor is active!
if (
@@ -146,7 +146,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
) {
if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
- this.Visible && this._onKeyPress?.(e);
+ this.Visible && this._onKeyDown?.(e);
((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
}
} else if (this.Visible) {
@@ -171,7 +171,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
public static Show(
x: number,
y: number,
- onKeyPress: (e: KeyboardEvent) => void,
+ onKeyDown: (e: KeyboardEvent) => void,
addLiveText: (doc: Doc) => void,
getTransform: () => Transform,
addDocument: undefined | ((doc: Doc | Doc[]) => boolean),
@@ -181,7 +181,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
const self = PreviewCursor.Instance;
if (self) {
self._clickPoint = [x, y];
- self._onKeyPress = onKeyPress;
+ self._onKeyDown = onKeyDown;
self._addLiveTextDoc = addLiveText;
self._getTransform = getTransform;
self._addDocument = addDocument || returnFalse;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 02f288a68..517a80d63 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -87,7 +87,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
containerDoc._isLightbox = !containerDoc._isLightbox;
//containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
- //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
});
}
@@ -219,7 +219,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// containerDoc._isLightbox = !containerDoc._isLightbox;
// //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
// const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- // //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ // //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
// containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
// });
// }
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 8581bdf73..476b46905 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -227,14 +227,15 @@
font-weight: bold;
width: 95px;
overflow-x: hidden;
- display: inline-block;
text-overflow: ellipsis;
white-space: nowrap;
+ display: flex;
+ align-items: center;
}
.propertiesView-sharingTable-item-permission {
display: flex;
- align-items: flex-end;
+ align-items: center;
text-align: right;
margin-left: auto;
margin-right: -12px;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index cbd3ff358..ae29382c1 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -10,7 +10,7 @@ import * as React from 'react';
import { ColorResult, SketchPicker } from 'react-color';
import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
-import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
+import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
@@ -41,7 +41,6 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { StyleProviderFuncType } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
-import { LinkBox } from './nodes/LinkBox';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -71,7 +70,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get selectedLink() {
- return this.selectedDocumentView?.ComponentView instanceof LinkBox ? this.selectedDocumentView.Document : LinkManager.Instance.currentLink;
+ return LinkManager.Instance.currentLink;
}
@computed get selectedLayoutDoc() {
@@ -715,7 +714,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
value={value}
style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
onChange={e => setter(e.target.value)}
- onKeyPress={e => e.stopPropagation()}
+ onKeyDown={e => e.stopPropagation()}
/>
<div className="inputBox-button">
<div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
@@ -1399,7 +1398,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
const targZoom = this.sourceAnchor?.followLinkZoom;
const indent = 30;
- const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
+ const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
return (
<>
@@ -1615,7 +1614,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
render() {
const isNovice = Doc.noviceMode;
- const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
if (!this.selectedDoc && !this.isPres) {
return (
<div className="propertiesView" style={{ width: this._props.width }}>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1e9272e93..3ad3c92da 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -94,7 +94,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
target[DocData][key] = val;
return {
type: 'dashField',
- attrs: { fieldKey: key, docId: '', hideKey: false, editable: true },
+ attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
};
});
@@ -121,7 +121,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
{ type: 'pFontSize', attrs: { fontSize: '8px' } },
{ type: 'em' },
],
- attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, editable: false },
+ attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, hideValue: false, editable: false },
},
],
},
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 30a026dbc..ce00f6101 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -3,6 +3,7 @@
.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;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1adb0d9e5..dcec2fe3d 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -18,6 +18,7 @@ import { LinkManager } from '../util/LinkManager';
import { SettingsManager } from '../util/SettingsManager';
import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from '../util/UndoManager';
+import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
import { DocumentViewProps } from './nodes/DocumentView';
@@ -88,7 +89,8 @@ 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 isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
+ const layoutDoc = doc ? Doc.Layout(doc) : doc;
+ const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
const isOpen = property.includes(':open');
const isEmpty = property.includes(':empty');
const boxBackground = property.includes(':box');
@@ -128,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
highlightStyle: doc.isGroup ? "dotted": highlightStyle,
highlightColor,
highlightIndex,
- highlightStroke: doc.layout_isSvg,
+ highlightStroke: layoutDoc?.layout_isSvg,
};
}
}
@@ -141,10 +143,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
- case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
+ case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
return (
(doc &&
+ !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
!props?.LayoutTemplateString &&
!doc.presentation_targetDoc &&
!props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
@@ -169,7 +172,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const backColor = backgroundCol();
return backColor ? lightOrDark(backColor) : undefined;
case StyleProp.BorderRounding:
- return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
+ const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
+ return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
case StyleProp.BorderPath:
const borderPath = Doc.IsComicStyle(doc) &&
props?.renderDepth &&
@@ -196,9 +200,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
: 0;
case StyleProp.BackgroundColor: {
if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
- let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+ const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
+ const usePath = StrCast(doc?.[dataKey + "_usePath"]);
+ const alternate = usePath.includes(":hover") ? ( doc?.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)
// prettier-ignore
- switch (doc?.type) {
+ switch (layoutDoc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
case DocumentType.PRES: docColor = docColor || 'transparent'; break;
case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
@@ -215,13 +223,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case DocumentType.SCREENSHOT:
case DocumentType.VID: docColor = docColor || (Colors.LIGHT_GRAY); break;
case DocumentType.COL:
- docColor = docColor || (Doc.IsSystem(doc)
+ docColor = docColor || (doc && Doc.IsSystem(doc)
? SettingsManager.userBackgroundColor
- : doc.annotationOn
+ : doc?.annotationOn
? '#00000010' // faint interior for collections on PDFs, images, etc
: doc?.isGroup
? undefined
- : doc._type_collection === CollectionViewType.Stacking ?
+ : doc?._type_collection === CollectionViewType.Stacking ?
(Colors.DARK_GRAY)
: Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
break;
@@ -236,7 +244,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case StyleProp.BoxShadow: {
if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
- if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !doc.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+ if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
switch (doc?.type) {
case DocumentType.COL:
return StrCast(
@@ -261,7 +269,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')}`
- : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline)
+ : DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline)
? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
: StrCast(doc.layout_boxShadow, '');
}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e7269df98..5df0bea1a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -99,7 +99,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
.forEach(template => templateMenu.push(<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
return (
<ul className="template-list">
- {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
+ {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyDown={this.onCustomKeypress} />}
{templateMenu}
<CollectionTreeView
Document={Doc.MyTemplates}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 7e484f3df..4e4bd43bf 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -14,7 +14,8 @@ import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FieldView';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
-const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
+
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
@computed get scrollSpeed() {
@@ -38,6 +39,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
}
};
+ @computed get carouselItems() {
+ return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
+ }
+
centerScale = Number(CAROUSEL3D_CENTER_SCALE);
panelWidth = () => this._props.PanelWidth() / 3;
panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
@@ -85,7 +90,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
);
};
- return this.childLayoutPairs.map((childPair, index) => {
+ return this.carouselItems.map((childPair, index) => {
return (
<div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
{displayDoc(childPair)}
@@ -96,7 +101,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
changeSlide = (direction: number) => {
SelectionManager.DeselectAll();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length;
};
onArrowClick = (direction: number) => {
@@ -154,7 +159,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
}
@computed get dots() {
- return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+ return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
}
@computed get translateX() {
const index = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index dae16bafb..9c370bfbb 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -12,6 +12,7 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView } from './CollectionSubView';
+import { DocumentType } from '../../documents/DocumentTypes';
@observer
export class CollectionCarouselView extends CollectionSubView() {
@@ -33,13 +34,17 @@ export class CollectionCarouselView extends CollectionSubView() {
}
};
+ @computed get carouselItems() {
+ return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
+ }
+
advance = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.carouselItems.length;
};
goback = (e: React.MouseEvent) => {
e.stopPropagation();
- this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.carouselItems.length) % this.carouselItems.length;
};
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
// first look for properties on the document in the carousel, then fallback to properties on the container
@@ -55,7 +60,7 @@ export class CollectionCarouselView extends CollectionSubView() {
captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
@computed get content() {
const index = NumCast(this.layoutDoc._carousel_index);
- const curDoc = this.childLayoutPairs?.[index];
+ const curDoc = this.carouselItems?.[index];
const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
return !(curDoc?.layout instanceof Doc) ? null : (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8f1633122..b2897a9b7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -3,14 +3,14 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import * as GoldenLayout from '../../../client/goldenLayout';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
+import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
@@ -32,6 +32,7 @@ import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import { TabDocView } from './TabDocView';
+import { ComputedField } from '../../../fields/ScriptField';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() {
}
};
+ /**
+ * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs
+ * Once published, any text that uses the 'title' in its body will automatically
+ * be linked to this published document.
+ * @param target
+ * @param title
+ */
+ titleChanged = (target: any, value: any) => {
+ const title = Field.toString(value);
+ if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+ const embedding = DocListCast(target.proto_embeddings).lastElement();
+ embedding && Doc.AddToMyPublished(embedding);
+ } else if (!title.startsWith('@')) {
+ DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc));
+ }
+ };
+
componentDidMount: () => void = async () => {
this._unmounting = false;
+ SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
if (this._containerRef.current) {
this._lightboxReactionDisposer = reaction(
() => LightboxView.LightboxDoc,
@@ -468,10 +487,15 @@ export class CollectionDockingView extends CollectionSubView() {
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
});
- const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
- DashboardView.SetupDashboardTrails(copy);
- DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed?
- return DashboardView.openDashboard(copy);
+ 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>();
+
+ DashboardView.SetupDashboardTrails(dashboardDoc);
+ DashboardView.SetupDashboardCalendars(dashboardDoc); // Zaul TODO: needed?
+ return DashboardView.openDashboard(dashboardDoc);
}
@action
@@ -512,13 +536,13 @@ export class CollectionDockingView extends CollectionSubView() {
stack.header?.element.on('mousedown', (e: any) => {
const dashboard = Doc.ActiveDashboard;
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
- dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 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.pane_count)}`,
});
Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.Document, docToAdd, false);
@@ -530,13 +554,13 @@ export class CollectionDockingView extends CollectionSubView() {
action(() => {
const dashboard = Doc.ActiveDashboard;
if (dashboard) {
- dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 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.pane_count)}`,
});
Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
inheritParentAcls(this.dataDoc, docToAdd, false);
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8729ef549..4f25f69ef 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,15 +1,16 @@
-import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
@@ -19,11 +20,10 @@ import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu } from '../AntimodeMenu';
import { EditableView } from '../EditableView';
import { MainView } from '../MainView';
-import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionLinearView } from './collectionLinear';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import './CollectionMenu.scss';
-import { DocData } from '../../../fields/DocSymbols';
+import { CollectionLinearView } from './collectionLinear';
interface CollectionMenuProps {
panelHeight: () => number;
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 4c2dcf9ab..95fda7b0a 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -1,7 +1,7 @@
@import '../global/globalCssVariables.module.scss';
.collectionNoteTakingView-DocumentButtons {
- display: none;
+ opacity: 0;
justify-content: space-between;
margin: auto;
}
@@ -15,7 +15,6 @@
.editableView-container-editing-oneLine,
.editableView-container-editing {
- color: grey;
padding: 10px;
width: 100%;
}
@@ -29,7 +28,6 @@
.editableView-input {
outline-color: black;
letter-spacing: 2px;
- color: grey;
border: 0px;
padding: 12px 10px 11px 10px;
}
@@ -41,7 +39,6 @@
.editableView-input {
outline-color: black;
letter-spacing: 2px;
- color: grey;
border: 0px;
padding: 12px 10px 11px 10px;
}
@@ -51,9 +48,9 @@
display: flex;
}
-.collectionNoteTakingViewFieldColumn:hover {
+.collectionNoteTakingViewFieldColumnHover:hover {
.collectionNoteTakingView-DocumentButtons {
- display: flex;
+ opacity: 1;
}
}
@@ -100,6 +97,9 @@
flex-direction: column;
height: max-content;
}
+ .collectionNoteTakingViewFieldColumnHover {
+ min-height: 100%; // if we use this, then we can't have autoHeight
+ }
.collectionSchemaView-previewDoc {
height: 100%;
@@ -250,7 +250,6 @@
overflow: visible;
width: 100%;
display: flex;
- color: gray;
align-items: center;
}
}
@@ -262,10 +261,6 @@
background: $medium-gray;
// overflow: hidden; overflow is visible so the color menu isn't hidden -ftong
- .editableView-input {
- color: $dark-gray;
- }
-
.editableView-input:hover,
.editableView-container-editing:hover,
.editableView-container-editing-oneLine:hover {
@@ -288,7 +283,6 @@
.editableView-container-editing-oneLine,
.editableView-container-editing {
- color: grey;
padding: 10px;
}
@@ -301,7 +295,6 @@
.editableView-input {
padding: 12px 10px 11px 10px;
border: 0px;
- color: grey;
text-align: center;
letter-spacing: 2px;
outline-color: black;
@@ -409,7 +402,6 @@
.editableView-container-editing-oneLine,
.editableView-container-editing {
- color: grey;
padding: 10px;
width: 100%;
}
@@ -423,13 +415,16 @@
.editableView-input {
outline-color: black;
letter-spacing: 2px;
- color: grey;
border: 0px;
padding: 12px 10px 11px 10px;
+ &::placeholder {
+ color: black;
+ }
}
}
.collectionNoteTakingView-addDocumentButton {
+ opacity: 0.5;
font-size: 75%;
letter-spacing: 2px;
cursor: pointer;
@@ -437,10 +432,12 @@
.editableView-input {
outline-color: black;
letter-spacing: 2px;
- color: grey;
border: 0px;
padding: 12px 10px 11px 10px;
}
+ &:hover {
+ opacity: unset;
+ }
}
.collectionNoteTakingView-addGroupButton {
@@ -497,6 +494,24 @@
.rc-switch-checked .rc-switch-inner {
left: 8px;
}
+
+ .collectionNoteTaking-pivotField {
+ display: none;
+ }
+ &:hover {
+ .collectionNoteTaking-pivotField {
+ display: unset;
+ }
+ }
+}
+
+.collectionNoteTakingViewLight {
+ .collectionNoteTakingView-addDocumentButton,
+ .collectionNoteTakingView-addGroupButton {
+ .editableView-input::placeholder {
+ color: white;
+ }
+ }
}
@media only screen and (max-device-width: 480px) {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 6318620e0..d8a0aebb1 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, Opt } from '../../../fields/Doc';
@@ -9,24 +9,25 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable, undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import './CollectionNoteTakingView.scss';
import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
import { CollectionSubView } from './CollectionSubView';
-import { JsxElement } from 'typescript';
+import { FieldsDropdown } from '../FieldsDropdown';
const _global = (window /* browser */ || global) /* node */ as any;
/**
@@ -41,7 +42,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
_disposers: { [key: string]: IReactionDisposer } = {};
_masonryGridRef: HTMLDivElement | null = null;
_draggerRef = React.createRef<HTMLDivElement>();
- notetakingCategoryField = 'NotetakingCategory';
+ @computed get notetakingCategoryField() {
+ return StrCast(this.dataDoc.notetaking_column, StrCast(this.layoutDoc.pivotField, 'notetaking_column'));
+ }
public DividerWidth = 16;
@observable docsDraggedRowCol: number[] = [];
@observable _scroll = 0;
@@ -153,25 +156,34 @@ export class CollectionNoteTakingView extends CollectionSubView() {
);
};
+ @computed get allFieldValues() {
+ return new Set(this.childDocs.map(doc => StrCast(doc[this.notetakingCategoryField])));
+ }
+
componentDidMount() {
super.componentDidMount?.();
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
- this._disposers.layout_autoHeight = reaction(
- () => this.layoutDoc._layout_autoHeight,
- layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))
+
+ this._disposers.autoColumns = reaction(
+ () => (this.layoutDoc._notetaking_columns_autoCreate ? Array.from(this.allFieldValues) : undefined),
+ columns => undoable(() => columns?.filter(col => !this.colHeaderData.some(h => h.heading === col)).forEach(col => this.addColumn(col)), 'adding columns')(),
+ { fireImmediately: true }
);
this._disposers.refList = reaction(
() => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
({ refList, autoHeight }) => {
- if (autoHeight) refList.forEach(r => this.observer.observe(r));
- else this.observer.disconnect();
+ if (autoHeight) {
+ refList.forEach(r => this.observer.observe(r));
+ this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)));
+ } else this.observer.disconnect();
},
{ fireImmediately: true }
);
}
componentWillUnmount() {
+ this.observer.disconnect();
document.removeEventListener('pointerup', this.removeDocDragHighlight, true);
super.componentWillUnmount();
Object.keys(this._disposers).forEach(key => this._disposers[key]());
@@ -298,7 +310,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
getDocWidth(d: Doc) {
const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
- const existingWidth = existingHeader?.width ? existingHeader.width : 0;
+ const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0;
const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
const width = d.layout_fitWidth ? maxWidth : NumCast(d._width);
return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
@@ -370,7 +382,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// we alter the pivot fields of the docs in case they are moved to a new column.
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
- DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader));
+ DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader));
// used to notify sections to re-render
this.docsDraggedRowCol.length = 0;
const columnFromCoord = this.getColumnFromXCoord(xCoord);
@@ -500,11 +512,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
editableViewProps = () => ({
GetValue: () => '',
- SetValue: this.addGroup,
- contents: '+ New Column',
+ SetValue: this.addColumn,
+ contents: '+ Column',
});
refList = () => this._refList;
+ backgroundColor = () => this._props.DocumentView?.().backgroundColor();
// sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column)
sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => (
@@ -512,7 +525,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
key={heading?.heading ?? 'unset'}
PanelWidth={this._props.PanelWidth}
refList={this._refList}
+ backgroundColor={this.backgroundColor}
select={this._props.select}
+ isContentActive={this.isContentActive}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
@@ -538,18 +553,26 @@ export class CollectionNoteTakingView extends CollectionSubView() {
/>
);
+ @undoBatch
+ remColumn = (value: SchemaHeaderField) => {
+ const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+ if (value) {
+ const index = colHdrData.indexOf(value);
+ index !== -1 && colHdrData.splice(index, 1);
+ this.resizeColumns(colHdrData);
+ }
+ };
+
// addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of
// columnHeaders and resizing the existing columns to make room for our new one.
@undoBatch
- addGroup = (value: string) => {
- if (this.colHeaderData) {
- for (const header of this.colHeaderData) {
- if (header.heading === value) {
- alert('You cannot use an existing column name. Please try a new column name');
- return value;
- }
+ addColumn = (value: string) => {
+ this.colHeaderData.forEach(header => {
+ if (header.heading === value) {
+ alert('You cannot use an existing column name. Please try a new column name');
+ return value;
}
- }
+ });
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));
@@ -557,11 +580,25 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return true;
};
+ removeEmptyColumns = undoable(() => {
+ this.colHeaderData.filter(h => !this.allFieldValues.has(h.heading)).forEach(this.remColumn);
+ }, 'remove empty Columns');
+
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!e.isPropagationStopped()) {
const subItems: ContextMenuProps[] = [];
- subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+ subItems.push({
+ description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`,
+ event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate),
+ icon: 'computer',
+ });
+ subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' });
+ subItems.push({
+ description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`,
+ event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize),
+ icon: 'plus',
+ });
subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
@@ -614,12 +651,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
TraceMobx();
return (
<div
- className="collectionNoteTakingView"
+ className={`collectionNoteTakingView ${lightOrDark(this.backgroundColor()) === Colors.WHITE ? 'collectionNoteTakingViewLight' : ''}`}
ref={this.createRef}
- key="notes"
style={{
- overflowY: this._props.isContentActive() ? 'auto' : 'hidden',
- background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
+ overflowY: this.isContentActive() ? 'auto' : 'hidden',
+ background: this.backgroundColor(),
pointerEvents: this.backgroundEvents,
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
@@ -630,6 +666,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
onContextMenu={this.onContextMenu}
onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
<>{this.renderedSections}</>
+ <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
+ <FieldsDropdown
+ Document={this.Document}
+ selectFunc={undoable(fieldKey => {
+ this.layoutDoc._pivotField = fieldKey;
+ this.removeEmptyColumns();
+ }, 'change pivot field')}
+ placeholder={StrCast(this.layoutDoc._pivotField)}
+ />
+ </div>
</div>
);
}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index db178d500..448b11b05 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,10 +1,9 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnEmptyString } from '../../../Utils';
+import { lightOrDark, returnEmptyString } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -26,6 +25,7 @@ import './CollectionNoteTakingView.scss';
interface CSVFieldColumnProps {
Document: Doc;
TemplateDataDocument: Opt<Doc>;
+ backgroundColor?: (() => string) | undefined;
docList: Doc[];
heading: string;
pivotField: string;
@@ -38,6 +38,7 @@ interface CSVFieldColumnProps {
gridGap: number;
headings: () => object[];
select: (ctrlPressed: boolean) => void;
+ isContentActive: () => boolean | undefined;
renderChildren: (docs: Doc[]) => JSX.Element[];
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
@@ -57,10 +58,16 @@ interface CSVFieldColumnProps {
*/
@observer
export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSVFieldColumnProps> {
- @observable private _background = 'inherit';
+ @observable private _hover = false;
+
+ constructor(props: CSVFieldColumnProps) {
+ super(props);
+ makeObservable(this);
+ }
// 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.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 + '%';
@@ -122,8 +129,8 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
return false;
};
- @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
- @action pointerLeave = () => (this._background = 'inherit');
+ @action pointerEntered = () => (this._hover = true);
+ @action pointerLeave = () => (this._hover = false);
@undoBatch
addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@@ -147,7 +154,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
deleteColumn = () => {
const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
if (this._props.headingObject) {
- this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+ // this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined));
colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
this._props.resizeColumns(colHdrData);
}
@@ -273,11 +280,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
</div>
{!this._props.chromeHidden ? (
- <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
- <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
+ <div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}>
+ <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} />
</div>
- <div key={`${this._props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
+ <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
<EditableView {...this._props.editableViewProps()} />
</div>
</div>
@@ -292,17 +299,17 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
TraceMobx();
return (
<div
- className="collectionNoteTakingViewFieldColumn"
- key={this._heading}
+ className="collectionNoteTakingViewFieldColumnHover"
+ onPointerEnter={this.pointerEntered}
+ onPointerLeave={this.pointerLeave}
style={{
width: this.columnWidth,
- background: this._background,
+ background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit',
marginLeft: this._props.headings().findIndex((h: any) => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0,
- }}
- ref={this.createColumnDropRef}
- onPointerEnter={this.pointerEntered}
- onPointerLeave={this.pointerLeave}>
- {this.innards}
+ }}>
+ <div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}>
+ {this.innards}
+ </div>
</div>
);
}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 5e4bce19d..50a97b978 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -59,6 +59,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
width: 12,
borderRight: '4px solid #282828',
borderLeft: '4px solid #282828',
+ position: 'fixed',
+ pointerEvents: 'none',
+ }}
+ />
+ <div
+ className="columnResizer-handler"
+ onPointerDown={e => this.registerResizing(e)}
+ style={{
+ height: '95%',
+ width: 12,
+ borderRight: '4px solid #282828',
+ borderLeft: '4px solid #282828',
}}
/>
</div>
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 7d7f0bb61..9d68c621b 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -6,6 +6,7 @@ import { ScriptField } from '../../../fields/ScriptField';
import { NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
+import { dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { OpenWhere } from '../nodes/DocumentView';
@@ -13,7 +14,6 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import './CollectionPileView.scss';
import { CollectionSubView } from './CollectionSubView';
-import { dropActionType } from '../../util/DragManager';
@observer
export class CollectionPileView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 5c47d4b9e..656f850b3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -594,6 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
<StackedTimelineAnchor
{...this._props}
mark={d.anchor}
+ containerViewPath={this._props.containerViewPath}
rangeClickScript={this.rangeClickScript}
rangePlayScript={this.rangePlayScript}
left={left - this._scroll}
@@ -701,6 +702,7 @@ interface StackedTimelineAnchorProps {
layoutDoc: Doc;
isDocumentActive?: () => boolean | undefined;
ScreenToLocalTransform: () => Transform;
+ containerViewPath?: () => DocumentView[];
_timeline: HTMLDivElement | null;
focus: FocusFuncType;
currentTimecode: () => number;
@@ -796,12 +798,15 @@ 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}"]`
+ );
+ };
// context menu
contextMenuItems = () => {
const resetTitle = {
- script: ScriptField.MakeFunction(
- `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
- )!,
+ method: this.resetTitle,
icon: 'folder-plus',
label: 'Reset Title',
};
@@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
TemplateDataDocument={undefined}
- containerViewPath={returnEmptyDoclist}
+ containerViewPath={this._props.containerViewPath}
pointerEvents={this.noEvents ? returnNone : undefined}
styleProvider={this._props.styleProvider}
renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2b23935eb..bf0393883 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -24,7 +24,7 @@ import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
@@ -316,7 +316,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
<DocumentView
ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
Document={doc}
- TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])}
+ TemplateDataDocument={dataDoc}
renderDepth={this._props.renderDepth + 1}
PanelWidth={panelWidth}
PanelHeight={panelHeight}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 641e01b81..c5292f880 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from '../../../fields/util';
import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -66,11 +66,26 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
_ele: HTMLElement | null = null;
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ const sourceDragAction = dragData.dropAction;
+ const sameCollection = !dragData.draggedDocuments.some(d => !this._props.docList.includes(d));
+ 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
+ ? sourceDragAction // keep the doc in place
+ : dropActionType.same; // otherwise use same tree semantics to move within tree
+
+ e.stopPropagation();
+ }
+ };
+
// This is likely similar to what we will be doing. Why do we need to make these refs?
// is that the only way to have drop targets?
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) 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.Document, this.onInternalPreDrop.bind(this));
else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
this._ele = ele;
};
@@ -345,7 +360,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
<div>
<div
key={`${heading}-stack`}
- className={`collectionStackingView-masonrySingle`}
+ className="collectionStackingView-masonrySingle"
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: 'auto',
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 59a695a3d..32198e3a2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,7 +4,7 @@ import * as rp from 'request-promise';
import { Utils, returnFalse } from '../../../Utils';
import CursorField from '../../../fields/CursorField';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate } from '../../../fields/DocSymbols';
+import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -15,13 +15,13 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
import { LoadingBox } from '../nodes/LoadingBox';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { CollectionView, CollectionViewProps } from './CollectionView';
@@ -196,13 +196,16 @@ export function CollectionSubView<X>(moreProps?: X) {
@undoBatch
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
- protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, dropAction: dropActionType) {
- if (de.complete.docDragData) {
- // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
- // so we check if our collection has a dropAction set on it and if so, we use that instead.
- if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) {
- de.complete.docDragData.dropAction = dropAction;
- }
+ protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) {
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ const sourceDragAction = dragData.dropAction;
+ const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._props.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
+ ? sourceDragAction // keep the doc in place
+ : dropActionType.same; // otherwise use same tree semantics to move within tree
e.stopPropagation();
}
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index b92edd165..37452ddfb 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -49,7 +49,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
getAnchor = (addAsAnnotation: boolean) => {
- const anchor = Docs.Create.HTMLMarkerDocument([], {
+ const anchor = Docs.Create.ConfigDocument({
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.Document,
});
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3b37bdcfa..5741fc29b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,6 +12,7 @@ import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFil
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -27,7 +28,6 @@ import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -157,14 +157,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const dragData = de.complete.docDragData;
if (dragData) {
const sourceDragAction = dragData.dropAction;
- const sameTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.Document);
- const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
- dragData.dropAction =
- targetDropAction && !isAlreadyInTree() // if dropped document is not in the tree
- ? targetDropAction // then use the target's drop action if it's specified
- : !sameTree() || sourceDragAction === dropActionType.inPlace // if doc from another tree, or a non inPlace source drag action is specified
- ? sourceDragAction // use the source dragAction
- : dropActionType.same; // otherwise use same tree semantics to move within tree
+ const sameTree = dragData.treeViewDoc?.[DocData] === this.dataDoc;
+ dragData.dropAction = !sameTree // 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
+ ? sourceDragAction // keep the doc in place
+ : dropActionType.same; // otherwise use same tree semantics to move within tree
e.stopPropagation();
}
};
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 699ec3b95..5cb7b149b 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -30,7 +30,7 @@ import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { Colors } from '../global/globalEnums';
import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -465,6 +465,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
addDocument={undefined}
removeDocument={this.remDocTab}
addDocTab={this.addDocTab}
+ suppressSetHeight={this._document._layout_fitWidth ? true : false}
ScreenToLocalTransform={this.ScreenToLocalTransform}
dontCenter={'y'}
whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 1b4349f44..4fd49f8fe 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -19,7 +19,6 @@ import { DocUtils, Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -36,7 +35,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import { TreeSort } from './TreeSort';
import './TreeView.scss';
-const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -450,7 +449,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const addDoc = inside ? this.localAdd : parentAddDoc;
const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd;
if (canAdd && (dropAction !== dropActionType.inPlace || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
- const move = (!dropAction || canEmbed || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && moveDocument;
+ const move =
+ (!dropAction || (canEmbed && dropAction !== dropActionType.copy) || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) &&
+ moveDocument;
this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === dropActionType.proto ? addDoc(d) : false) : addDoc(d)) || added, false);
this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
@@ -929,7 +930,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
case 'Tab':
e.stopPropagation?.();
e.preventDefault?.();
- setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+ setTimeout(() => RichTextMenu.Instance?.TextView?.EditorView?.focus(), 150);
UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab');
return true;
case 'Backspace':
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 08dfb32ad..0acc99360 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -14,8 +14,8 @@ export interface CollectionFreeFormViewBackgroundGridProps {
nativeDimScaling: () => number;
zoomScaling: () => number;
layoutDoc: Doc;
- cachedCenteringShiftX: number;
- cachedCenteringShiftY: number;
+ centeringShiftX: number;
+ centeringShiftY: number;
}
@observer
export class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
@@ -32,7 +32,7 @@ export class CollectionFreeFormBackgroundGrid extends React.Component<Collection
const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const strokeStyle = this.props.color();
- return !this.props.nativeDimScaling() ? null : (
+ return (
<canvas
className="collectionFreeFormView-grid"
width={w}
@@ -41,8 +41,8 @@ export class CollectionFreeFormBackgroundGrid extends React.Component<Collection
ref={el => {
const ctx = el?.getContext('2d');
if (ctx) {
- const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
- const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+ const Cx = this.props.centeringShiftX % renderGridSpace;
+ const Cy = this.props.centeringShiftY % renderGridSpace;
ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
ctx.clearRect(0, 0, w, h);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField';
import { listSpec } from '../../../../fields/Schema';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { aggregateBounds } from '../../../../Utils';
-import * as React from 'react';
export interface ViewDefBounds {
type: string;
@@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
+ backgroundColor: StrCast(layoutDoc.backgroundColor),
pair: { layout: doc },
replica: val.replicas[i],
});
@@ -388,7 +388,7 @@ function normalizeResults(
minWidth: number,
extras: ViewDefBounds[]
): ViewDefResult[] {
- const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
const aggBounds = aggregateBounds(
extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2fb5848e..079a5d977 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+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';
@@ -17,7 +17,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, numberValue, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -76,7 +76,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
- const field = this.layoutDoc[this.fieldKey];
+ const field = this.dataDoc[this.fieldKey];
const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
return !paintFunc
? ''
@@ -169,17 +169,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get nativeHeight() {
return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
- @computed get cachedCenteringShiftX(): number {
- const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ @computed get centeringShiftX(): number {
+ const scaling = this.nativeDimScaling;
return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
- @computed get cachedCenteringShiftY(): number {
+ @computed get centeringShiftY(): number {
+ const panLocAtCenter = !(this._props.isAnnotationOverlay || this._props.originTopLeft);
+ if (!panLocAtCenter) return 0;
const dv = this.DocumentView?.();
- const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
- const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ const aspect = !(this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth) && dv?.nativeWidth && dv?.nativeHeight;
+ const scaling = this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
- const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth();
- return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ const height = aspect ? (dv.nativeHeight / dv.nativeWidth) * this._props.PanelWidth() : this._props.PanelHeight();
+ return height / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get panZoomXf() {
return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
@@ -187,7 +189,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get screenToFreeformContentsXf() {
return this._props
.ScreenToLocalTransform() //
- .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
+ .translate(-this.centeringShiftX, -this.centeringShiftY)
.transform(this.panZoomXf);
}
@@ -250,8 +252,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
- PanZoomCenterXf = () =>
- this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ 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);
isAnyChildContentActive = () => this._props.isAnyChildContentActive();
@@ -314,7 +315,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// options.didMove = true;
// }
}
- if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
+ if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -335,6 +336,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
+ if (doc === this.Document) return res(this.DocumentView?.());
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -564,7 +566,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (doc && this.childDocList?.includes(doc))
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.BackgroundColor:
const cluster = NumCast(doc?.layout_cluster);
if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -589,7 +591,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
trySelectCluster = (addToSel: boolean) => {
- if (this._hitCluster !== -1) {
+ if (addToSel && this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
@@ -647,7 +649,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
x: B.x - inkWidth / 2,
y: B.y - inkWidth / 2,
_width: B.width + inkWidth,
- _height: B.height + inkWidth }, // prettier-ignore
+ _height: B.height + inkWidth,
+ stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
inkWidth
);
if (Doc.ActiveTool === InkTool.Write) {
@@ -1112,9 +1115,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (scale !== undefined) {
const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
const newScale =
- scale === 0
- ? NumCast(this.layoutDoc[this.scaleFieldKey])
- : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height)));
+ scale === 0 ? NumCast(this.layoutDoc[this.scaleFieldKey]) : Math.min(maxZoom, (1 / this.nativeDimScaling) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height)));
return {
panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
@@ -1261,7 +1262,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lightboxDoc = doc;
return true;
}
- if (this.childDocList?.includes(doc)) {
+ if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
if (doc.hidden) doc.hidden = false;
return true;
}
@@ -1286,15 +1287,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const rotation = Cast(_rotation,'number',
!this.layoutDoc._rotation_jitter ? null
: NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
+ const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1404,8 +1406,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection),
+ layout_unrendered: true,
+ presentation_transition: 500,
+ annotationOn: this.Document,
+ });
+ PresBox.pinDocView(
+ anchor,
+ { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } },
+ this.Document
+ );
if (addAsAnnotation) {
if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
@@ -1713,7 +1724,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
});
- SnappingManager.addSnapLines(horizLines, vertLines);
+ this.layoutDoc._freeform_snapLines && SnappingManager.addSnapLines(horizLines, vertLines);
};
incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
@@ -1755,8 +1766,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
isAnnotationOverlay={this.isAnnotationOverlay}
- cachedCenteringShiftX={this.cachedCenteringShiftX}
- cachedCenteringShiftY={this.cachedCenteringShiftY}
+ centeringShiftX={this.centeringShiftX}
+ centeringShiftY={this.centeringShiftY}
/>
</div>
);
@@ -1804,7 +1815,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@computed get nativeDimScaling() {
- if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0;
+ if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1;
const nw = this.nativeWidth;
const nh = this.nativeHeight;
const hscale = nh ? this._props.PanelHeight() / nh : 1;
@@ -1858,11 +1869,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
style={{
pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : (this._props.pointerEvents?.() as any),
textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
- transform: `scale(${this.nativeDimScaling || 1})`,
- width: `${100 / (this.nativeDimScaling || 1)}%`,
- height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
+ transform: `scale(${this.nativeDimScaling})`,
+ width: `${100 / this.nativeDimScaling}%`,
+ height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`,
}}>
- {this.paintFunc ? null : this._lightboxDoc ? (
+ {this.paintFunc ? (
+ <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+ ) : this._lightboxDoc ? (
<div style={{ padding: 15, width: '100%', height: '100%' }}>
<DocumentView
{...this._props}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b913e05ad..6eca91e9d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -101,13 +101,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
@action
- onKeyPress = (e: KeyboardEvent) => {
+ onKeyDown = (e: KeyboardEvent) => {
//make textbox and add it to this collection
// 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://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+ 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)
+ );
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this._props.ungroup) {
@@ -309,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
PreviewCursor.Instance.Doc = doc;
- PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
+ PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
}
this.clearSelection();
}
@@ -497,7 +499,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
_layout_showSidebar: true,
title: 'overview',
});
- const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+ const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 621d3691d..6fb8e40db 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -226,6 +226,15 @@
align-items: center;
}
+.schemaRTFCell {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ min-width: 10px;
+ min-height: 10px;
+}
+
.schema-row {
cursor: grab;
justify-content: flex-end;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 2eda5062b..0244db891 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -28,7 +28,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
-const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
+const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export enum ColumnType {
Number,
@@ -260,7 +260,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
break;
case 'Backspace': {
- this.removeDocument(this._selectedDocs);
+ undoable(() => this.removeDocument(this._selectedDocs), 'delete schema row');
break;
}
case 'Escape': {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index f6f3a24a9..0085f9938 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -53,6 +53,8 @@ export interface SchemaTableCellProps {
options?: string[];
menuTarget: HTMLDivElement | null;
transform: () => Transform;
+ autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click
+ rootSelected?: () => boolean;
}
@observer
@@ -91,16 +93,18 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
select: emptyFunction,
dragAction: dropActionType.move,
renderDepth: 1,
+ noSidebar: true,
isContentActive: returnFalse,
whenChildContentsActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
addDocTab: SchemaTableCell.addFieldDoc,
pinToPres: returnZero,
- Document,
+ Document: DocCast(Document.rootDocument, Document),
fieldKey: fieldKey,
PanelWidth: columnWidth,
PanelHeight: props.rowHeight,
+ rootSelected: props.rootSelected,
};
const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
const cursor = !readOnly ? 'text' : 'default';
@@ -127,19 +131,20 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
pointerEvents,
}}>
<EditableView
+ ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)}
oneLine={this._props.oneLine}
allowCRs={this._props.allowCRs}
contents={undefined}
fieldContents={fieldProps}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey, SnappingManager.MetaKey)}
+ GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)}
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 ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
+ const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
@@ -221,7 +226,7 @@ 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 alts = DocListCast(this._props.Document[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)
.filter(url => url)
@@ -328,13 +333,15 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
return this._props.isRowActive() && selected && selected?.filter(doc => doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col;
//return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
+
+ // if the text box blurs and none of its contents are focused(), then the edit finishes
selectedFunc = () => this.selected;
render() {
const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
fieldProps.isContentActive = this.selectedFunc;
return (
- <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}>
+ {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3fdc9a488..2a5732708 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -12,7 +12,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { UndoManager, undoable } from '../../util/UndoManager';
import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
+import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
// import { InkTranscription } from '../InkTranscription';
import { DocData } from '../../../fields/DocSymbols';
@@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView';
import { VideoBox } from '../nodes/VideoBox';
import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { ImageBox } from '../nodes/ImageBox';
ScriptingGlobals.add(function IsNoneSelected() {
return SelectionManager.Views.length <= 0;
@@ -58,7 +59,9 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
obj[fieldKey] = color;
CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
} else {
- dv.Document[DocData][fieldKey] = color;
+ const dataKey = Doc.LayoutFieldKey(dv.Document);
+ const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
+ dv.dataDoc[fieldKey + alternate] = color;
}
});
} else {
@@ -87,8 +90,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
});
} else {
Doc.SharingDoc().headingColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).headingColor = color;
- Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
+ Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+ Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
}
});
@@ -102,10 +105,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
- const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+ const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
['grid', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -131,10 +134,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
}],
- ['flashcards', {
- checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
- setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
- }],
]);
if (checkResult) {
@@ -152,26 +151,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
['font', {
checkResult: () => RichTextMenu.Instance?.fontFamily,
- setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
}],
['highlight', {
- checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
- setDoc: () => value && RichTextMenu.Instance.setHighlight(value),
+ checkResult: () => RichTextMenu.Instance?.fontHighlight,
+ setDoc: () => value && RichTextMenu.Instance?.setHighlight(value),
}],
['fontColor', {
checkResult: () => RichTextMenu.Instance?.fontColor,
- setDoc: () => value && RichTextMenu.Instance.setColor(value),
+ setDoc: () => value && RichTextMenu.Instance?.setColor(value),
}],
['alignment', {
- checkResult: () => RichTextMenu.Instance.textAlign,
- setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+ checkResult: () => RichTextMenu.Instance?.textAlign,
+ setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
}],
['fontSize', {
checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
setDoc: () => {
if (typeof value === 'number') value = value.toString();
if (value && Number(value).toString() === value) value += 'px';
- RichTextMenu.Instance.setFontSize(value);
+ RichTextMenu.Instance?.setFontSize(value);
},
}],
]);
@@ -183,45 +182,45 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
});
type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
-type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
const textView = RichTextMenu.Instance?.TextView;
const editorView = textView?.EditorView;
// prettier-ignore
const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
- [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance.textVcenter:
- (RichTextMenu.Instance.textAlign === where)):
+ [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
+ (RichTextMenu.Instance?.textAlign === where)):
where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
(Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
- RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
+ toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
+ RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
(Doc.UserDoc().textAlign = where))}]); // prettier-ignore
// prettier-ignore
const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
- [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
- toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+ [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false),
+ toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
['elide', { checkResult: () => false,
toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
- ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+ ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
- ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
- ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
- ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+ ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+ ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+ ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
const map = new Map(attrs.concat(alignments).concat(listings));
if (checkResult) {
return map.get(charStyle)?.checkResult();
}
- undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
+ undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
});
export function checkInksToGroup() {
@@ -340,15 +339,20 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
+ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
// prettier-ignore
- const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
+ const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
}],
+ ['labels', {
+ checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
+ setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel),
+ setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
+ }],
['fillColor', {
checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
@@ -403,6 +407,15 @@ ScriptingGlobals.add(function videoSnapshot() {
selected?.Snapshot();
});
+ScriptingGlobals.add(function imageSetPixelSize() {
+ const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+ selected?.setNativeSize();
+});
+ScriptingGlobals.add(function imageRotate90() {
+ const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+ selected?.rotate();
+});
+
/** Schema
* toggleSchemaPreview
**/
@@ -435,10 +448,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
*/
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
}
- if (editorView) RichTextMenu.Instance.setFontFamily(key);
+ if (editorView) RichTextMenu.Instance?.setFontFamily(key);
else Doc.UserDoc().fontFamily = key;
});
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 028d3da53..60def5d45 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -58,7 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
? DocCast(linkDoc.link_anchor_2)
: DocCast(linkDoc.link_anchor_1)
: LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
- LinkManager.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));
+ LinkManager.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));
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 81dd0eb98..a2c9d10b6 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -183,7 +183,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</div>
</Tooltip>
<Tooltip disableInteractive={true} title={<div className="dash-tooltip">Show/Hide Link</div>}>
- <div title="click to show link" className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
+ <div className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
<FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
</div>
</Tooltip>
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
index a272ce294..76efe0185 100644
--- a/src/client/views/newlightbox/Header/LightboxHeader.tsx
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -1,62 +1,70 @@
-import './LightboxHeader.scss';
-import * as React from 'react';
-import { INewLightboxHeader } from "./utils";
-import { NewLightboxView } from '../NewLightboxView';
-import { StrCast } from '../../../../fields/Types';
-import { EditableText } from '../components/EditableText';
-import { getType } from '../utils';
import { Button, IconButton, Size, Type } from 'browndash-components';
-import { MdExplore, MdTravelExplore } from 'react-icons/md'
-import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
+import * as React from 'react';
+import { BsBookmark, BsBookmarkFill } from 'react-icons/bs';
+import { MdTravelExplore } from 'react-icons/md';
import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
import { LightboxView } from '../../LightboxView';
import { Colors } from '../../global/globalEnums';
-
+import { NewLightboxView } from '../NewLightboxView';
+import { EditableText } from '../components/EditableText';
+import { getType } from '../utils';
+import './LightboxHeader.scss';
+import { INewLightboxHeader } from './utils';
export const NewLightboxHeader = (props: INewLightboxHeader) => {
- const {height = 100, width} = props;
- const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc)
- const [editing, setEditing] = React.useState<boolean>(false)
- const [title, setTitle] = React.useState<JSX.Element | null>(
- (null)
- )
+ const { height = 100, width } = props;
+ const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc);
+ const [editing, setEditing] = React.useState<boolean>(false);
+ const [title, setTitle] = React.useState<JSX.Element | null>(null);
React.useEffect(() => {
- let lbDoc = LightboxView.LightboxDoc
- setDoc(lbDoc)
+ let lbDoc = LightboxView.LightboxDoc;
+ setDoc(lbDoc);
if (lbDoc) {
setTitle(
- <EditableText
- editing={editing}
- text={StrCast(lbDoc.title)}
- onEdit={(newText: string) => {
- if(lbDoc) lbDoc.title = newText;
- }}
- setEditing={setEditing}
- />)
+ <EditableText
+ editing={editing}
+ text={StrCast(lbDoc.title)}
+ onEdit={(newText: string) => {
+ if (lbDoc) lbDoc.title = newText;
+ }}
+ setEditing={setEditing}
+ />
+ );
}
- }, [LightboxView.LightboxDoc])
+ }, [LightboxView.LightboxDoc]);
- const [saved, setSaved] = React.useState<boolean>(false)
+ const [saved, setSaved] = React.useState<boolean>(false);
- if (!doc) return null
- else return <div className={`newLightboxHeader-container`} onPointerDown={(e) => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
- <div className={`title-container`}>
- <div className={`lb-label`}>Title</div>
- {title}
- </div>
- <div className={`type-container`}>
- <div className={`lb-label`}>Type</div>
- <div className={`type`}>{getType(StrCast(doc.type))}</div>
- </div>
- <div style={{gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
- <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
- <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
- </div>
- <div style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
- <Button onClick={() => {
- console.log(NewLightboxView.ExploreMode)
- NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
- }} size={Size.XSMALL} color={Colors.DARK_GRAY} type={Type.SEC} text={"t-SNE 2D Embeddings"} icon={<MdTravelExplore/>}/>
- </div>
- </div>
-} \ No newline at end of file
+ if (!doc) return null;
+ else
+ return (
+ <div className={`newLightboxHeader-container`} onPointerDown={e => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
+ <div className={`title-container`}>
+ <div className={`lb-label`}>Title</div>
+ {title}
+ </div>
+ <div className={`type-container`}>
+ <div className={`lb-label`}>Type</div>
+ <div className={`type`}>{getType(StrCast(doc.type))}</div>
+ </div>
+ <div style={{ gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
+ </div>
+ <div style={{ gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
+ <Button
+ onClick={() => {
+ console.log(NewLightboxView.ExploreMode);
+ NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode);
+ }}
+ size={Size.XSMALL}
+ color={Colors.DARK_GRAY}
+ type={Type.SEC}
+ text={'t-SNE 2D Embeddings'}
+ icon={<MdTravelExplore />}
+ />
+ </div>
+ </div>
+ );
+};
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
index 9f3c32e4e..1d502b73f 100644
--- a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -1,74 +1,67 @@
-import { GrClose } from 'react-icons/gr';
-import { IRecommendation, Recommendation } from "../components";
-import './RecommendationList.scss';
+import { IconButton, Size, Type } from 'browndash-components';
import * as React from 'react';
-import { IRecommendationList } from "./utils";
-import { NewLightboxView } from '../NewLightboxView';
-import { DocCast, StrCast } from '../../../../fields/Types';
import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
-import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
-import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils';
-import { IBounds } from '../ExploreView/utils';
+import { GrClose } from 'react-icons/gr';
+import { DocListCast, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
-import { Id } from '../../../../fields/FieldSymbols';
+import { StrCast } from '../../../../fields/Types';
import { LightboxView } from '../../LightboxView';
-import { IconButton, Size, Type } from 'browndash-components';
import { Colors } from '../../global/globalEnums';
+import { IBounds } from '../ExploreView/utils';
+import { NewLightboxView } from '../NewLightboxView';
+import { IRecommendation, Recommendation } from '../components';
+import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils';
+import './RecommendationList.scss';
+import { IRecommendationList } from './utils';
export const RecommendationList = (props: IRecommendationList) => {
- const {loading, keywords} = props
- const [loadingKeywords, setLoadingKeywords] = React.useState<boolean>(true)
- const [showMore, setShowMore] = React.useState<boolean>(false)
- const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([])
- const [update, setUpdate] = React.useState<boolean>(true)
- const initialRecs: IRecommendation[] = [
- {loading: true},
- {loading: true},
- {loading: true},
- {loading: true},
- {loading: true}
- ];
- const [recs, setRecs] = React.useState<IRecommendation[]>(initialRecs)
+ const { loading, keywords } = props;
+ const [loadingKeywords, setLoadingKeywords] = React.useState<boolean>(true);
+ const [showMore, setShowMore] = React.useState<boolean>(false);
+ const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([]);
+ const [update, setUpdate] = React.useState<boolean>(true);
+ const initialRecs: IRecommendation[] = [{ loading: true }, { loading: true }, { loading: true }, { loading: true }, { loading: true }];
+ const [recs, setRecs] = React.useState<IRecommendation[]>(initialRecs);
React.useEffect(() => {
const getKeywords = async () => {
- let text = StrCast(LightboxView.LightboxDoc?.text)
- console.log('[1] fetching keywords')
- const response = await fetchKeywords(text, 5, true)
- console.log('[2] response:', response)
+ let text = StrCast(LightboxView.LightboxDoc?.text);
+ console.log('[1] fetching keywords');
+ const response = await fetchKeywords(text, 5, true);
+ console.log('[2] response:', response);
const kw = response.keywords;
console.log(kw);
NewLightboxView.SetKeywords(kw);
if (LightboxView.LightboxDoc) {
- console.log('setting keywords on doc')
+ console.log('setting keywords on doc');
LightboxView.LightboxDoc.keywords = new List<string>(kw);
setKeywordsLoc(NewLightboxView.Keywords);
}
- setLoadingKeywords(false)
- }
- let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords)
+ setLoadingKeywords(false);
+ };
+ let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords);
if (!keywordsList || keywordsList.length < 2) {
- setLoadingKeywords(true)
- getKeywords()
- setUpdate(!update)
+ setLoadingKeywords(true);
+ getKeywords();
+ setUpdate(!update);
} else {
- setKeywordsLoc(keywordsList)
- setLoadingKeywords(false)
- setUpdate(!update)
+ setKeywordsLoc(keywordsList);
+ setLoadingKeywords(false);
+ setUpdate(!update);
}
- }, [NewLightboxView.LightboxDoc])
+ }, [NewLightboxView.LightboxDoc]);
- // terms: vannevar bush, information spaces,
+ // terms: vannevar bush, information spaces,
React.useEffect(() => {
const getRecommendations = async () => {
- console.log('fetching recommendations')
- let query = 'undefined'
- if (keywordsLoc) query = keywordsLoc.join(',')
- let src = StrCast(NewLightboxView.LightboxDoc?.text)
- let dashDocs:IDocRequest[] = [];
+ console.log('fetching recommendations');
+ let query = 'undefined';
+ if (keywordsLoc) query = keywordsLoc.join(',');
+ let src = StrCast(NewLightboxView.LightboxDoc?.text);
+ let dashDocs: IDocRequest[] = [];
// get linked docs
- let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links)
- console.log("linked docs", linkedDocs)
+ let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links);
+ console.log('linked docs', linkedDocs);
// get context docs (docs that are also in the collection)
// let contextDocs: Doc[] = DocListCast(DocCast(LightboxView.LightboxDoc?.context).data)
// let docId = LightboxView.LightboxDoc && LightboxView.LightboxDoc[Id]
@@ -83,114 +76,142 @@ export const RecommendationList = (props: IRecommendationList) => {
// })
// }
// })
- console.log("dash docs", dashDocs)
+ console.log('dash docs', dashDocs);
if (query !== undefined) {
- const response = await fetchRecommendations(src, query, [], true)
- const num_recs = response.num_recommendations
- const recs = response.recommendations
- const keywords = response.keywords
+ const response = await fetchRecommendations(src, query, [], true);
+ const num_recs = response.num_recommendations;
+ const recs = response.recommendations;
+ const keywords = response.keywords;
const response_bounds: IBounds = {
- max_x: response.max_x,
- max_y: response.max_y,
- min_x: response.min_x,
- min_y: response.min_y
- }
+ max_x: response.max_x,
+ max_y: response.max_y,
+ min_x: response.min_x,
+ min_y: response.min_y,
+ };
// if (NewLightboxView.NewLightboxDoc) {
// NewLightboxView.NewLightboxDoc.keywords = new List<string>(keywords);
// setKeywordsLoc(NewLightboxView.Keywords);
// }
// console.log(response_bounds)
- NewLightboxView.SetBounds(response_bounds)
+ NewLightboxView.SetBounds(response_bounds);
const recommendations: IRecommendation[] = [];
for (const key in recs) {
- console.log(key)
+ console.log(key);
const title = recs[key].title;
- const url = recs[key].url
- const type = recs[key].type
- const text = recs[key].text
- const transcript = recs[key].transcript
- const previewUrl = recs[key].previewUrl
- const embedding = recs[key].embedding
- const distance = recs[key].distance
- const source = recs[key].source
- const related_concepts = recs[key].related_concepts
- const docId = recs[key].doc_id
- related_concepts.length >= 1 && recommendations.push({
- title: title,
- data: url,
- type: type,
- text: text,
- transcript: transcript,
- previewUrl: previewUrl,
- embedding: embedding,
- distance: Math.round(distance * 100) / 100,
- source: source,
- related_concepts: related_concepts,
- docId: docId
- })
+ const url = recs[key].url;
+ const type = recs[key].type;
+ const text = recs[key].text;
+ const transcript = recs[key].transcript;
+ const previewUrl = recs[key].previewUrl;
+ const embedding = recs[key].embedding;
+ const distance = recs[key].distance;
+ const source = recs[key].source;
+ const related_concepts = recs[key].related_concepts;
+ const docId = recs[key].doc_id;
+ related_concepts.length >= 1 &&
+ recommendations.push({
+ title: title,
+ data: url,
+ type: type,
+ text: text,
+ transcript: transcript,
+ previewUrl: previewUrl,
+ embedding: embedding,
+ distance: Math.round(distance * 100) / 100,
+ source: source,
+ related_concepts: related_concepts,
+ docId: docId,
+ });
}
recommendations.sort((a, b) => {
if (a.distance && b.distance) {
- return a.distance - b.distance
- } else return 0
- })
- console.log("[rec]: ", recommendations)
- NewLightboxView.SetRecs(recommendations)
- setRecs(recommendations)
+ return a.distance - b.distance;
+ } else return 0;
+ });
+ console.log('[rec]: ', recommendations);
+ NewLightboxView.SetRecs(recommendations);
+ setRecs(recommendations);
}
- }
+ };
getRecommendations();
- }, [update])
-
-
+ }, [update]);
- return <div className={`recommendationlist-container`} onPointerDown={(e) => {e.stopPropagation()}}>
- <div className={`header`}>
- <div className={`title`}>
- Recommendations
- </div>
- {NewLightboxView.LightboxDoc && <div style={{fontSize: 10}}>
- The recommendations are produced based on the text in the document <b><u>{StrCast(NewLightboxView.LightboxDoc.title)}</u></b>. The following keywords are used to fetch the recommendations.
- </div>}
- <div className={`lb-label`}>Keywords</div>
- {loadingKeywords ? <div className={`keywords`}>
- <div className={`keyword ${loadingKeywords && 'loading'}`}/>
- <div className={`keyword ${loadingKeywords && 'loading'}`}/>
- <div className={`keyword ${loadingKeywords && 'loading'}`}/>
- <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ return (
+ <div
+ className={`recommendationlist-container`}
+ onPointerDown={e => {
+ e.stopPropagation();
+ }}>
+ <div className={`header`}>
+ <div className={`title`}>Recommendations</div>
+ {NewLightboxView.LightboxDoc && (
+ <div style={{ fontSize: 10 }}>
+ The recommendations are produced based on the text in the document{' '}
+ <b>
+ <u>{StrCast(NewLightboxView.LightboxDoc.title)}</u>
+ </b>
+ . The following keywords are used to fetch the recommendations.
</div>
- :
- <div className={`keywords`}>
- {keywordsLoc && keywordsLoc.map((word, ind) => {
- return <div className={`keyword`}>
- {word}
- <IconButton type={Type.PRIM} size={Size.XSMALL} color={Colors.DARK_GRAY} icon={<GrClose/>} onClick={() => {
- let kw = keywordsLoc
- kw.splice(ind)
- NewLightboxView.SetKeywords(kw)
- }}/>
+ )}
+ <div className={`lb-label`}>Keywords</div>
+ {loadingKeywords ? (
+ <div className={`keywords`}>
+ <div className={`keyword ${loadingKeywords && 'loading'}`} />
+ <div className={`keyword ${loadingKeywords && 'loading'}`} />
+ <div className={`keyword ${loadingKeywords && 'loading'}`} />
+ <div className={`keyword ${loadingKeywords && 'loading'}`} />
</div>
- })}
- </div>
- }
- {!showMore ?
- <div className={`lb-caret`} onClick={() => {setShowMore(true)}}>
- More <FaCaretDown/>
+ ) : (
+ <div className={`keywords`}>
+ {keywordsLoc &&
+ keywordsLoc.map((word, ind) => {
+ return (
+ <div className={`keyword`}>
+ {word}
+ <IconButton
+ type={Type.PRIM}
+ size={Size.XSMALL}
+ color={Colors.DARK_GRAY}
+ icon={<GrClose />}
+ onClick={() => {
+ let kw = keywordsLoc;
+ kw.splice(ind);
+ NewLightboxView.SetKeywords(kw);
+ }}
+ />
+ </div>
+ );
+ })}
+ </div>
+ )}
+ {!showMore ? (
+ <div
+ className={`lb-caret`}
+ onClick={() => {
+ setShowMore(true);
+ }}>
+ More <FaCaretDown />
+ </div>
+ ) : (
+ <div className={`more`}>
+ <div
+ className={`lb-caret`}
+ onClick={() => {
+ setShowMore(false);
+ }}>
+ Less <FaCaretUp />
+ </div>
+ <div className={`lb-label`}>Type</div>
+ <div className={`lb-label`}>Sources</div>
+ </div>
+ )}
</div>
- :
- <div className={`more`}>
- <div className={`lb-caret`} onClick={() => {setShowMore(false)}}>
- Less <FaCaretUp/>
- </div>
- <div className={`lb-label`}>Type</div>
- <div className={`lb-label`}>Sources</div>
+ <div className={`recommendations`}>
+ {recs &&
+ recs.map((rec: IRecommendation) => {
+ return <Recommendation {...rec} />;
+ })}
</div>
- }
- </div>
- <div className={`recommendations`}>
- {recs && recs.map((rec: IRecommendation) => {
- return <Recommendation {...rec} />
- })}
</div>
- </div>
-} \ No newline at end of file
+ );
+};
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8a38ef663..c685ec66f 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -22,6 +22,7 @@ import { ViewBoxAnnotatableComponent } from '../DocComponent';
import './AudioBox.scss';
import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
+import { OpenWhere } from './DocumentView';
/**
* AudioBox
@@ -383,7 +384,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
Doc.AddToMyOverlay(newDoc);
} else {
- this._props.addDocument?.(newDoc);
+ this._props.addDocTab(newDoc, OpenWhere.addRight);
}
}),
false
@@ -606,7 +607,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div
className="audiobox-file"
style={{
- pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none',
flexDirection: this.miniPlayer ? 'row' : 'column',
justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
}}>
@@ -662,9 +662,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
max="1"
value={this._muted ? 0 : this._volume}
className="toolbar-slider volume"
- onPointerDown={(e: React.PointerEvent) => {
- e.stopPropagation();
- }}
+ onPointerDown={e => e.stopPropagation()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
/>
</div>
@@ -691,8 +689,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
value={this.timeline?._zoomFactor ?? 1}
className="toolbar-slider"
id="zoom-slider"
- onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.zoom(Number(e.target.value))}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={e => this.zoom(Number(e.target.value))}
/>
</div>
)}
@@ -752,7 +750,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
render() {
return (
- <div ref={this.setupTimelineDrop} className="audiobox-container" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+ <div ref={this.setupTimelineDrop} className="audiobox-container" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none' }}>
{!this.path ? this.recordingControls : this.playbackControls}
</div>
);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2800ea200..0d0a7c623 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { OmitKeys, numberRange } from '../../../Utils';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
+import { TransitionTimer } from '../../../fields/DocSymbols';
/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -91,6 +92,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@observable AutoDim = this.props.autoDim;
@observable Transition = this.props.transition;
+ componentDidMount(): void {
+ if (this.props.transition && !this.Document[TransitionTimer]) {
+ const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
+ this.Document[TransitionTimer] = setTimeout(
+ action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+ num
+ );
+ }
+ }
+
componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
super.componentDidUpdate(prevProps);
this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
@@ -98,14 +109,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
// this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
- DataTransition = () => this._props.transition; // prettier-ignore
+ DataTransition = () => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
if (doc === this.layoutDoc) {
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
case StyleProp.BackgroundColor: return this.BackgroundColor;
case StyleProp.Color: return this.Color;
@@ -224,7 +235,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
// undefined - this is not activated by a group
isGroupActive = () => {
if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
- const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ const backColor = this.BackgroundColor;
+ const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
render() {
@@ -237,7 +249,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.PanelWidth(),
height: this.PanelHeight(),
transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
- transition: this.Transition || StrCast(this.Document.dataTransition),
+ transition: this.DataTransition(),
zIndex: this.ZIndex,
display: this.Width ? undefined : 'none',
}}>
@@ -251,6 +263,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
styleProvider={this.styleProvider}
ScreenToLocalTransform={this.screenToLocalTransform}
isGroupActive={this.isGroupActive}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
/>
)}
</div>
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 62f630c6c..9ffdc350d 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -69,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
action((e, doubleTap) => {
if (doubleTap) {
this._isAnyChildContentActive = true;
- if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
- if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
}
}),
false,
@@ -131,8 +131,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return false;
};
- whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
-
closeDown = (e: React.PointerEvent, which: string) => {
setupMoveUpEvents(
this,
@@ -182,7 +180,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// 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_2)
if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
- var queryText = altText.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+ var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
if (queryText && queryText.match(/\(\(.*\)\)/)) {
KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index 6b5738790..e9a346fbe 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -32,6 +32,10 @@
.liveSchema-checkBox {
margin-bottom: -35px;
}
+
+ .displaySchemaLive {
+ margin-bottom: 20px;
+ }
.dataviz-sidebar {
position: absolute;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 66a08f13e..60c5fdba2 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -18,7 +18,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponen
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { SidebarAnnos } from '../../SidebarAnnos';
import { AnchorMenu } from '../../pdf/AnchorMenu';
-import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
+import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
import { DocumentView } from '../DocumentView';
import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
@@ -28,6 +28,7 @@ import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import { Checkbox } from '@mui/material';
+import { ContextMenu } from '../../ContextMenu';
export enum DataVizView {
TABLE = 'table',
@@ -43,6 +44,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ sidebarAddDoc: ((doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean) | undefined;
crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
@observable _marqueeing: number[] | undefined = undefined;
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -118,8 +120,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
- const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
- const changedAxes = this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
+ const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
+ const changedAxes = data.config_dataVizAxes && this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows);
this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
@@ -266,7 +268,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
this._disposers.datavis = reaction(
() => {
- if (this.layoutDoc.dataViz_schemaLive==undefined) this.layoutDoc.dataViz_schemaLive = true;
+ if (this.layoutDoc.dataViz_schemaLive == undefined) this.layoutDoc.dataViz_schemaLive = true;
const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
if (!keys) return;
@@ -283,42 +285,43 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
});
current.push(row);
});
- if (!this.layoutDoc._dataViz_schemaOG){ // makes a copy of the original table for the "live" toggle
- let csvRows = [];
- csvRows.push(keys.join(','));
- for (let i = 0; i < children.length-1; i++) {
- let eachRow = [];
- for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
- eachRow.push(cell);
- }
- csvRows.push(eachRow);
+ if (!this.layoutDoc._dataViz_schemaOG) {
+ // makes a copy of the original table for the "live" toggle
+ let csvRows = [];
+ csvRows.push(keys.join(','));
+ for (let i = 0; i < children.length - 1; i++) {
+ let eachRow = [];
+ for (let j = 0; j < keys.length; j++) {
+ var cell = children[i][keys[j]];
+ if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ eachRow.push(cell);
}
- const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
- const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
- const file = new File([blob], 'schemaTable for static dataviz', options);
- const loading = Docs.Create.LoadingDocument(file, options);
- DocUtils.uploadFileToDoc(file, {}, loading);
- 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.href
- if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){ // sets original dataset to the var
- const lastRow = current.pop();
- DataVizBox.datasetSchemaOG.set(href, current);
- current.push(lastRow!);
- fetch('/csvData?uri=' + ogHref)
- .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ csvRows.push(eachRow);
}
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
+ const file = new File([blob], 'schemaTable for static dataviz', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ DocUtils.uploadFileToDoc(file, {}, loading);
+ 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.href;
+ if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
+ // sets original dataset to the var
+ const lastRow = current.pop();
+ DataVizBox.datasetSchemaOG.set(href, current);
+ current.push(lastRow!);
+ fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ }
return current;
},
current => {
if (current) {
- 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)!);
+ 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)!);
}
},
{ fireImmediately: true }
@@ -402,6 +405,26 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
}
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const cm = ContextMenu.Instance;
+ const options = cm.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+ }
+
+
+ askGPT = action(async () => {
+ GPTPopup.Instance.setSidebarId('data_sidebar');
+ GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+ GPTPopup.Instance.setDataJson("");
+ GPTPopup.Instance.setMode(GPTPopupMode.DATA);
+ let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ let input = JSON.stringify(data);
+ GPTPopup.Instance.setDataJson(input);
+ GPTPopup.Instance.generateDataAnalysis();
+ });
+
render() {
const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
@@ -418,6 +441,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
transform: `scale(${scale})`,
position: 'absolute',
}}
+ onContextMenu={this.specificContextMenu}
onWheel={e => e.stopPropagation()}
ref={this._mainCont}>
<div className="datatype-button">
@@ -428,9 +452,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
</div>
{(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
- <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
- <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
- Display Live Updates to Canvas
+ <div className={'displaySchemaLive'}>
+ <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
</div>
) : null}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 41ce637ac..cf0007cfd 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -120,11 +120,62 @@
}
}
}
-.selectAll-buttons {
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
+.tableBox-selectButtons {
margin-top: 5px;
- margin-right: 10px;
- float: right;
+ margin-left: 25px;
+ display: inline-block;
+ padding: 2px;
+ .tableBox-selectTitle {
+ display: inline-flex;
+ flex-direction: row;
+ }
+ .tableBox-filtering {
+ display: flex;
+ flex-direction: row;
+ float: right;
+ margin-right: 10px;
+ .tableBox-filterAll {
+ min-width: 75px;
+ }
+ }
+}
+
+.tableBox-filterPopup {
+ background: $light-gray;
+ position: absolute;
+ min-width: 235px;
+ top: 60px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ z-index: 2;
+ padding: 7px;
+ border-radius: 5px;
+ margin: 3px;
+ .tableBox-filterPopup-selectColumn {
+ margin-top: 5px;
+ flex-direction: row;
+ .tableBox-filterPopup-selectColumn-each {
+ margin-left: 25px;
+ border-radius: 3px;
+ background: $light-gray;
+ }
+ }
+ .tableBox-filterPopup-setValue {
+ margin-top: 5px;
+ display: flex;
+ flex-direction: row;
+ .tableBox-filterPopup-setValue-each {
+ margin-right: 5px;
+ border-radius: 3px;
+ background: $light-gray;
+ }
+ .tableBox-filterPopup-setValue-input {
+ margin: 5px;
+ }
+ }
+ .tableBox-filterPopup-setFilter {
+ margin-top: 5px;
+ align-self: center;
+ }
}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 1b239b5e5..67e1c67bd 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -12,7 +12,8 @@ import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';
-const { default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
+import { undoable } from '../../../../util/UndoManager';
+const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
interface TableBoxProps {
Document: Doc;
layoutDoc: Doc;
@@ -37,6 +38,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
_inputChangedDisposer?: IReactionDisposer;
_containerRef: HTMLDivElement | null = null;
+ @observable settingTitle: boolean = false; // true when setting a title column
+ @observable hasRowsToFilter: boolean = false; // true when any rows are selected
+ @observable filtering: boolean = false; // true when the filtering menu is open
+ @observable filteringColumn: any = ''; // column to filter
+ @observable filteringType: string = 'Value'; // "Value" or "Range"
+ filteringVal: any[] = ['', '']; // value or range to filter the column with
+
@observable _scrollTop = -1;
@observable _tableHeight = 0;
@observable _tableContainerHeight = 0;
@@ -49,6 +57,8 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
// if the tableData changes (ie., when records are selected by the parent (input) visulization),
// then we need to remove any selected rows that are no longer part of the visualized dataset.
this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true });
+ const selected = NumListCast(this._props.layoutDoc.dataViz_selectedRows);
+ if (selected.length > 0) this.hasRowsToFilter = true;
this.handleScroll();
}
componentWillUnmount() {
@@ -64,9 +74,6 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
- // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@computed get columns() {
@@ -115,6 +122,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
} else selected?.push(rowId);
}
e.stopPropagation();
+ this.hasRowsToFilter = selected.length > 0 ? true : false;
};
columnPointerDown = (e: React.PointerEvent, col: string) => {
@@ -155,15 +163,15 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
},
emptyFunction,
action(e => {
- if (e.shiftKey){
- if (this._props.titleCol == col) this._props.titleCol = "";
+ if (e.shiftKey || this.settingTitle) {
+ if (this.settingTitle) this.settingTitle = false;
+ if (this._props.titleCol == col) this._props.titleCol = '';
else this._props.titleCol = col;
this._props.selectTitleCol(this._props.titleCol);
- }
- else{
+ } else {
const newAxes = this._props.axes;
if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
- else if (newAxes.length > 2) newAxes[newAxes.length-1] = col;
+ else if (newAxes.length > 2) newAxes[newAxes.length - 1] = col;
else newAxes.push(col);
this._props.selectAxes(newAxes);
}
@@ -171,6 +179,134 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
);
};
+ /**
+ * These functions handle the filtering popup for when the "filter" button is pressed to select rows
+ */
+ filter = undoable((e: any) => {
+ var start: any;
+ var end: any;
+ if (this.filteringType == 'Range') {
+ start = (this.filteringVal[0] as Number) ? Number(this.filteringVal[0]) : this.filteringVal[0];
+ end = (this.filteringVal[1] as Number) ? Number(this.filteringVal[1]) : this.filteringVal[0];
+ }
+
+ this._tableDataIds.forEach(rowID => {
+ if (this.filteringType == 'Value') {
+ if (this._props.records[rowID][this.filteringColumn] == this.filteringVal[0]) {
+ if (!NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowID)) {
+ this.tableRowClick(e, rowID);
+ }
+ }
+ } else {
+ let compare = this._props.records[rowID][this.filteringColumn];
+ if (compare as Number) compare = Number(compare);
+ if (start <= compare && compare <= end) {
+ if (!NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowID)) {
+ this.tableRowClick(e, rowID);
+ }
+ }
+ }
+ });
+ this.filtering = false;
+ this.filteringColumn = '';
+ this.filteringVal = ['', ''];
+ }, 'filter table');
+ @action
+ setFilterColumn = (e: any) => {
+ this.filteringColumn = e.currentTarget.value;
+ };
+ @action
+ setFilterType = (e: any) => {
+ this.filteringType = e.currentTarget.value;
+ };
+ changeFilterValue = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.filteringVal[0] = e.target.value;
+ });
+ changeFilterRange0 = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.filteringVal[0] = e.target.value;
+ });
+ changeFilterRange1 = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.filteringVal[1] = e.target.value;
+ });
+ @computed get renderFiltering() {
+ if (this.filteringColumn === '') this.filteringColumn = this.columns[0];
+ return (
+ <div className="tableBox-filterPopup" style={{ right: this._props.width * 0.05 }}>
+ <div className="tableBox-filterPopup-selectColumn">
+ Column:
+ <select className="tableBox-filterPopup-selectColumn-each" value={this.filteringColumn != '' ? this.filteringColumn : this.columns[0]} onChange={e => this.setFilterColumn(e)}>
+ {this.columns.map(column => (
+ <option className="" key={column} value={column}>
+ {' '}
+ {column}{' '}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div className="tableBox-filterPopup-setValue">
+ <select className="tableBox-filterPopup-setValue-each" value={this.filteringType} onChange={e => this.setFilterType(e)}>
+ <option className="" key={'Value'} value={'Value'}>
+ {' '}
+ {'Value'}{' '}
+ </option>
+ <option className="" key={'Range'} value={'Range'}>
+ {' '}
+ {'Range'}{' '}
+ </option>
+ </select>
+ :
+ {this.filteringType == 'Value' ? (
+ <input
+ className="tableBox-filterPopup-setValue-input"
+ defaultValue=""
+ autoComplete="off"
+ onChange={this.changeFilterValue}
+ onKeyDown={e => {
+ e.stopPropagation();
+ }}
+ type="text"
+ placeholder=""
+ id="search-input"
+ />
+ ) : (
+ <div>
+ <input
+ className="tableBox-filterPopup-setValue-input"
+ defaultValue=""
+ autoComplete="off"
+ onChange={this.changeFilterRange0}
+ onKeyDown={e => {
+ e.stopPropagation();
+ }}
+ type="text"
+ placeholder=""
+ id="search-input"
+ style={{ width: this._props.width * 0.15 }}
+ />
+ to
+ <input
+ className="tableBox-filterPopup-setValue-input"
+ defaultValue=""
+ autoComplete="off"
+ onChange={this.changeFilterRange1}
+ onKeyDown={e => {
+ e.stopPropagation();
+ }}
+ type="text"
+ placeholder=""
+ id="search-input"
+ style={{ width: this._props.width * 0.15 }}
+ />
+ </div>
+ )}
+ </div>
+ <div className="tableBox-filterPopup-setFilter">
+ <Button onClick={action(e => this.filter(e))} text="Set Filter" type={Type.SEC} color={'black'} />
+ </div>
+ </div>
+ );
+ }
+
render() {
if (this._tableData.length > 0) {
return (
@@ -184,9 +320,39 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
}
}}>
- <div className="selectAll-buttons">
- <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
- <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
+ <div className="tableBox-selectButtons">
+ <div className="tableBox-selectTitle">
+ <Button onClick={action(() => (this.settingTitle = !this.settingTitle))} text="Select Title Column" type={Type.SEC} color={'black'} />
+ </div>
+ <div className="tableBox-filtering">
+ {this.filtering ? this.renderFiltering : null}
+ <Button onClick={action(() => (this.filtering = !this.filtering))} text="Filter" type={Type.SEC} color={'black'} />
+ <div className="tableBox-filterAll">
+ {this.hasRowsToFilter ? (
+ <Button
+ onClick={action(() => {
+ this._props.layoutDoc.dataViz_selectedRows = new List<number>();
+ this.hasRowsToFilter = false;
+ })}
+ text="Deselect All"
+ type={Type.SEC}
+ color={'black'}
+ tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
+ />
+ ) : (
+ <Button
+ onClick={action(() => {
+ this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
+ this.hasRowsToFilter = true;
+ })}
+ text="Select All"
+ type={Type.SEC}
+ color={'black'}
+ tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
+ />
+ )}
+ </div>
+ </div>
</div>
<div
className={`tableBox-container ${this.columns[0]}`}
@@ -220,15 +386,23 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
<th
key={this.columns.indexOf(col)}
style={{
- color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen'
- : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? 'darkred'
- : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? 'darkblue' : undefined,
- background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb'
- : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? '#Fbdbdb'
- : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? '#c6ebf7' : undefined,
- // blue: #ADD8E6
- // green: #E3fbdb
- // red: #Fbdbdb
+ color:
+ this._props.axes.slice().reverse().lastElement() === col
+ ? 'darkgreen'
+ : this._props.axes.length > 2 && this._props.axes.lastElement() === col
+ ? 'darkred'
+ : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+ ? 'darkblue'
+ : undefined,
+ background: this.settingTitle
+ ? 'lightgrey'
+ : this._props.axes.slice().reverse().lastElement() === col
+ ? '#E3fbdb'
+ : this._props.axes.length > 2 && this._props.axes.lastElement() === col
+ ? '#Fbdbdb'
+ : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+ ? '#c6ebf7'
+ : undefined,
fontWeight: 'bolder',
border: '3px solid black',
}}
@@ -251,10 +425,10 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}}>
{this.columns.map(col => {
var colSelected = false;
- if (this._props.axes.length>2) colSelected = this._props.axes[0]==col || this._props.axes[1]==col || this._props.axes[2]==col;
- else if (this._props.axes.length>1) colSelected = this._props.axes[0]==col || this._props.axes[1]==col;
- else if (this._props.axes.length>0) colSelected = this._props.axes[0]==col;
- if (this._props.titleCol==col) colSelected = true;
+ if (this._props.axes.length > 2) colSelected = this._props.axes[0] == col || this._props.axes[1] == col || this._props.axes[2] == col;
+ else if (this._props.axes.length > 1) colSelected = this._props.axes[0] == col || this._props.axes[1] == col;
+ else if (this._props.axes.length > 0) colSelected = this._props.axes[0] == col;
+ if (this._props.titleCol == col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5a326ecb0..e729e2fa2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -7,7 +7,7 @@ import { OmitKeys, Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { InkingStroke } from '../InkingStroke';
import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -128,7 +128,9 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
if (!this.layoutDoc) return '<p>awaiting layout</p>';
if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
+ const layoutDoc = tempLayout ?? this.layoutDoc;
+ const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string');
if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return '<p>Loading layout</p>';
@@ -154,6 +156,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
'LayoutTemplate',
'layoutFieldKey',
'dontCenter',
+ 'DataTransition',
'contextMenuItems',
//'onClick', // don't need to omit this since it will be set
'onDoubleClickScript',
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5421c1b50..23dada260 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -234,6 +234,12 @@
}
}
+.documntViewInternal-dropdown {
+ > div {
+ transform-origin: top left !important;
+ }
+}
+
.contentFittingDocumentView {
position: relative;
display: flex;
@@ -256,6 +262,5 @@
.documentView-node:first-child {
position: relative;
- background: '#B59B66'; //$white;
}
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ae83819e5..21c7f3079 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -104,12 +104,11 @@ export interface DocumentViewProps extends FieldViewSharedProps {
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;
- suppressSetHeight?: boolean;
onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
DataTransition?: () => string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
- contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+ contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
@@ -201,6 +200,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
((!this.disableClickScriptFunc && //
this.onClickHandler &&
!this._props.onBrowseClickScript?.() &&
+ !this.layoutDoc.layout_isSvg &&
this.isContentActive() !== true) ||
this.isContentActive() === false)
? 'none'
@@ -223,7 +223,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
);
}
- @computed get _allLinks() {
+ @computed get _allLinks(): Doc[] {
TraceMobx();
return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
}
@@ -560,7 +560,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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' })
);
- this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
+ this._props
+ .contextMenuItems?.()
+ .forEach(
+ item =>
+ item.label &&
+ cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
+ );
if (!this.Document.isFolder) {
const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
@@ -597,7 +603,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!this.Document.annotationOn) {
onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
!Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
- cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
@@ -810,8 +816,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
/**
* displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
- * setting layout_showTitle using the format: field1[;field2[...][:hover]]
- * from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover]
+ * setting layout_showTitle using the format: field1[:hover]
**/
@computed get titleView() {
const showTitle = this.layout_showTitle?.split(':')[0];
@@ -838,7 +843,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
- {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>}
+ {!dropdownWidth ? null : (
+ <div className="documntViewInternal-dropdown" style={{ width: dropdownWidth }}>
+ {this.fieldsDropdown(showTitle)}
+ </div>
+ )}
<div
style={{
width: `calc(100% - ${dropdownWidth}px)`,
@@ -851,21 +860,26 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
contents={
showTitle
.split(';')
- .map(field => Field.toString(targetDoc[field.trim()] as Field))
+ .map(field => Field.toJavascriptString(this.Document[field] as Field))
.join(' \\ ') || '-unset-'
}
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
- GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
+ GetValue={() =>
+ showTitle
+ .split(';')
+ .map(field => Field.toKeyValueString(this.Document, field))
+ .join('\\')
+ }
SetValue={undoBatch((input: string) => {
- if (input?.startsWith('#')) {
+ if (input?.startsWith('$')) {
if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = input?.substring(1);
+ this.layoutDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
} else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+ Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
}
- } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+ } else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
KeyValueBox.SetField(targetDoc, showTitle, input);
}
return true;
@@ -892,6 +906,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
fieldKey={this.layout_showCaption}
styleProvider={this.captionStyleProvider}
dontRegisterView={true}
+ rootSelected={this.rootSelected}
noSidebar={true}
dontScale={true}
renderDepth={this._props.renderDepth}
@@ -909,7 +924,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: this.docContents ?? (
<div
className="documentView-node"
- id={this.Document[Id]}
+ id={this.Document.type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
style={{
...style,
background: this.backgroundBoxColor,
@@ -1053,8 +1068,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewTimer: NodeJS.Timeout | undefined;
private _animEffectTimer: NodeJS.Timeout | undefined;
- public Guid = Utils.GenerateGuid(); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
-
+ /**
+ * This is used to create an id for tracking a Doc. Since the Doc can be in a regular view and in the lightbox at
+ * the same time, this creates a different version of the id depending on whether the search scope will be in the lightbox or not.
+ * @param inLightbox is the id scoped to the lightbox
+ * @param id the id
+ * @returns
+ */
+ public static UniquifyId(inLightbox: boolean | undefined, id: string) {
+ return (inLightbox ? 'lightbox-' : '') + id;
+ }
+ public ViewGuid = DocumentView.UniquifyId(LightboxView.Contains(this), Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
+ public DocUniqueId = DocumentView.UniquifyId(LightboxView.Contains(this), this.Document[Id]);
@computed public static get exploreMode() {
return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
@@ -1357,6 +1382,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
//}
};
+ backgroundColor = () => this._docViewInternal?.backgroundBoxColor;
DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition);
ShouldNotScale = () => this.shouldNotScale;
NativeWidth = () => this.effectiveNativeWidth;
@@ -1410,7 +1436,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div id={this.Guid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 50d4c7c78..a557cff4f 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,17 +1,17 @@
import { action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DivHeight, DivWidth } from '../../../Utils';
import { Id } from '../../../fields/FieldSymbols';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import EquationEditor from './formattedText/EquationEditor';
-import { DivHeight, DivWidth } from '../../../Utils';
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -80,7 +80,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
_height: 300,
backgroundColor: 'white',
});
+ const link = DocUtils.MakeLink(this.Document, graph, { link_relationship: 'function', link_description: 'input' });
this._props.addDocument?.(graph);
+ link && this._props.addDocument?.(link);
e.stopPropagation();
}
if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
@@ -94,11 +96,10 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.layoutDoc._nativeWidth) {
// if equation has been scaled then editing the expression must also edit the native dimensions to keep the aspect ratio
const prevNwidth = NumCast(this.layoutDoc._nativeWidth);
- const prevNheight = NumCast(this.layoutDoc._nativeHeight);
- this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', '')));
- this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', '')));
+ const newNwidth = (this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', ''))));
+ const newNheight = (this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', ''))));
this.layoutDoc._width = (NumCast(this.layoutDoc._width) * NumCast(this.layoutDoc._nativeWidth)) / prevNwidth;
- this.layoutDoc._height = (NumCast(this.layoutDoc._height) * NumCast(this.layoutDoc._nativeHeight)) / prevNheight;
+ this.layoutDoc._height = (NumCast(this.layoutDoc._width) * newNheight) / newNwidth;
} else {
this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', '')));
this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', '')));
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5b47dd91d..771856788 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -92,6 +92,7 @@ export interface FieldViewSharedProps {
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt<string>;
+ suppressSetHeight?: boolean;
}
/**
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 2e7a2120e..a86bdbd79 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -7,12 +7,13 @@ import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
+import { LinkManager } from '../../util/LinkManager';
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -33,7 +34,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
componentDidMount() {
this._props.setContentViewBox?.(this);
reaction(
- () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
+ () => [this.graphFuncs, this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
() => this.createGraph()
);
}
@@ -45,11 +46,26 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
if (addAsAnnotation) this.addDocument(anchor);
return anchor;
};
+ @computed get graphFuncs() {
+ const links = LinkManager.Instance.getAllRelatedLinks(this.Document)
+ .map(d => LinkManager.getOppositeAnchor(d, this.Document))
+ .filter(d => d)
+ .map(d => d!);
+ const funcs = links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc =>
+ StrCast(doc.text, 'x^2')
+ .replace(/\\sqrt/g, 'sqrt')
+ .replace(/\\frac\{(.*)\}\{(.*)\}/g, '($1/$2)')
+ .replace(/\\left/g, '')
+ .replace(/\\right/g, '')
+ .replace(/\{/g, '')
+ .replace(/\}/g, '')
+ );
+ return funcs;
+ }
createGraph = (ele?: HTMLDivElement) => {
this._plotEle = ele || this._plotEle;
const width = this._props.PanelWidth();
const height = this._props.PanelHeight();
- const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
try {
this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
this._plot = functionPlot({
@@ -59,7 +75,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) },
yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) },
grid: true,
- data: fns.map(fn => ({
+ data: this.graphFuncs.map(fn => ({
fn,
// derivative: { fn: "2 * x", updateOnMouseMove: true }
})),
@@ -72,7 +88,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
- const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true);
+ const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
+ ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
+ if (res) {
+ const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' });
+ link && this._props.addDocument?.(link);
+ }
+ return res;
+ }, true);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
@@ -104,7 +127,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
{this.theGraph}
<div
style={{
- display: this._props.isSelected() ? 'none' : undefined,
+ display: this._props.isContentActive() ? 'none' : undefined,
position: 'absolute',
width: '100%',
height: '100%',
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e2b0ee7df..a79dda74e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,20 +1,23 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
+import { Colors } from 'browndash-components';
+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';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, 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 { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
+import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
@@ -23,29 +26,39 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
+export class ImageEditorData {
+ private static _instance: ImageEditorData;
+ private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
+ @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
+ @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc });
+
+ constructor() {
+ makeObservable(this);
+ ImageEditorData._instance = this;
+ }
+
+ public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore
+ public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore
+ public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
+ public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
+ public static set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore
+ public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
+}
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ImageBox, fieldKey);
}
-
- @observable public static imageRootDoc: Doc | undefined = undefined;
- @observable public static imageEditorOpen: boolean = false;
- @observable public static imageEditorSource: string = '';
- @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined = undefined;
- @action public static setImageEditorOpen(open: boolean) {
- ImageBox.imageEditorOpen = open;
- }
- @action public static setImageEditorSource(source: string) {
- ImageBox.imageEditorSource = source;
- }
private _ignoreScroll = false;
private _forcedScroll = false;
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -134,7 +147,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
- return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
}, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -159,8 +172,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@undoBatch
setNativeSize = action(() => {
- const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
- const nscale = NumCast(this._props.PanelWidth()) / scaling;
+ const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
@@ -243,10 +255,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
funcs.push({
description: 'Open Image Editor',
event: action(() => {
- ImageBox.setImageEditorOpen(true);
- ImageBox.setImageEditorSource(this.choosePath(field.url));
- ImageBox.addDoc = this._props.addDocument;
- ImageBox.imageRootDoc = this.Document;
+ ImageEditorData.Open = true;
+ ImageEditorData.Source = this.choosePath(field.url);
+ ImageEditorData.AddDoc = this._props.addDocument;
+ ImageEditorData.RootDoc = this.Document;
}),
icon: 'pencil-alt',
});
@@ -259,7 +271,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
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 Utils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +309,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
ref={this._overlayIconRef}
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +323,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // 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 altpaths = alts
- .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
- .filter(url => url)
- .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
+ const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+ const altpaths =
+ alts
+ ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(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;
- return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+ return paths.length ? paths : [defaultUrl.href];
}
@observable _error = '';
@@ -326,7 +340,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@computed get content() {
TraceMobx();
- const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -370,6 +384,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
+ if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
@@ -468,4 +483,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
</div>
);
}
+
+ public chooseImage = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.accept = 'image/*';
+ input.onchange = async _e => {
+ const file = input.files?.[0];
+ if (file) {
+ const disposer = OverlayView.ShowSpinner();
+ const [{ result }] = await Networking.UploadFilesToServer({ file });
+ if (result instanceof Error) {
+ alert('Error uploading files - possibly due to unsupported file types');
+ } else {
+ this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+ }
+ disposer();
+ } else {
+ console.log('No file selected');
+ }
+ };
+ input.click();
+ };
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index d85432631..31a2367fc 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -55,7 +55,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
+ return DocCast(this._props.Document);
}
@action
@@ -105,14 +105,15 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
default: {
const _setCacheResult_ = (value: FieldResult) => {
field = value as Field;
- setResult?.(value);
+ if (setResult) setResult?.(value);
+ else target[key] = field;
};
const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log);
if (!res.success) {
if (key) target[key] = script.originalScript;
return false;
}
- field === undefined && (field = res.result);
+ field === undefined && (field = res.result instanceof Array ? new List<any>(res.result) : res.result);
}
}
if (!key) return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index be20b5934..74e78c671 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get Title() {
- return Field.toString(this.dataDoc[this.fieldKey] as Field);
+ return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 0a4325d8c..ff1e62885 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -13,7 +13,7 @@ import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkAnchorBox.scss';
import { LinkInfo } from './LinkDocPreview';
-const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
@observer
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 6e4d0e92a..3a2509c3d 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,18 +2,20 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { FieldResult } from '../../../fields/Doc';
import { DocCss, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
+import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
@@ -22,8 +24,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string = 'link') {
return FieldView.LayoutString(LinkBox, fieldKey);
}
- disposer: IReactionDisposer | undefined;
- @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+ _disposers: { [name: string]: IReactionDisposer } = {};
+ @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
@observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
constructor(props: FieldViewProps) {
@@ -38,45 +40,48 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
+ _hackToSeeIfDeleted: any;
componentWillUnmount() {
- this.disposer?.();
+ this._hackToSeeIfDeleted && clearTimeout(this._hackToSeeIfDeleted);
+ Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
componentDidMount() {
this._props.setContentViewBox?.(this);
- this.disposer = reaction(
- () => ({ drag: SnappingManager.IsDragging }),
- ({ drag }) => {
- !LightboxView.Contains(this.DocumentView?.()) &&
- setTimeout(
- // need to wait for drag manager to set 'hidden' flag on dragged DOM elements
- action(() => {
- const a = this.anchor1,
- b = this.anchor2;
- let a1 = a && document.getElementById(a.Guid);
- let a2 = b && document.getElementById(b.Guid);
- // test whether the anchors themselves are hidden,...
- if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
- else {
- // .. or whether and of their DOM parents are hidden
- for (; a1 && !a1.hidden; a1 = a1.parentElement);
- for (; a2 && !a2.hidden; a2 = a2.parentElement);
- this._hide = a1 || a2 ? true : false;
- }
- })
- );
- },
- { fireImmediately: true }
+ this._disposers.deleting = reaction(
+ () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
+ empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() =>
+ (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
+ )), 1000) // prettier-ignore
+ );
+ this._disposers.dragging = reaction(
+ () => SnappingManager.IsDragging,
+ () => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements
+ const a = this.anchor1,
+ b = this.anchor2;
+ let a1 = a && document.getElementById(a.ViewGuid);
+ let a2 = b && document.getElementById(b.ViewGuid);
+ // test whether the anchors themselves are hidden,...
+ if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+ else {
+ // .. or whether any of their DOM parents are hidden
+ for (; a1 && !a1.hidden; a1 = a1.parentElement);
+ for (; a2 && !a2.hidden; a2 = a2.parentElement);
+ this._hide = a1 || a2 ? true : false;
+ }
+ })) // prettier-ignore
);
}
render() {
+ TraceMobx();
+
if (this._hide) return null;
const a = this.anchor1;
const b = this.anchor2;
this._forceAnimate;
const docView = this._props.docViewPath().lastElement();
- if (a && b && !LightboxView.Contains(docView)) {
+ if (a && b) {
// text selection bounds are not directly observable, so we have to
// force an update when anything that could affect them changes (text edits causing reflow, scrolling)
a.Document[a.LayoutFieldKey];
@@ -92,18 +97,39 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+ var foundParent = false;
+ const getAnchor = (field: FieldResult): Element[] => {
+ const docField = DocCast(field);
+ const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
+ const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id]));
+ if (ele?.className === 'linkBox-label') foundParent = true;
+ if (ele?.getBoundingClientRect().width) return [ele];
+ const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+ const annoOn = DocCast(doc.annotationOn);
+ if (eles.length || !annoOn) return eles;
+ const pareles = getAnchor(annoOn);
+ foundParent = pareles.length ? true : false;
+ return pareles;
+ };
// if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
- const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+ const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1);
+ const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2);
- const aid = targetAhyperlink?.id || a.Document[Id];
- const bid = targetBhyperlink?.id || b.Document[Id];
- if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ const container = this.DocumentView?.().containerViewPath?.().lastElement()?.ContentDiv;
+ const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
+ const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
+ if (!aid || !bid) {
setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
return null;
}
+ if (foundParent) {
+ setTimeout(
+ action(() => (this._forceAnimate = this._forceAnimate + 0.01)),
+ 1
+ );
+ }
if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
@@ -149,6 +175,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
color={color}
labels={
<div
+ id={this.DocumentView?.().DocUniqueId}
+ className={'linkBox-label'}
style={{
borderRadius: '8px',
pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
@@ -192,6 +220,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
</>
);
}
+
+ setTimeout(
+ action(() => (this._forceAnimate = this._forceAnimate + 1)),
+ 2
+ );
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 1645d0813..2a96ce458 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -69,8 +69,10 @@ export class LinkDescriptionPopup extends React.Component<{}> {
}}>
<input
className="linkDescriptionPopup-input"
- onKeyDown={e => e.stopPropagation()}
- onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
+ onKeyDown={e => {
+ e.key === 'Enter' && this.onDismiss(true);
+ e.stopPropagation();
+ }}
value={this.description}
placeholder={this.description || '(Optional) Enter link description...'}
onChange={e => this.descriptionChanged(e)}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ae25ff179..c9c8f9260 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -172,6 +172,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
if (nextHrefInd !== this._hrefInd) {
this._linkDoc = undefined;
this._hrefInd = nextHrefInd;
+ this.updateHref();
}
}),
true
@@ -184,8 +185,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
} else if (this._props.hrefs?.length) {
const webDoc =
- Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()).lastElement() ??
- Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
+ Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
+ .filter(doc => doc.type === DocumentType.WEB)
+ .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
DocumentManager.Instance.showDocument(webDoc, {
openLocation: OpenWhere.lightbox,
willPan: true,
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 927e6fad4..b73898f59 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -6,7 +6,7 @@ import { IconButton, Size, Type } from 'browndash-components';
import * as d3 from 'd3';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
-import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
@@ -14,7 +14,6 @@ import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState
import { MarkerEvent } from 'react-map-gl/dist/esm/types';
import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCss, Highlight } from '../../../../fields/DocSymbols';
import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocumentType } from '../../../documents/DocumentTypes';
import { DocUtils, Docs } from '../../../documents/Documents';
@@ -28,7 +27,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -53,7 +52,6 @@ import { MarkerIcons } from './MarkerIcons';
* A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
*/
-const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
@@ -66,72 +64,69 @@ type PopupInfo = {
description: string;
};
-// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
-// mapboxAccessToken: string;
-// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
-// position: ControlPosition;
-
-// onResult: (...args: any[]) => void;
-// };
-
-type MapMarker = {
- longitude: number;
- latitude: number;
-};
-
-/**
- * Consider integrating later: allows for drawing, circling, making shapes on map
- */
-// const drawingManager = new window.google.maps.drawing.DrawingManager({
-// drawingControl: true,
-// drawingControlOptions: {
-// position: google.maps.ControlPosition.TOP_RIGHT,
-// drawingModes: [
-// google.maps.drawing.OverlayType.MARKER,
-// // currently we are not supporting the following drawing mode on map, a thought for future development
-// google.maps.drawing.OverlayType.CIRCLE,
-// google.maps.drawing.OverlayType.POLYLINE,
-// ],
-// },
-// });
-
@observer
export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapBox, fieldKey);
}
- private _dragRef = React.createRef<HTMLDivElement>();
+ private _unmounting = false;
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _mapRef: React.RefObject<MapRef> = React.createRef();
private _disposers: { [key: string]: IReactionDisposer } = {};
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
- @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @computed get allSidebarDocs() {
- return DocListCast(this.dataDoc[this.SidebarKey]);
- }
+ @observable _featuresFromGeocodeResults: any[] = [];
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected
+ @observable _mapReady = false;
+ @observable _isAnimating: boolean = false;
+ @observable _routeToAnimate: Doc | undefined = undefined;
+ @observable _animationPhase: number = 0;
+ @observable _finishedFlyTo: boolean = false;
+ @observable _frameId: number | null = null;
+ @observable _animationUtility: AnimationUtility | null = null;
+ @observable _settingsOpen: boolean = false;
+ @observable _mapStyle: string = 'mapbox://styles/mapbox/standard';
+ @observable _showTerrain: boolean = true;
+ @observable _currentPopup: PopupInfo | undefined = undefined;
+ @observable _isStreetViewAnimation: boolean = false;
+ @observable _animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+ @observable _animationLineColor: string = '#ffff00';
+ @observable _temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [] };
+ @observable _dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
+ type: 'Feature',
+ properties: {},
+ geometry: { type: 'LineString', coordinates: [] },
+ };
+
+ @observable path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
+ type: 'Feature',
+ geometry: { type: 'LineString', coordinates: [] },
+ properties: {},
+ };
+
// this list contains pushpins and configs
- @computed get allAnnotations() {
- return DocListCast(this.dataDoc[this.annotationKey]);
- }
- @computed get allPushpins() {
- return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
- }
- @computed get allRoutes() {
- return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
+ @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
+ @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
+ @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
+ @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } //prettier-ignore
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
- if (this.routeToAnimate?.routeCoordinates) {
- const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+ if (this._routeToAnimate?.routeCoordinates) {
+ 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 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>;
@@ -147,7 +142,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
feature = {
type: 'Feature',
properties: {
- routeTitle: StrCast(this.routeToAnimate.title),
+ routeTitle: StrCast(this._routeToAnimate.title),
},
geometry: geometry,
};
@@ -158,9 +153,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const fraction = index - startIndex;
const interpolator = d3.interpolateArray(startCoord, endCoord);
-
const interpolatedCoord = interpolator(fraction);
-
const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
geometry = {
@@ -170,14 +163,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
feature = {
type: 'Feature',
properties: {
- routeTitle: StrCast(this.routeToAnimate.title),
+ routeTitle: StrCast(this._routeToAnimate.title),
},
geometry: geometry,
};
}
autorun(() => {
- const animationUtil = this.animationUtility;
+ const animationUtil = this._animationUtility;
const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
const newFeature: Feature<LineString, turf.Properties> = {
type: 'Feature',
@@ -204,11 +197,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
}
@computed get selectedRouteCoordinates(): Position[] {
- let coordinates: Position[] = [];
- if (this.routeToAnimate?.routeCoordinates) {
- coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
- }
- return coordinates;
+ return !this._routeToAnimate?.routeCoordinates ? [] : JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
}
@computed get allRoutesGeoJson(): FeatureCollection {
@@ -233,29 +222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
}
- @computed get SidebarShown() {
- return this.layoutDoc._layout_showSidebar ? true : false;
- }
- @computed get sidebarWidthPercent() {
- return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
- }
- @computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
- }
- @computed get SidebarKey() {
- return this.fieldKey + '_sidebar';
- }
-
componentDidMount() {
this._unmounting = false;
this._props.setContentViewBox?.(this);
}
- _unmounting = false;
- componentWillUnmount(): void {
+ componentWillUnmount() {
this._unmounting = true;
this.deselectPinOrRoute();
- this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
}
@@ -269,7 +243,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
- let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute;
+ 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));
}
@@ -370,10 +344,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const sourceAnchorCreator = action(() => {
const note = this.getAnchor(true);
- if (note && this.selectedPinOrRoute) {
- note.latitude = this.selectedPinOrRoute.latitude;
- note.longitude = this.selectedPinOrRoute.longitude;
- note.map = this.selectedPinOrRoute.map;
+ if (note && this._selectedPinOrRoute) {
+ note.latitude = this._selectedPinOrRoute.latitude;
+ note.longitude = this._selectedPinOrRoute.longitude;
+ note.map = this._selectedPinOrRoute.map;
}
return note as Doc;
});
@@ -399,10 +373,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const createFunc = undoable(
action(() => {
const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
- if (note && this.selectedPinOrRoute) {
- note.latitude = this.selectedPinOrRoute.latitude;
- note.longitude = this.selectedPinOrRoute.longitude;
- note.map = this.selectedPinOrRoute.map;
+ if (note && this._selectedPinOrRoute) {
+ note.latitude = this._selectedPinOrRoute.latitude;
+ note.longitude = this._selectedPinOrRoute.longitude;
+ note.map = this._selectedPinOrRoute.map;
}
}),
'create note annotation'
@@ -423,12 +397,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return false;
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
-
- addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
-
pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
-
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
@@ -439,52 +408,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
savedAnnotations = () => this._savedAnnotations;
- _bingSearchManager: any;
- _bingMap: any;
- get MicrosoftMaps() {
- return (window as any).Microsoft.Maps;
- }
- // uses Bing Search to retrieve lat/lng for a location. eg.,
- // const results = this.geocodeQuery(map.map, 'Philadelphia, PA');
- // to move the map to that location:
- // const location = await this.geocodeQuery(this._bingMap, 'Philadelphia, PA');
- // this._bingMap.current.setView({
- // mapTypeId: this.MicrosoftMaps.MapTypeId.aerial,
- // center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
- // });
- //
- bingGeocode = (map: any, query: string) => {
- return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
- //If search manager is not defined, load the search module.
- if (!this._bingSearchManager) {
- //Create an instance of the search manager and call the geocodeQuery function again.
- this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
- this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
- res(this.bingGeocode(map, query));
- });
- } else {
- this._bingSearchManager.geocode({
- where: query,
- callback: action((r: any) => res(r.results[0].location)),
- errorCallback: (e: any) => reject(),
- });
- }
- });
- };
-
- @observable
- bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
-
- geoDataRequestOptions = {
- entityType: 'PopulatedPlace',
- };
-
- // The pin that is selected
- @observable selectedPinOrRoute: Doc | undefined = undefined;
-
@action
deselectPinOrRoute = () => {
- if (this.selectedPinOrRoute) {
+ if (this._selectedPinOrRoute) {
// // Removes filter
// Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
// Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
@@ -511,79 +437,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
};
- /*
- * Pushpin onclick
- */
- @action
- pushpinClicked = (pinDoc: Doc) => {
- this.deselectPinOrRoute();
- this.selectedPinOrRoute = pinDoc;
- this.bingSearchBarContents = pinDoc.map;
-
- // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
- // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
-
- this.recolorPin(this.selectedPinOrRoute, 'green');
-
- MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
- MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
- MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
- MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
-
- const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
- const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = point.y + this._props.PanelHeight() / 2 + 32;
- const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
- MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
-
- document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
- };
-
- /**
- * Map OnClick
- */
- @action
- mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
- this._props.select(false);
- this.deselectPinOrRoute();
- };
- /*
- * Updates values of layout doc to match the current map
- */
- @action
- mapRecentered = () => {
- if (
- Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || //
- Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7
- ) {
- this.dataDoc.latitude = this._bingMap.current.getCenter().latitude;
- this.dataDoc.longitude = this._bingMap.current.getCenter().longitude;
- this.dataDoc.map = '';
- this.bingSearchBarContents = '';
- }
- this.dataDoc.map_zoom = this._bingMap.current.getZoom();
- };
- /*
- * Updates maptype
- */
- @action
- updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
-
- /*
- * For Bing Maps
- * Called by search button's onClick
- * Finds the geocode of the searched contents and sets location to that location
- **/
- @action
- bingSearch = () => {
- return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
- this.dataDoc.latitude = location.latitude;
- this.dataDoc.longitude = location.longitude;
- this.dataDoc.map_zoom = this._bingMap.current.getZoom();
- this.dataDoc.map = this.bingSearchBarContents;
- });
- };
/*
* Returns doc w/ relevant info
@@ -592,14 +445,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
- config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
- config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
+ text: (StrCast(this._selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
+ config_latitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
+ config_longitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
// config_map_type: StrCast(this.dataDoc.map_type),
- config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
+ config_map: StrCast((existingPin ?? this._selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
layout_unrendered: true,
- mapPin: existingPin ?? this.selectedPinOrRoute,
+ mapPin: existingPin ?? this._selectedPinOrRoute,
annotationOn: this.Document,
});
if (anchor) {
@@ -613,25 +466,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
map_docToPinMap = new Map<Doc, any>();
map_pinHighlighted = new Map<Doc, boolean>();
- /*
- * Input: pin doc
- * Adds MicrosoftMaps Pushpin to the map (render)
- */
- @action
- addPushpin = (pin: Doc) => {
- const pushPin = pin.infoWindowOpen
- ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {})
- : new this.MicrosoftMaps.Pushpin(
- new this.MicrosoftMaps.Location(pin.latitude, pin.longitude)
- // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'}
- );
-
- this._bingMap.current.entities.push(pushPin);
-
- this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
- // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
- this.map_docToPinMap.set(pin, pushPin);
- };
/*
* Input: pin doc
@@ -640,27 +474,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);
- /*
- * Removes pushpin from map render
- */
- deletePushpin = (pinDoc: Doc) => {
- if (!this._unmounting) {
- this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
- }
- this.map_docToPinMap.delete(pinDoc);
- this.selectedPinOrRoute = undefined;
- };
-
@action
deleteSelectedPinOrRoute = undoable(() => {
console.log('deleting');
- if (this.selectedPinOrRoute) {
+ if (this._selectedPinOrRoute) {
// Removes filter
- Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
- Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
- Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
+ Doc.setDocFilter(this.Document, 'latitude', this._selectedPinOrRoute.latitude, 'remove');
+ Doc.setDocFilter(this.Document, 'longitude', this._selectedPinOrRoute.longitude, 'remove');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove');
- this.removePushpinOrRoute(this.selectedPinOrRoute);
+ this.removePushpinOrRoute(this._selectedPinOrRoute);
}
MapAnchorMenu.Instance.fadeOut(true);
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -677,7 +500,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.preventDefault();
MapAnchorMenu.Instance.fadeOut(true);
runInAction(() => {
- this.temporaryRouteSource = {
+ this._temporaryRouteSource = {
type: 'FeatureCollection',
features: [],
};
@@ -688,9 +511,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
centerOnSelectedPin = () => {
- if (this.selectedPinOrRoute) {
+ if (this._selectedPinOrRoute) {
this._mapRef.current?.flyTo({
- center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)],
+ center: [NumCast(this._selectedPinOrRoute.longitude), NumCast(this._selectedPinOrRoute.latitude)],
});
}
// if (this.selectedPin) {
@@ -703,33 +526,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
};
- /**
- * View options for bing maps
- */
- bingViewOptions = {
- // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng },
- zoom: this.dataDoc.latitude ?? 10,
- mapTypeId: 'grayscale',
- };
-
- /**
- * Map options
- */
- bingMapOptions = {
- navigationBarMode: 'square',
- backgroundColor: '#f1f3f4',
- enableInertia: true,
- supportedMapTypes: ['grayscale', 'canvasLight'],
- disableMapTypeSelectorMouseOver: true,
- // showScalebar:true
- // disableRoadView:true,
- // disableBirdseye:true
- streetsideOptions: {
- showProblemReporting: false,
- showCurrentAddress: false,
- },
- };
-
recolorPin = (pin: Doc, color?: string) => {
// this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
// this.map_docToPinMap.delete(pin);
@@ -739,109 +535,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// this.map_docToPinMap.set(pin, newpin);
};
- /*
- * Called when BingMap is first rendered
- * Initializes starting values
- */
- @observable _mapReady = false;
- @action
- bingMapReady = (map: any) => {
- this._mapReady = true;
- this._bingMap = map.map;
- if (!this._bingMap.current) {
- alert('NO Map!?');
- }
- this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick);
- this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change'));
- this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
-
- this._disposers.mapLocation = reaction(
- () => this.Document.map,
- mapLoc => (this.bingSearchBarContents = mapLoc),
- { fireImmediately: true }
- );
- this._disposers.highlight = reaction(
- () => this.allAnnotations.map(doc => doc[Highlight]),
- () => {
- const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
- allConfigPins.forEach(({ doc, pushpin }) => {
- if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
- this.recolorPin(pushpin);
- this.map_pinHighlighted.delete(pushpin);
- }
- });
- allConfigPins.forEach(({ doc, pushpin }) => {
- if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) {
- this.recolorPin(pushpin, 'orange');
- this.map_pinHighlighted.set(pushpin, true);
- }
- });
- },
- { fireImmediately: true }
- );
-
- this._disposers.location = reaction(
- () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
- locationObject => {
- // if (this._bingMap.current)
- try {
- locationObject?.zoom &&
- this._bingMap.current?.setView({
- mapTypeId: locationObject.mapType,
- zoom: locationObject.zoom,
- center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng),
- });
- } catch (e) {
- console.log(e);
- }
- },
- { fireImmediately: true }
- );
- };
-
- dragToggle = (e: React.PointerEvent) => {
- let dragClone: HTMLDivElement | undefined;
-
- setupMoveUpEvents(
- e,
- e,
- e => {
- // move event
- if (!dragClone) {
- dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin
- dragClone.style.position = 'absolute';
- dragClone.style.zIndex = '10000';
- DragManager.Root().appendChild(dragClone); // add clone to root
- }
- dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
- return false;
- },
- e => {
- // up event
- if (!dragClone) return;
- DragManager.Root().removeChild(dragClone);
- let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
- while (target) {
- if (target === this._ref.current) {
- const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
- const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
- const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
- this.createPushpin(location.latitude, location.longitude);
- break;
- }
- target = target.parentElement;
- }
- },
- e => {
- const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
- if (this.bingSearchBarContents) {
- this.bingSearch().then(createPin);
- } else createPin();
- }
- );
- };
-
// incrementer: number = 0;
/*
* Creates Pushpin doc and adds it to the list of annotations
@@ -880,7 +573,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (createPinForDestination) {
this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
}
- this.temporaryRouteSource = {
+ this._temporaryRouteSource = {
type: 'FeatureCollection',
features: [],
};
@@ -890,10 +583,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// TODO: Display error that can't create route to same location
}, 'createmaproute');
- searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
-
- @observable
- featuresFromGeocodeResults: any[] = [];
+ @action
+ searchbarKeyDown = (e: any) => {
+ if (e.key === 'Enter' && this._featuresFromGeocodeResults) {
+ const center = this._featuresFromGeocodeResults[0]?.center;
+ this._featuresFromGeocodeResults = [];
+ setTimeout(() => center && this._mapRef.current?.flyTo({ center }));
+ }
+ };
@action
addMarkerForFeature = (feature: any) => {
@@ -910,7 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
center: feature.center,
});
}
- this.featuresFromGeocodeResults = [];
+ this._featuresFromGeocodeResults = [];
} else {
// TODO: handle error
}
@@ -922,11 +619,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
*/
handleSearchChange = async (searchText: string) => {
const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
- if (features && !this.isAnimating) {
+ if (features && !this._isAnimating) {
runInAction(() => {
- this.settingsOpen = false;
- this.featuresFromGeocodeResults = features;
- this.routeToAnimate = undefined;
+ this._settingsOpen = false;
+ this._featuresFromGeocodeResults = features;
+ this._routeToAnimate = undefined;
});
}
// try {
@@ -946,8 +643,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
handleMapClick = (e: MapLayerMouseEvent) => {
- this.featuresFromGeocodeResults = [];
- this.settingsOpen = false;
+ this._featuresFromGeocodeResults = [];
+ this._settingsOpen = false;
if (this._mapRef.current) {
const features = this._mapRef.current.queryRenderedFeatures(e.point, {
layers: ['map-routes-layer'],
@@ -960,8 +657,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
this.deselectPinOrRoute(); // TODO: Also deselect route if selected
if (routeDoc) {
- this.selectedPinOrRoute = routeDoc;
- Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+ this._selectedPinOrRoute = routeDoc;
+ Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
// TODO: Recolor route
@@ -1008,7 +705,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
if (features) {
runInAction(() => {
- this.featuresFromGeocodeResults = features;
+ this._featuresFromGeocodeResults = features;
});
}
@@ -1028,21 +725,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// }
};
- @observable
- currentPopup: PopupInfo | undefined = undefined;
-
@action
handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
- this.featuresFromGeocodeResults = [];
+ this._featuresFromGeocodeResults = [];
this.deselectPinOrRoute(); // TODO: check this method
- this.selectedPinOrRoute = pinDoc;
+ this._selectedPinOrRoute = pinDoc;
// this.bingSearchBarContents = pinDoc.map;
// Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
// Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
- this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
+ this.recolorPin(this._selectedPinOrRoute, 'green'); // TODO: check this method
MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
@@ -1072,12 +766,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// })
};
- @observable
- temporaryRouteSource: FeatureCollection = {
- type: 'FeatureCollection',
- features: [],
- };
-
@action
displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
if (routeInfoMap) {
@@ -1096,41 +784,23 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
// TODO: Create pin for destination
// TODO: Fly to point where full route will be shown
- this.temporaryRouteSource = newTempRouteSource;
+ this._temporaryRouteSource = newTempRouteSource;
}
};
- @observable
- isAnimating: boolean = false;
-
- @observable
- routeToAnimate: Doc | undefined = undefined;
-
- @observable
- animationPhase: number = 0;
-
- @observable
- finishedFlyTo: boolean = false;
-
@action
setAnimationPhase = (newValue: number) => {
- this.animationPhase = newValue;
+ this._animationPhase = newValue;
};
- @observable
- frameId: number | null = null;
-
@action
setFrameId = (frameId: number) => {
- this.frameId = frameId;
+ this._frameId = frameId;
};
- @observable
- animationUtility: AnimationUtility | null = null;
-
@action
setAnimationUtility = (util: AnimationUtility) => {
- this.animationUtility = util;
+ this._animationUtility = util;
};
@action
@@ -1138,8 +808,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (routeDoc) {
MapAnchorMenu.Instance.fadeOut(true);
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
- this.featuresFromGeocodeResults = [];
- this.routeToAnimate = routeDoc;
+ this._featuresFromGeocodeResults = [];
+ this._routeToAnimate = routeDoc;
}
};
@@ -1161,29 +831,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@computed
get preAnimationViewState() {
- if (!this.isAnimating) {
+ if (!this._isAnimating) {
return this.mapboxMapViewState;
}
}
- @observable
- isStreetViewAnimation: boolean = false;
-
- @observable
- animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
-
- @observable
- animationLineColor: string = '#ffff00';
-
@action
setAnimationLineColor = (color: ColorResult) => {
- this.animationLineColor = color.hex;
+ this._animationLineColor = color.hex;
};
@action
updateAnimationSpeed = () => {
let newAnimationSpeed: AnimationSpeed;
- switch (this.animationSpeed) {
+ switch (this._animationSpeed) {
case AnimationSpeed.SLOW:
newAnimationSpeed = AnimationSpeed.MEDIUM;
break;
@@ -1197,63 +858,33 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
newAnimationSpeed = AnimationSpeed.MEDIUM;
break;
}
- this.animationSpeed = newAnimationSpeed;
- if (this.animationUtility) {
- this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
+ this._animationSpeed = newAnimationSpeed;
+ if (this._animationUtility) {
+ this._animationUtility.updateAnimationSpeed(newAnimationSpeed);
}
};
@computed get animationSpeedTooltipText(): string {
- switch (this.animationSpeed) {
- case AnimationSpeed.SLOW:
- return '1x speed';
- case AnimationSpeed.MEDIUM:
- return '2x speed';
- case AnimationSpeed.FAST:
- return '3x speed';
- default:
- return '2x speed';
- }
+ switch (this._animationSpeed) {
+ case AnimationSpeed.SLOW: return '1x speed';
+ case AnimationSpeed.MEDIUM: return '2x speed';
+ case AnimationSpeed.FAST: return '3x speed';
+ default: return '2x speed';
+ } // prettier-ignore
}
@computed get animationSpeedIcon(): JSX.Element {
- switch (this.animationSpeed) {
- case AnimationSpeed.SLOW:
- return slowSpeedIcon;
- case AnimationSpeed.MEDIUM:
- return mediumSpeedIcon;
- case AnimationSpeed.FAST:
- return fastSpeedIcon;
- default:
- return mediumSpeedIcon;
- }
+ switch (this._animationSpeed) {
+ case AnimationSpeed.SLOW: return slowSpeedIcon;
+ case AnimationSpeed.MEDIUM: return mediumSpeedIcon;
+ case AnimationSpeed.FAST: return fastSpeedIcon;
+ default: return mediumSpeedIcon;
+ } // prettier-ignore
}
@action
toggleIsStreetViewAnimation = () => {
- const newVal = !this.isStreetViewAnimation;
- this.isStreetViewAnimation = newVal;
- if (this.animationUtility) {
- this.animationUtility.updateIsStreetViewAnimation(newVal);
- }
- };
-
- @observable
- dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
- type: 'Feature',
- properties: {},
- geometry: {
- type: 'LineString',
- coordinates: [],
- },
- };
-
- @observable
- path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
- type: 'Feature',
- geometry: {
- type: 'LineString',
- coordinates: [],
- },
- properties: {},
+ const newVal = !this._isStreetViewAnimation;
+ this._isStreetViewAnimation = newVal;
+ this._animationUtility?.updateIsStreetViewAnimation(newVal);
};
getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
@@ -1272,19 +903,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
playAnimation = (status: AnimationStatus) => {
- if (!this._mapRef.current || !this.routeToAnimate) {
+ if (!this._mapRef.current || !this._routeToAnimate) {
return;
}
- this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
- this.frameId = AnimationStatus.RESUME ? this.frameId : null;
- this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
+ this._animationPhase = status === AnimationStatus.RESUME ? this._animationPhase : 0;
+ this._frameId = AnimationStatus.RESUME ? this._frameId : null;
+ this._finishedFlyTo = AnimationStatus.RESUME ? this._finishedFlyTo : false;
const path = turf.lineString(this.selectedRouteCoordinates);
- this.settingsOpen = false;
+ this._settingsOpen = false;
this.path = path;
- this.isAnimating = true;
+ this._isAnimating = true;
runInAction(() => {
return new Promise<void>(async resolve => {
@@ -1293,18 +924,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
lat: this.selectedRouteCoordinates[0][1],
};
- const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current);
- runInAction(() => {
- this.setAnimationUtility(animationUtil);
- });
-
- const updateFrameId = (newFrameId: number) => {
- this.setFrameId(newFrameId);
- };
+ const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current);
+ runInAction(() => this.setAnimationUtility(animationUtil));
- const updateAnimationPhase = (newAnimationPhase: number) => {
- this.setAnimationPhase(newAnimationPhase);
- };
+ const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId);
+ const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);
if (status !== AnimationStatus.RESUME) {
const result = await animationUtil.flyInAndRotate({
@@ -1324,9 +948,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
console.log('Altitude: ', result.altitude);
}
- runInAction(() => {
- this.finishedFlyTo = true;
- });
+ runInAction(() => (this._finishedFlyTo = true));
// follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
await animationUtil.animatePath({
@@ -1335,7 +957,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// startBearing: -20,
// startAltitude: this.isStreetViewAnimation ? 80 : 12000,
// pitch: this.isStreetViewAnimation ? 80: 50,
- currentAnimationPhase: this.animationPhase,
+ currentAnimationPhase: this._animationPhase,
updateAnimationPhase,
updateFrameId,
});
@@ -1353,7 +975,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
});
setTimeout(() => {
- this.isStreetViewAnimation = false;
+ this._isStreetViewAnimation = false;
resolve();
}, 10000);
});
@@ -1362,27 +984,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
pauseAnimation = () => {
- if (this.frameId && this.animationPhase > 0) {
- window.cancelAnimationFrame(this.frameId);
- this.frameId = null;
- this.isAnimating = false;
+ if (this._frameId && this._animationPhase > 0) {
+ window.cancelAnimationFrame(this._frameId);
+ this._frameId = null;
+ this._isAnimating = false;
}
};
@action
stopAnimation = (close: boolean) => {
- if (this.frameId) {
- window.cancelAnimationFrame(this.frameId);
+ if (this._frameId) {
+ window.cancelAnimationFrame(this._frameId);
}
- this.animationPhase = 0;
- this.frameId = null;
- this.finishedFlyTo = false;
- this.isAnimating = false;
+ this._animationPhase = 0;
+ this._frameId = null;
+ this._finishedFlyTo = false;
+ this._isAnimating = false;
if (close) {
- this.animationSpeed = AnimationSpeed.MEDIUM;
- this.isStreetViewAnimation = false;
- this.routeToAnimate = undefined;
- this.animationUtility = null;
+ this._animationSpeed = AnimationSpeed.MEDIUM;
+ this._isStreetViewAnimation = false;
+ this._routeToAnimate = undefined;
+ this._animationUtility = null;
}
};
@@ -1390,21 +1012,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return (
<>
<IconButton
- tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+ tooltip={this._isAnimating && this._finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
onPointerDown={() => {
- if (this.isAnimating && this.finishedFlyTo) {
+ if (this._isAnimating && this._finishedFlyTo) {
this.pauseAnimation();
- } else if (this.animationPhase > 0) {
+ } else if (this._animationPhase > 0) {
this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase
} else {
this.playAnimation(AnimationStatus.START); // Play from the beginning
}
}}
- icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+ icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
color="black"
size={Size.MEDIUM}
/>
- {this.isAnimating && this.finishedFlyTo && (
+ {this._isAnimating && this._finishedFlyTo && (
<IconButton
tooltip="Restart animation"
onPointerDown={() => {
@@ -1420,7 +1042,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<>
<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} />} />
+ <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this._isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
<div id="divider">|</div>
<IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
<div id="divider">|</div>
@@ -1436,26 +1058,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
hideRoute = () => {
- this.temporaryRouteSource = {
+ this._temporaryRouteSource = {
type: 'FeatureCollection',
features: [],
};
};
- @observable
- settingsOpen: boolean = false;
-
- @observable
- mapStyle: string = 'mapbox://styles/mapbox/standard';
-
- @observable
- showTerrain: boolean = true;
-
@action
toggleSettings = () => {
- if (!this.isAnimating && this.animationPhase == 0) {
- this.featuresFromGeocodeResults = [];
- this.settingsOpen = !this.settingsOpen;
+ if (!this._isAnimating && this._animationPhase == 0) {
+ this._featuresFromGeocodeResults = [];
+ this._settingsOpen = !this._settingsOpen;
}
};
@@ -1500,14 +1113,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
onStepZoomChange = (increment: boolean) => {
if (this._mapRef.current) {
- let newZoom: number;
- if (increment) {
- console.log('inc');
- newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1);
- } else {
- console.log('dec');
- newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1);
- }
+ const newZoom = increment //
+ ? Math.min(16, this.mapboxMapViewState.zoom + 1)
+ : Math.max(0, this.mapboxMapViewState.zoom - 1);
+
this._mapRef.current.setZoom(newZoom);
this.dataDoc.map_zoom = newZoom;
}
@@ -1523,7 +1132,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
@action
- toggleShowTerrain = () => (this.showTerrain = !this.showTerrain);
+ toggleShowTerrain = () => (this._showTerrain = !this._showTerrain);
getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
const markerType = StrCast(pinDoc.markerType);
@@ -1532,25 +1141,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
};
- static _firstRender = true;
- static _rerenderDelay = 500;
- _rerenderTimeout: any;
+ _textRef = React.createRef<any>();
render() {
- // bcz: no idea what's going on here, but bings maps have some kind of bug
- // such that we need to delay rendering a second map on startup until the first map is rendered.
- this.Document[DocCss];
- if (MapBox._rerenderDelay) {
- // prettier-ignore
- this._rerenderTimeout = this._rerenderTimeout ??
- setTimeout(action(() => {
- if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
- MapBox._rerenderDelay = 0;
- }
- this._rerenderTimeout = undefined;
- this.Document[DocCss] = this.Document[DocCss] + 1;
- }), MapBox._rerenderDelay);
- return null;
- }
const scale = this._props.NativeDimScaling?.() || 1;
const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
@@ -1560,20 +1152,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<div
className="mapBox-wrapper"
onWheel={e => e.stopPropagation()}
- onPointerDown={async e => {
- e.button === 0 && !e.ctrlKey && 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() }}>
<div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
{renderAnnotations(this.opaqueFilter)}
{SnappingManager.IsDragging ? null : renderAnnotations()}
- {!this.routeToAnimate && (
+ {!this._routeToAnimate && (
<div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
- <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
+ <TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={(e: any) => this.handleSearchChange(e.target.value)} />
<IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+ <div style={{ opacity: 0 }}>
+ <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+ </div>
</div>
)}
- {this.settingsOpen && !this.routeToAnimate && (
+ {this._settingsOpen && !this._routeToAnimate && (
<div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
<div className="mapbox-style-select">
<div>Map Style:</div>
@@ -1605,21 +1198,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
</div>
<div className="mapbox-terrain-selection">
<div>Show terrain: </div>
- <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} />
+ <input type="checkbox" checked={this._showTerrain} onChange={this.toggleShowTerrain} />
</div>
</div>
)}
- {this.routeToAnimate && (
+ {this._routeToAnimate && (
<div className="animation-panel" style={{ width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)` }}>
- <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div>
+ <div id="route-to-animate-title">{StrCast(this._routeToAnimate.title)}</div>
<div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
</div>
)}
- {this.featuresFromGeocodeResults.length > 0 && (
+ {this._featuresFromGeocodeResults.length > 0 && (
<div className="mapbox-geocoding-search-results">
- <React.Fragment>
+ <>
<h4>Choose a location for your pin: </h4>
- {this.featuresFromGeocodeResults
+ {this._featuresFromGeocodeResults
.filter(feature => feature.place_name)
.map((feature, idx) => (
<div
@@ -1632,14 +1225,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<div className="search-result-place-name">{feature.place_name}</div>
</div>
))}
- </React.Fragment>
+ </>
</div>
)}
<MapProvider>
<MapboxMap
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: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
style={{
position: 'absolute',
@@ -1649,20 +1242,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
width: NumCast(this.layoutDoc._width) * parscale,
height: NumCast(this.layoutDoc._height) * parscale,
}}
- initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+ initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState}
onZoom={this.onMapZoom}
onMove={this.onMapMove}
onClick={this.handleMapClick}
onDblClick={this.handleMapDblClick}
- terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+ terrain={this._showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
<Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
- <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} />
+ <Source id="temporary-route" type="geojson" data={this._temporaryRouteSource} />
<Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
<Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
- {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />}
- {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && (
+ {!this._isAnimating && this._animationPhase == 0 && (
+ <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />
+ )}
+ {this._routeToAnimate && (this._isAnimating || this._animationPhase > 0) && (
<>
- {!this.isStreetViewAnimation && (
+ {!this._isStreetViewAnimation && (
<>
<Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
<Layer
@@ -1670,7 +1265,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
type="line"
source="animated-route"
paint={{
- 'line-color': this.animationLineColor,
+ 'line-color': this._animationLineColor,
'line-width': 5,
}}
/>
@@ -1722,10 +1317,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)}
<>
- {!this.isAnimating &&
- this.animationPhase == 0 &&
- this.allPushpins
- // .filter(anno => !anno.layout_unrendered)
+ {!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: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
{this.getMarkerIcon(pushpin)}
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index fc5b4dd18..8ebc90157 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
-import { MapBox } from './MapBox';
+import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer';
/**
* Map Pushpin doc class
@@ -18,7 +18,7 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
get mapBoxView() {
- return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
+ return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBoxContainer;
}
get mapBox() {
return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index e38a42b29..1f976f926 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -65,7 +65,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
@undoBatch
- @action
public static WorkspaceStopRecording() {
const remDoc = RecordingBox.screengrabber?.Document;
if (remDoc) {
@@ -90,7 +89,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @returns
*/
@undoBatch
- @action
public static WorkspaceStartRecording(value: string) {
const screengrabber =
value === 'Record Workspace'
@@ -120,7 +118,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param value RecordingBox rootdoc
*/
@undoBatch
- @action
public static replayWorkspace(value: Doc) {
Doc.UserDoc().currentRecording = value;
value.overlayX = 70;
@@ -138,7 +135,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param value current recordingbox
*/
@undoBatch
- @action
public static addRecToWorkspace(value: RecordingBox) {
let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
(ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
@@ -149,7 +145,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UserDoc().workspaceRecordingState = undefined;
}
- @action
public static resumeWorkspaceReplaying(doc: Doc) {
const docView = DocumentManager.Instance.getDocumentView(doc);
if (docView?.ComponentView instanceof VideoBox) {
@@ -158,7 +153,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UserDoc().workspaceReplayingState = media_state.Playing;
}
- @action
public static pauseWorkspaceReplaying(doc: Doc) {
const docView = DocumentManager.Instance.getDocumentView(doc);
const videoBox = docView?.ComponentView as VideoBox;
@@ -168,7 +162,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UserDoc().workspaceReplayingState = media_state.Paused;
}
- @action
public static stopWorkspaceReplaying(value: Doc) {
Doc.RemFromMyOverlay(value);
Doc.UserDoc().currentRecording = undefined;
@@ -178,7 +171,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@undoBatch
- @action
public static removeWorkspaceReplaying(value: Doc) {
Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value);
Doc.RemFromMyOverlay(value);
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
index 287cccd8f..f2d5a980d 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.scss
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -1,208 +1,200 @@
video {
- // flex: 100%;
- width: 100%;
- // min-height: 400px;
- //height: auto;
- height: 100%;
- //display: block;
- object-fit: cover;
- background-color: black;
-}
-
-button {
- margin: 0 .5rem
+ // flex: 100%;
+ width: 100%;
+ // min-height: 400px;
+ //height: auto;
+ height: 100%;
+ //display: block;
+ object-fit: cover;
+ background-color: black;
}
.recording-container {
- height: 100%;
- width: 100%;
- // display: flex;
- pointer-events: all;
- background-color: black;
+ height: 100%;
+ width: 100%;
+ // display: flex;
+ pointer-events: all;
+ background-color: black;
+ button {
+ margin: 0 0.5rem;
+ }
}
.video-wrapper {
- // max-width: 600px;
- // max-width: 700px;
- // position: relative;
- display: flex;
- justify-content: center;
- // overflow: hidden;
- border-radius: 10px;
- margin: 0;
+ // max-width: 600px;
+ // max-width: 700px;
+ // position: relative;
+ display: flex;
+ justify-content: center;
+ // overflow: hidden;
+ border-radius: 10px;
+ margin: 0;
}
.video-wrapper:hover .controls {
- bottom: 34.5px;
- transform: translateY(0%);
- opacity: 100%;
+ bottom: 34.5px;
+ transform: translateY(0%);
+ opacity: 100%;
}
.controls {
- display: flex;
- align-items: center;
- justify-content: center;
- position: absolute;
- width: 100%;
- flex-wrap: wrap;
- background: rgba(255, 255, 255, 0.25);
- box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
- // backdrop-filter: blur(4px);
- border-radius: 10px;
- border: 1px solid rgba(255, 255, 255, 0.18);
- transition: all 0.3s ease-in-out;
- bottom: 34.5px;
- height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ width: 100%;
+ flex-wrap: wrap;
+ background: rgba(255, 255, 255, 0.25);
+ box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
+ // backdrop-filter: blur(4px);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ transition: all 0.3s ease-in-out;
+ bottom: 34.5px;
+ height: 60px;
}
.controls:active {
- bottom: 40px;
+ bottom: 40px;
}
.actions button {
- background: none;
- border: none;
- outline: none;
- cursor: pointer;
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
}
.actions button i {
- background-color: none;
- color: white;
- font-size: 30px;
+ background-color: none;
+ color: white;
+ font-size: 30px;
}
-
.velocity {
- appearance: none;
- background: none;
- color: white;
- outline: none;
- border: none;
- text-align: center;
- font-size: 16px;
+ appearance: none;
+ background: none;
+ color: white;
+ outline: none;
+ border: none;
+ text-align: center;
+ font-size: 16px;
}
.mute-btn {
- background: none;
- border: none;
- outline: none;
- cursor: pointer;
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
}
.mute-btn i {
- background-color: none;
- color: white;
- font-size: 20px;
+ background-color: none;
+ color: white;
+ font-size: 20px;
}
.recording-sign {
- height: 20px;
- width: auto;
- display: flex;
- flex-direction: row;
- position: absolute;
- top: 10px;
- right: 15px;
- align-items: center;
- justify-content: center;
-
- .timer {
- font-size: 15px;
- color: white;
- margin: 0;
- }
-
- .dot {
- height: 15px;
- width: 15px;
- margin: 5px;
- background-color: red;
- border-radius: 50%;
- display: inline-block;
- }
+ height: 20px;
+ width: auto;
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ align-items: center;
+ justify-content: center;
+
+ .timer {
+ font-size: 15px;
+ color: white;
+ margin: 0;
+ }
+
+ .dot {
+ height: 15px;
+ width: 15px;
+ margin: 5px;
+ background-color: red;
+ border-radius: 50%;
+ display: inline-block;
+ }
}
.controls-inner-container {
- display: flex;
- flex-direction: row;
- position: relative;
- width: 100%;
- align-items: center;
- justify-content: center;
+ display: flex;
+ flex-direction: row;
+ position: relative;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
}
.record-button-wrapper {
- width: 35px;
- height: 35px;
- font-size: 0;
- background-color: grey;
- border: 0px;
- border-radius: 35px;
- margin: 10px;
- display: flex;
- justify-content: center;
-
- .record-button {
- background-color: red;
- border: 0px;
- border-radius: 50%;
- height: 80%;
- width: 80%;
- align-self: center;
- margin: 0;
-
- &:hover {
- height: 85%;
- width: 85%;
- }
- }
-
- .stop-button {
- background-color: red;
- border: 0px;
- border-radius: 10%;
- height: 70%;
- width: 70%;
- align-self: center;
- margin: 0;
-
-
- // &:hover {
- // width: 40px;
- // height: 40px
- // }
- }
-
+ width: 35px;
+ height: 35px;
+ font-size: 0;
+ background-color: grey;
+ border: 0px;
+ border-radius: 35px;
+ margin: 10px;
+ display: flex;
+ justify-content: center;
+
+ .record-button {
+ background-color: red;
+ border: 0px;
+ border-radius: 50%;
+ height: 80%;
+ width: 80%;
+ align-self: center;
+ margin: 0;
+
+ &:hover {
+ height: 85%;
+ width: 85%;
+ }
+ }
+
+ .stop-button {
+ background-color: red;
+ border: 0px;
+ border-radius: 10%;
+ height: 70%;
+ width: 70%;
+ align-self: center;
+ margin: 0;
+
+ // &:hover {
+ // width: 40px;
+ // height: 40px
+ // }
+ }
}
.options-wrapper {
- height: 100%;
- display: flex;
- flex-direction: row;
- align-content: center;
- position: relative;
- top: 0;
- bottom: 0;
-
- &.video-edit-wrapper {
-
- // right: 50% - 15;
-
- .track-screen {
- font-weight: 200;
- }
-
- }
-
- &.track-screen-wrapper {
-
- // right: 50% - 30;
-
- .track-screen {
- font-weight: 200;
- color: aqua;
- }
-
- }
-} \ No newline at end of file
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-content: center;
+ position: relative;
+ top: 0;
+ bottom: 0;
+
+ &.video-edit-wrapper {
+ // right: 50% - 15;
+
+ .track-screen {
+ font-weight: 200;
+ }
+ }
+
+ &.track-screen-wrapper {
+ // right: 50% - 30;
+
+ .track-screen {
+ font-weight: 200;
+ color: aqua;
+ }
+ }
+}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index d9d0dbe3e..8c65fd34e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -670,7 +670,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const definedParameters = !this.compileParams.length ? null : (
<div className="scriptingBox-plist" style={{ width: '30%' }}>
{this.compileParams.map((parameter, i) => (
- <div key={i} className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+ <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<EditableView
display={'block'}
maxHeight={72}
@@ -749,7 +749,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
{!this.compileParams.length || !this.paramsNames ? null : (
<div className="scriptingBox-plist">
{this.paramsNames.map((parameter: string, i: number) => (
- <div key={i} className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+ <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>
{this.paramsTypes[i] === 'boolean' ? this.renderEnum(parameter, [true, false]) : null}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b2ae7201c..4773a21c9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -8,12 +8,13 @@ import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
+import { dropActionType } from '../../util/DragManager';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ReplayMovements } from '../../util/ReplayMovements';
@@ -27,11 +28,10 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { DocumentView } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
-import { dropActionType } from '../../util/DragManager';
/**
* VideoBox
@@ -335,7 +335,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
const link = 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);
+ // 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
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
@@ -343,10 +343,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
- const anchor = addAsAnnotation
- ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
- : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.Document);
+ const anchor =
+ addAsAnnotation && marquee
+ ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
+ : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
return anchor;
};
@@ -373,7 +374,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
getView = (doc: Doc, options: FocusViewOptions) => {
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
- this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ // do we want to always open up the timeline when followin a link? kind of clunky visually
+ //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
options.didMove = true;
}
return this._stackedTimeline.getView(doc, options);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9340edc0..033b01d24 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -34,7 +34,7 @@ import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
import { SidebarAnnos } from '../SidebarAnnos';
import { StyleProp } from '../StyleProvider';
import { DocumentView, OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import { LinkInfo } from './LinkDocPreview';
import { PinProps, PresBox } from './trails';
import './WebBox.scss';
@@ -179,9 +179,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
// bcz: need to make sure that doc.data_annotations points to the currently active web page's annotations (this could/should be when the doc is created)
if (this._url) {
const reqdFuncs: { [key: string]: string } = {};
- reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"])`;
- reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"] = value`;
- reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"sidebar"])`;
+ reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"])`;
+ reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"] = value`;
+ reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_sidebar"])`;
DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
}
});
@@ -341,7 +341,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
savedAnnotationsCreator: () => ObservableMap<number, HTMLDivElement[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
@action
+ iframeMove = (e: PointerEvent) => {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ this._marqueeref.current?.onMove(theclick);
+ };
+ @action
iframeUp = (e: PointerEvent) => {
+ this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+ this.marqueeing = undefined;
this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
this._textAnnotationCreator = undefined;
this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
@@ -358,6 +368,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
+ } else {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+ else {
+ if (!(e.target as any)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.marqueeing = undefined;
+ }
+
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(false);
+ if (e?.button === 2 || e?.altKey) {
+ e?.preventDefault();
+ e?.stopPropagation();
+ setTimeout(() => {
+ // if menu comes up right away, the down event can still be active causing a menu item to be selected
+ this.specificContextMenu(undefined as any);
+ this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+ });
+ }
}
};
@action
@@ -400,6 +433,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
@action
iframeDown = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+ if (sel?.empty)
+ sel.empty(); // Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+
this._props.select(false);
const theclick = this.props
.ScreenToLocalTransform()
@@ -409,6 +448,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
this.marqueeing = theclick;
+ this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+ this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
e.preventDefault();
}
};
@@ -739,28 +780,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.marqueeing = undefined;
}
};
- @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ @action finishMarquee = (x?: number, y?: number) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.marqueeing = undefined;
- this._textAnnotationCreator = undefined;
- const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
- if (sel?.empty)
- sel.empty(); // Chrome
- else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
- if (x !== undefined && y !== undefined) {
- ContextMenu.Instance.closeMenu();
- ContextMenu.Instance.setIgnoreEvents(false);
- if (e?.button === 2 || e?.altKey) {
- e?.preventDefault();
- e?.stopPropagation();
- setTimeout(() => {
- // if menu comes up right away, the down event can still be active causing a menu item to be selected
- this.specificContextMenu(undefined as any);
- this.DocumentView?.().onContextMenu(undefined, x, y);
- });
- }
- }
};
@observable lighttext = false;
@@ -992,6 +1015,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
@@ -1071,6 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
: 'none';
annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 7a0ff8776..d79df4272 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,20 +1,11 @@
@import '../../global/globalCssVariables.module.scss';
+.dashFieldView-active,
.dashFieldView {
position: relative;
display: inline-flex;
align-items: center;
- select {
- display: none;
- }
-
- &:hover {
- select {
- display: unset;
- }
- }
-
.dashFieldView-enumerables {
width: 10px;
height: 10px;
@@ -35,6 +26,7 @@
display: inline-block;
font-weight: normal;
background: rgba(0, 0, 0, 0.1);
+ cursor: default;
}
.dashFieldView-fieldSpan {
min-width: 8px;
@@ -50,6 +42,27 @@
}
}
}
+
+.dashFieldView,
+.dashFieldView-active {
+ .dashFieldView-select {
+ height: 10p;
+ font-size: 12px;
+ background: transparent;
+ opacity: 0;
+ width: 5px;
+ }
+}
+
+.dashFieldView {
+ &:hover {
+ .dashFieldView-select {
+ opacity: unset;
+ width: 15px !important;
+ }
+ }
+}
+
.ProseMirror-selectedNode {
outline: solid 1px $light-blue !important;
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 6186b3d99..17b8b53e7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
@@ -22,27 +22,49 @@ import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import { DocData } from '../../../../fields/DocSymbols';
+import { NodeSelection } from 'prosemirror-state';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
root: any;
node: any;
tbox: FormattedTextBox;
+ getpos: any;
+ @observable _nodeSelected = false;
+ NodeSelected = () => this._nodeSelected;
unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ makeObservable(this);
+ const self = this;
this.node = node;
this.tbox = tbox;
+ this.getpos = getPos;
this.dom = document.createElement('div');
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.onkeypress = function (e: any) {
+ const tBox = this.tbox;
+ this.dom.onkeypress = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeydown = function (e: any) {
+ this.dom.onkeydown = function (e: KeyboardEvent) {
e.stopPropagation();
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const editor = tbox.EditorView;
+ if (editor) {
+ const state = editor.state;
+ for (var i = self.getpos() + 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;
+ }
+ }
+ // tBox.setFocus(state.selection.to);
+ }
+ }
};
this.dom.onkeyup = function (e: any) {
e.stopPropagation();
@@ -62,9 +84,9 @@ export class DashFieldView {
width={node.attrs.width}
height={node.attrs.height}
hideKey={node.attrs.hideKey}
+ hideValue={node.attrs.hideValue}
editable={node.attrs.editable}
- expanded={node.attrs.expanded}
- dataDoc={node.attrs.dataDoc}
+ nodeSelected={this.NodeSelected}
tbox={tbox}
/>
);
@@ -77,9 +99,11 @@ export class DashFieldView {
});
}
deselectNode() {
+ runInAction(() => (this._nodeSelected = false));
this.dom.classList.remove('ProseMirror-selectednode');
}
selectNode() {
+ setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
this.dom.classList.add('ProseMirror-selectednode');
}
}
@@ -88,12 +112,12 @@ interface IDashFieldViewInternal {
fieldKey: string;
docId: string;
hideKey: boolean;
+ hideValue: boolean;
tbox: FormattedTextBox;
width: number;
height: number;
editable: boolean;
- expanded: boolean;
- dataDoc: boolean;
+ nodeSelected: () => boolean;
node: any;
getPos: any;
unclickable: () => boolean;
@@ -106,14 +130,14 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
_fieldKey: string;
_fieldRef = React.createRef<HTMLDivElement>();
@observable _dashDoc: Doc | undefined = undefined;
- @observable _expanded = this._props.expanded;
+ @observable _expanded = this._props.nodeSelected();
constructor(props: IDashFieldViewInternal) {
super(props);
makeObservable(this);
this._fieldKey = this._props.fieldKey;
this._textBoxDoc = this._props.tbox.Document;
- const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc);
+ const setDoc = action((doc: Doc) => (this._dashDoc = doc));
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -132,22 +156,29 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
componentWillUnmount() {
this._reactionDisposer?.();
}
- isRowActive = () => this._expanded && this._props.editable;
- finishEdit = action(() => (this._expanded = false));
- selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
- selectedCells = () => [this._dashDoc!];
+ isRowActive = () => (this._props.nodeSelected() || this._expanded) && this._props.editable;
+ finishEdit = action(() => {
+ if (this._expanded) {
+ this._expanded = false;
+ // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus
+ // the timeout allows switching focus from one dashFieldView to another in the same text box
+ setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
+ }
+ });
+ selectedCells = () => (this._dashDoc ? [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
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
- <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
+ <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
<SchemaTableCell
Document={this._dashDoc}
col={0}
deselectCell={emptyFunction}
selectCell={emptyFunction}
maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
- columnWidth={returnZero}
+ columnWidth={this._expanded || this._props.nodeSelected() ? this.columnWidth : returnZero}
selectedCells={this.selectedCells}
selectedCol={returnZero}
fieldKey={this._fieldKey}
@@ -158,10 +189,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
setColumnValues={returnFalse}
setSelectedColumnValues={returnFalse}
allowCRs={true}
- oneLine={!this._expanded}
+ oneLine={!this._expanded && !this._props.nodeSelected()}
finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
+ autoFocus={true}
+ rootSelected={this._props.tbox._props.rootSelected}
/>
</div>
);
@@ -185,31 +218,49 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
};
toggleFieldHide = undoable(
- action(() => this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])),
+ action(() => {
+ const editor = this._props.tbox.EditorView!;
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
+ }),
'hideKey'
);
+ toggleValueHide = undoable(
+ action(() => {
+ const editor = this._props.tbox.EditorView!;
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
+ }),
+ 'hideValue'
+ );
+
@computed get _hideKey() {
- return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey'];
+ return this._props.hideKey && !this._expanded;
+ }
+
+ @computed get _hideValue() {
+ return this._props.hideValue && !this._props.nodeSelected();
}
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
- onPointerDownLabelSpan = (e: any) => {
+ onPointerDownLabelSpan = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
+ DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+ const editor = this._props.tbox.EditorView!;
+ setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
});
};
@undoBatch
selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
- event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value);
+ event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value === '-unset-' ? undefined : event.target.value);
};
@computed get values() {
- if (this._props.expanded) return [];
+ if (this._props.nodeSelected()) return [];
const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
return vals.strings.map(facet => ({ value: facet, label: facet }));
@@ -218,21 +269,22 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
render() {
return (
<div
- className="dashFieldView"
+ className={`dashFieldView${this.isRowActive() ? '-active' : ''}`}
ref={this._fieldRef}
style={{
width: this._props.width,
height: this._props.height,
pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
- {this._props.hideKey || this._hideKey ? null : (
+ {this._hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
{(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
</span>
)}
- {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
{!this.values.length ? null : (
- <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+ <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 => (
<option value={val.value}>{val.label}</option>
))}
@@ -247,6 +299,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: DashFieldViewMenu;
static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
static toggleFieldHide: () => void = emptyFunction;
+ static toggleValueHide: () => void = emptyFunction;
constructor(props: any) {
super(props);
DashFieldViewMenu.Instance = this;
@@ -260,6 +313,10 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
DashFieldViewMenu.toggleFieldHide();
DashFieldViewMenu.Instance.fadeOut(true);
};
+ toggleValueHide = (e: React.MouseEvent) => {
+ DashFieldViewMenu.toggleValueHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
@observable _fieldKey = '';
@@ -275,14 +332,27 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
};
render() {
return this.getElement(
- <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.showFields}>
- <FontAwesomeIcon icon="eye" size="lg" />
- </button>
- <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- </Tooltip>
+ <>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="sm" />
+ </button>
+ </Tooltip>
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+ <FontAwesomeIcon icon="bullseye" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+ <FontAwesomeIcon icon="hashtag" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ </>
);
}
}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b786c5ffb..b90653acc 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -8,6 +8,7 @@ import { StrCast } from '../../../../fields/Types';
import './DashFieldView.scss';
import EquationEditor from './EquationEditor';
import { FormattedTextBox } from './FormattedTextBox';
+import { DocData } from '../../../../fields/DocSymbols';
export class EquationView {
dom: HTMLDivElement; // container for label and value
@@ -88,7 +89,6 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
}
e.stopPropagation();
}}
- onKeyPress={e => e.stopPropagation()}
style={{
position: 'relative',
display: 'inline-block',
@@ -96,12 +96,11 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
height: this.props.height,
background: 'white',
borderRadius: '10%',
- bottom: 3,
}}>
<EquationEditor
ref={this._ref}
- value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
- onChange={(str: any) => (this._textBoxDoc[this._fieldKey] = str)}
+ value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
+ onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
spaceBehavesLikeTab={true}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index cf48e1250..b327e5137 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -23,6 +23,7 @@ export class FootnoteView {
this.dom = document.createElement('footnote');
this.dom.addEventListener('pointerup', this.toggle, true);
+ this.dom.addEventListener('mouseup', (e: MouseEvent) => e.stopPropagation(), true);
// These are used when the footnote is selected
this.innerView = null;
}
@@ -82,9 +83,10 @@ export class FootnoteView {
document.removeEventListener('pointerup', this.ignore, true);
};
- toggle = () => {
+ toggle = (e: PointerEvent) => {
if (this.innerView) this.close();
else this.open();
+ e.stopPropagation();
};
close() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 03ff0436b..38dd2e847 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -273,6 +273,7 @@ footnote::before {
height: 20px;
&::before {
content: '→';
+ cursor: default;
}
&:hover {
background: orange;
@@ -348,6 +349,8 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
+ background-color: inherit;
+ display: contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
}
blockquote {
@@ -397,6 +400,7 @@ footnote::before {
font-family: inherit;
}
margin-left: 0;
+ background-color: inherit;
}
.decimal2-ol {
counter-reset: deci2;
@@ -406,6 +410,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.1em;
+ background-color: inherit;
}
.decimal3-ol {
counter-reset: deci3;
@@ -415,6 +420,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.85em;
+ background-color: inherit;
}
.decimal4-ol {
counter-reset: deci4;
@@ -424,6 +430,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 3.85em;
+ background-color: inherit;
}
.decimal5-ol {
counter-reset: deci5;
@@ -432,6 +439,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.decimal6-ol {
counter-reset: deci6;
@@ -440,6 +448,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.decimal7-ol {
counter-reset: deci7;
@@ -448,6 +457,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.multi1-ol {
@@ -458,6 +468,7 @@ footnote::before {
}
margin-left: 0;
padding-left: 1.2em;
+ background-color: inherit;
}
.multi2-ol {
counter-reset: multi2;
@@ -467,6 +478,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2em;
+ background-color: inherit;
}
.multi3-ol {
counter-reset: multi3;
@@ -476,6 +488,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.85em;
+ background-color: inherit;
}
.multi4-ol {
counter-reset: multi4;
@@ -485,6 +498,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 3.85em;
+ background-color: inherit;
}
//.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
@@ -788,6 +802,7 @@ footnote::before {
height: 20px;
&::before {
content: '→';
+ cursor: default;
}
&:hover {
background: orange;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2b48494f2..43010b2ed 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
@@ -21,7 +21,7 @@ import { List } from '../../../../fields/List';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
@@ -68,8 +68,13 @@ import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
// import * as applyDevTools from 'prosemirror-dev-tools';
+
+interface FormattedTextBoxProps extends FieldViewProps {
+ onBlur?: () => void; // callback when text loses focus
+ autoFocus?: boolean; // whether text should get input focus when created
+}
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -86,7 +91,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _sidebarRef = React.createRef<SidebarAnnos>();
private _sidebarTagRef = React.createRef<React.Component>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
- private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _scrollRef: HTMLDivElement | null = null;
private _editorView: Opt<EditorView>;
public _applyingChange: string = '';
private _inDrop = false;
@@ -99,7 +104,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _ignoreScroll = false;
- private _hadDownFocus = false;
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
@@ -200,7 +204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
}
- constructor(props: FieldViewProps) {
+ constructor(props: FormattedTextBoxProps) {
super(props);
makeObservable(this);
FormattedTextBox.Instance = this;
@@ -242,8 +246,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- if (!pinProps && this._editorView?.state.selection.empty) return this.Document;
- const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
+ const rootDoc = 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 });
this.addDocument(anchor);
this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
@@ -286,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -321,8 +326,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
leafText = (node: Node) => {
if (node.type === this._editorView?.state.schema.nodes.dashField) {
- const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
- return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field);
+ const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (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 Field))
+ );
}
return '';
};
@@ -337,12 +346,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
tryUpdateDoc = (force: boolean) => {
if (this._editorView && (this._editorView as any).docView) {
const state = this._editorView.state;
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const dataDoc = this.dataDoc;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+ const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
const effectiveAcl = GetEffectiveAcl(dataDoc);
const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, '');
@@ -359,25 +369,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
let unchanged = true;
- if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
+ const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes
+ if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
this._applyingChange = this.fieldKey;
- const textChange = newText !== prevData?.Text;
textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
- if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
+ if ((!prevData && !protoData && !layoutData) || newText || (!newText && !protoData && !layoutData)) {
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
+ if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
- dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined;
+ dataDoc[this.fieldKey] =
+ numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined;
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
- dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
unchanged = false;
}
} else {
// if we've deleted all the text in a note driven by a template, then restore the template data
dataDoc[this.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data)));
- dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
@@ -443,16 +452,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.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))) ||
+ (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+ link.link_relationship === LinkManager.AutoKeywords
+ ); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
+
const t = this._editorView.state.selection.to;
var tr = this._editorView.state.tr as any;
const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -462,7 +478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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
- (title.startsWith('-') || title.startsWith('@')) &&
+ title.startsWith('-') &&
this._editorView &&
!this.dataDoc.title_custom &&
(Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
@@ -470,14 +486,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let node = this._editorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
- const prefix = str.startsWith('@') ? '' : '-';
+ const prefix = '-';
const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
- if (str.startsWith('@') && str.length > 1) {
- Doc.AddToMyPublished(this.Document);
- }
}
}
};
@@ -494,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
- const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
+ const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
var alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
if (
@@ -617,7 +630,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
docId: draggedDoc[Id],
float: 'unset',
});
- if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
+ if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
added = dragData.removeDocument?.(draggedDoc) ? true : false;
} else {
added = true;
@@ -871,7 +884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.layoutDoc._headerHeight = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+ setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
}),
icon: 'eye',
});
@@ -944,6 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
this._props.renderDepth &&
@@ -969,10 +983,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
animateRes = (resIndex: number, newText: string) => {
if (resIndex < newText.length) {
const marks = this._editorView?.state.storedMarks ?? [];
- this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
- setTimeout(() => {
- this.animateRes(resIndex + 1, newText);
- }, 20);
+ this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
+ setTimeout(() => this.animateRes(resIndex + 1, newText), 20);
}
};
@@ -980,13 +992,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
try {
let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
- console.error('GPT call failed');
this.animateRes(0, 'Something went wrong.');
- } else {
- this.animateRes(0, res);
+ } else if (this._editorView) {
+ const { dispatch, state } = this._editorView;
+ // for no animation, use: dispatch(state.tr.insertText(res));
+ // for animted response starting at end of text, use:
+ dispatch(state.tr.setSelection(Selection.atEnd(state.doc)));
+ this.animateRes(0, '\n\n' + res);
}
} catch (err) {
- console.error('GPT call failed');
+ console.error(err);
this.animateRes(0, 'Something went wrong.');
}
});
@@ -1006,7 +1021,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const state = this._editorView.state;
const to = state.selection.to;
const updated = TextSelection.create(state.doc, to, to);
- this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
+ this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
if (this._recordingDictation) {
this.recordDictation();
}
@@ -1230,9 +1245,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
this._disposers.editorState = reaction(
() => {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc;
- return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) };
+ const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey];
+ 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 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)) };
},
incomingValue => {
if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1242,11 +1263,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
this.tryUpdateScrollHeight();
}
- } else {
+ } else if (this._editorView.state.doc.textContent !== incomingValue?.str) {
selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
}
}
- }
+ },
+ { fireImmediately: true }
);
this._disposers.search = reaction(
@@ -1284,25 +1306,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
if (this._recordingDictation) setTimeout(this.recordDictation);
}
- var quickScroll: string | undefined = '';
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
pos => {
- if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
- const viewTrans = quickScroll ?? StrCast(this.Document._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;
- if (duration) {
- this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
- } else {
- this._scrollRef.current.scrollTo({ top: pos });
- }
+ if (!this._ignoreScroll && this._scrollRef) {
+ const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]+)(m?)s/);
+ const duration = Number(durationStr?.[1]) * (durationStr?.[2] ? 1 : 1000);
+ this._scrollStopper = smoothScroll(duration || 0, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
}
},
{ fireImmediately: true }
);
- quickScroll = undefined;
this.tryUpdateScrollHeight();
setTimeout(this.tryUpdateScrollHeight, 250);
}
@@ -1342,7 +1356,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
if (pdfAnchor instanceof Doc) {
const dashField = view.state.schema.nodes.paragraph.create({}, [
- view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [
+ view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false }, undefined, [
view.state.schema.marks.linkAnchor.create({
allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
title: StrCast(pdfAnchor.title),
@@ -1394,14 +1408,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
const viewRect = self._ref.current!.getBoundingClientRect();
- const scrollRef = self._scrollRef.current;
+ const scrollRef = self._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 * self.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
- scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1471,6 +1485,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
+ 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
}
}
if (selectOnLoad) {
@@ -1483,6 +1498,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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.
}
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -1555,7 +1571,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
(this.ProseRef?.children?.[0] as any).focus();
}
}
- this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
@@ -1564,23 +1579,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
document.removeEventListener('pointerup', this.onSelectEnd);
};
onPointerUp = (e: React.PointerEvent): void => {
- const editor = this._editorView!;
- const state = editor?.state;
- if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime) && !this._hadDownFocus) {
- (this.ProseRef?.children[0] as HTMLElement)?.blur?.();
- }
- if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
- if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- else if (this._props.isContentActive() && !e.button) {
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- let xpos = pcords?.pos || 0;
- while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
- xpos = xpos - 1;
- }
- editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
+ const state = this.EditorView?.state;
+ if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
+ if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
}
};
@action
@@ -1601,10 +1606,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e.stopPropagation();
}
};
- setFocus = () => {
- const pos = this._editorView?.state.selection.$from.pos || 1;
- (this.ProseRef?.children?.[0] as any).focus();
- setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+ setFocus = (ipos?: number) => {
+ const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1);
+ setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100);
+ setTimeout(() => (this.ProseRef?.children?.[0] as any).focus(), 200);
};
@action
onFocused = (e: React.FocusEvent): void => {
@@ -1656,10 +1661,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
- let olistPos = clickPos?.pos;
+ const clickPosVal = clickPos?.pos || 1;
+ let olistPos = clickPosVal;
if (clickPos && olistPos && this._props.rootSelected?.()) {
- const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
- const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
+ const clickNode = this._editorView?.state.doc.resolve(olistPos).node();
+ const nodeBef = this._editorView?.state.doc.resolve(Math.max(0, olistPos - 1)).node();
olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
let $olistPos = this._editorView?.state.doc.resolve(olistPos);
let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
@@ -1669,18 +1675,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
$olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
}
}
- const listPos = this._editorView?.state.doc.resolve(clickPos.pos);
- const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
+ const maxSize = this._editorView?.state.doc.content.size ?? 0;
+ const listPos = this._editorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal));
+ const listNode = listPos?.node();
if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
if (!highlightOnly) {
if (selectOrderedList) {
this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
} else {
- const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
- this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
+ const nodePos = clickPosVal - (olistPos === clickPosVal ? 0 : 1);
+ if (this._editorView.state.doc.nodeAt(nodePos)) {
+ const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
+ this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
+ }
}
}
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' });
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'gray' });
}
}
}
@@ -1697,33 +1707,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
const stordMarks = this._editorView?.state.storedMarks?.slice();
- this.autoLink();
- if (this._editorView?.state.tr) {
- const tr = stordMarks?.reduce((tr, m) => {
- tr.addStoredMark(m);
- return tr;
- }, this._editorView.state.tr);
- tr && this._editorView.dispatch(tr);
+ if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
+ this.autoLink();
+ if (this._editorView?.state.tr) {
+ const tr = stordMarks?.reduce((tr, m) => {
+ tr.addStoredMark(m);
+ return tr;
+ }, this._editorView.state.tr);
+ tr && this._editorView.dispatch(tr);
+ }
}
}
if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
+
+ // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
+ const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+ if (match) {
+ this.dataDoc.title_custom = true;
+ this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
+ this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
+ }
+
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
- UndoManager.RunInBatch(() => {
- this.dataDoc.title_custom = true;
- this.dataDoc.layout_showTitle = 'title';
- const tr = this._editorView!.state.tr;
- this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection());
- }, 'titler');
- }
+ // if the text box blurs and none of its contents are focused(), then pass the blur along
+ setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
};
onKeyDown = (e: React.KeyboardEvent) => {
@@ -1756,7 +1771,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
- RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
@@ -1769,7 +1784,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
default:
if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
case ' ':
- if (e.code !== 'Space') {
+ if (e.code !== 'Space' && e.code !== 'Backspace') {
[AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
@@ -1783,28 +1798,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
};
onScroll = (e: React.UIEvent) => {
- if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
- if (!this._props.dontSelectOnLoad) {
- this._ignoreScroll = true;
- this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
- this._ignoreScroll = false;
- e.stopPropagation();
- e.preventDefault();
- }
+ if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
+ this._ignoreScroll = true;
+ this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
+ this._ignoreScroll = false;
+ e.stopPropagation();
+ e.preventDefault();
}
};
tryUpdateScrollHeight = () => {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children && !SnappingManager.IsDragging) {
- const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
- const toHgt = (node: Element) => {
+ const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
+ const toNum = (val: string) => Number(val.replace('px', ''));
+ const toHgt = (node: Element): number => {
const { height, marginTop, marginBottom } = getComputedStyle(node);
- return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
+ const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height);
+ return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
};
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
+ const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children);
const scrollHeight = this.ProseRef && proseHeight;
- if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
+ 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);
@@ -1986,11 +2001,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// 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 styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ const styleFromLayoutString = Doc.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(styleFromLayoutString.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ !Number.isNaN(height) &&
+ (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
(this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
@@ -2018,12 +2034,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ const styleFromLayoutString = Doc.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 styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox"
- onPointerEnter={action(() => (this._isHovering = true))}
- onPointerLeave={action(() => (this._isHovering = false))}
+ onPointerEnter={action(() => {
+ this._isHovering = true;
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
+ })}
+ onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
ref={r => {
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = r;
@@ -2065,9 +2084,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onDoubleClick={this.onDoubleClick}>
<div
className="formattedTextBox-outer"
- ref={this._scrollRef}
+ ref={r => (this._scrollRef = r)}
style={{
- width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2084,8 +2103,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}}
/>
</div>
- {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+ {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 47527847b..03c902580 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,7 +1,7 @@
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 { splitListItem, wrapInList } from 'prosemirror-schema-list';
+import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
@@ -11,8 +11,8 @@ import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
-import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
import { Doc } from '../../../../fields/Doc';
+import { EditorView } from 'prosemirror-view';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -89,7 +89,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
- const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ const marks = state.storedMarks || state.selection.$to.parentOffset ? state.selection.$from.marks() : undefined;
if (
!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
@@ -102,11 +102,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
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) => {
- const tx3 = updateBullets(tx2, schema);
+ const tx25 = updateBullets(tx2, schema);
+ const ol_node = tx25.doc.nodeAt(range!.start)!;
+ const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
+ const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2)));
+ dispatch(tx4);
})
) {
console.log('bullet promote fail');
@@ -120,7 +123,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (
- !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+ !liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -164,12 +167,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
SelectionManager.DeselectAll();
});
- const splitMetadata = (marks: any, tx: Transaction) => {
- marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- return tx;
- };
-
bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
@@ -260,7 +257,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
@@ -272,6 +269,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!joinBackward(state, (tx: Transaction) => {
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, (tx: Transaction) => view.dispatch(tx));
+ }
})
) {
if (
@@ -284,59 +285,80 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
}
return true;
- });
+ };
+ bind('Backspace', backspace);
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
- bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+
+ const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
- const path = (state.selection.$from as any).path;
- const depth = trange ? liftTarget(trange) : undefined;
- const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item;
- if (split && trange && depth !== undefined && depth !== null) {
+ const depth = trange ? liftTarget(trange) : null;
+ if (
+ depth !== null &&
+ state.selection.$from.node(-1)?.type === state.schema.nodes.blockquote && //
+ !state.selection.$from.node().content.size &&
+ trange
+ ) {
dispatch(state.tr.lift(trange, depth) as any);
return true;
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- const cr = state.selection.$from.node().textContent.endsWith('\n');
- if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
- if (
+ if (!newlineInCode(state, dispatch as any)) {
+ 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)
+ {
+ // 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++);
+ } else if (
!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
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));
})
) {
- const fromattrs = state.selection.$from.node().attrs;
- if (
- !splitBlockKeepMarks(state, (tx3: Transaction) => {
- 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);
- splitMetadata(marks, tx4);
- if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) {
+ 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);
+ } else {
+ const fromattrs = state.selection.$from.node().attrs;
+ if (
+ !splitBlockKeepMarks(state, (tx3: Transaction) => {
+ 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);
dispatch(tx4);
}
- } else dispatch(tx3.insertText('\r\n'));
- })
- ) {
- return false;
+
+ if (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);
+ }
+ })
+ ) {
+ return false;
+ }
}
}
}
return true;
- });
+ };
+ bind('Enter', enter);
//Command to create a blank space
bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
- const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- dispatch(splitMetadata(marks, state.tr));
return false;
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index bee0d72e3..cecf106a3 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -27,7 +27,10 @@ const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
- @observable static Instance: RichTextMenu;
+ static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
+ static get Instance() {
+ return RichTextMenu._instance?.menu;
+ }
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
@@ -48,7 +51,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _activeFontSize: string = '13px';
@observable private _activeFontFamily: string = '';
- @observable private activeListType: string = '';
+ @observable private _activeListType: string = '';
@observable private _activeAlignment: string = 'left';
@observable private brushMarks: Set<Mark> = new Set();
@@ -67,7 +70,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
constructor(props: AntimodeMenuProps) {
super(props);
makeObservable(this);
- RichTextMenu.Instance = this;
+ RichTextMenu._instance.menu = this;
this.updateMenu(undefined, undefined, props, this.layoutDoc);
this._canFade = false;
this.Pinned = true;
@@ -100,6 +103,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get fontSize() {
return this._activeFontSize;
}
+ @computed get listStyle() {
+ return this._activeListType;
+ }
@computed get textAlign() {
return this._activeAlignment;
}
@@ -131,11 +137,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
}
- // update active marks
- const activeMarks = this.getActiveMarksOnSelection();
- this.setActiveMarkButtons(activeMarks);
-
- // update active font family and size
+ this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
const activeFamilies = active.activeFamilies;
const activeSizes = active.activeSizes;
@@ -144,7 +146,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
- this.activeListType = this.getActiveListStyle();
+ this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
@@ -161,17 +163,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const newPos = nodeOl ? numberRange(state.selection.from).findIndex(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) : state.selection.from;
+ const fromRange = numberRange(state.selection.from).reverse();
+ const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
- if (node?.type === schema.nodes.ordered_list) {
- let attrs = node.attrs;
- if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
- if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
- if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
- const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to))));
- }
- {
+ if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
+ const hasMark = node.marks.some(m => m.type === mark.type);
+ const otherMarks = node.marks.filter(m => m.type !== mark.type);
+ const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
+ const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
+ dispatch(updateBullets(markup, state.schema));
+ } else {
const state = this.view?.state;
const tr = this.view?.state.tr;
if (tr && state) {
@@ -201,16 +202,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// finds font sizes and families in selection
getActiveListStyle() {
- if (this.view && this.TextView?._props.rootSelected?.()) {
- const path = (this.view.state.selection.$from as any).path;
- for (let i = 0; i < path.length; i += 3) {
- if (path[i].type === this.view.state.schema.nodes.ordered_list) {
- return path[i].attrs.mapStyle;
+ const state = this.view?.state;
+ if (state) {
+ const pos = state.selection.$anchor;
+ for (let i = 0; i < pos.depth; i++) {
+ const node = pos.node(i);
+ if (node.type === schema.nodes.ordered_list) {
+ return node.attrs.mapStyle;
}
}
- if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) {
- return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
- }
}
return '';
}
@@ -224,11 +224,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.view && this.TextView?._props.rootSelected?.()) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
- const marks: Mark[] = [...(state.storedMarks ?? [])];
+ var marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
} else if (state.selection.empty) {
- const ref_node = this.reference_node(pos);
- marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
+ for (let i = 0; i <= pos.depth; i++) {
+ marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
+ }
} else {
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
@@ -255,41 +256,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
- let activeMarks: MarkType[] = [];
- if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks;
+ if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
- //current selection
- const { empty, ranges, $to } = this.view.state.selection as TextSelection;
const state = this.view.state;
- if (!empty) {
- activeMarks = markGroup.filter(mark => {
- const has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
- }
- return false;
- });
- } else {
- const pos = this.view.state.selection.$from;
- const ref_node: ProsNode | null = this.reference_node(pos);
- if (ref_node !== null && ref_node !== this.view.state.doc) {
- if (ref_node.isText) {
- } else {
- return [];
- }
- activeMarks = markGroup.filter(mark_type => {
- // if (mark_type === state.schema.marks.pFontSize) {
- // return mark.isINSet
- // ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
- // }
- const mark = state.schema.mark(mark_type);
- return mark.isInSet(ref_node.marks);
- });
+ var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const pos = this.view.state.selection.$from;
+ if (state.storedMarks !== null) {
+ } else if (state.selection.empty) {
+ for (let i = 0; i <= pos.depth; i++) {
+ marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
+ } else {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
+ });
}
- return activeMarks;
+ const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+ return markGroup.filter(mark_type => {
+ const mark = state.schema.mark(mark_type);
+ return mark.isInSet(marks);
+ });
}
@action
@@ -318,16 +304,20 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
});
}
- elideSelection = () => {
- const state = this.view?.state;
- if (!state) return;
- if (state.selection.empty) return false;
+ elideSelection = (txstate: EditorState | undefined = undefined, visibility = false) => {
+ const state = txstate ?? this.view?.state;
+ if (!state || state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
- const tr = state.tr;
- 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() });
- this.view?.dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+ const tr = state.tr.addMark(state.tr.selection.from, state.selection.to, mark);
+ const text = tr.selection.content();
+ const elideNode = state.schema.nodes.summary.create({ visibility, text, textslice: text.toJSON() });
+ const summary = tr.replaceSelectionWith(elideNode).removeMark(tr.selection.from - 1, tr.selection.from, mark);
+ const expanded = () => {
+ const endOfElidableText = summary.selection.to + text.content.size;
+ const res = summary.insert(summary.selection.to, text.content).insert(endOfElidableText, state.schema.nodes.paragraph.create({}));
+ return res.setSelection(new TextSelection(res.doc.resolve(endOfElidableText + 1)));
+ };
+ this.view?.dispatch?.(visibility ? expanded() : summary);
return true;
};
@@ -366,7 +356,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setFontSize = (fontSize: string) => {
if (this.view) {
if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
- this.TextView.dataDoc.fontSize = fontSize;
+ this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
this.view.focus();
} else {
const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
@@ -381,7 +371,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setFontFamily = (family: string) => {
if (this.view) {
- const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
+ const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
} else if (SelectionManager.Views.length) {
@@ -413,40 +403,24 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
- const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
- const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
- if (!this.view || nodeType?.attrs.mapStyle === '') return;
-
- const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
- let inList: any = undefined;
- let fromList = -1;
- const path: any = Array.from((this.view.state.selection.$from as any).path);
- for (let i = 0; i < path.length; i++) {
- if (path[i]?.type === schema.nodes.ordered_list) {
- inList = path[i];
- fromList = path[i - 1];
- }
- }
+ const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
+ const newMapStyle = active === mapStyle ? '' : mapStyle;
+ if (!this.view || newMapStyle === '') return;
+ let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (
- inList ||
+ if (inList) {
+ const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+ this.view.dispatch(tx2);
+ } else
!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- this.view!.dispatch(tx2);
- })
- ) {
- const tx2 = this.view.state.tr;
- if (nodeType && (inList || nextIsOL)) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to);
+ const tx3 = updateBullets(tx2, schema, newMapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- this.view.dispatch(tx3);
- }
- }
+ this.view!.dispatch(tx3);
+ });
this.view.focus();
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
@@ -561,7 +535,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// todo: add brushes to brushMap to save with a style name
onBrushNameKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
- RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+ RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
this._brushNameRef.current!.style.background = 'lightGray';
}
};
@@ -569,7 +543,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@action
clearBrush() {
- RichTextMenu.Instance.brushMarks = new Set();
+ RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set());
}
@action
@@ -705,118 +679,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- linkExtend($start: ResolvedPos, href: string) {
- const mark = this.view!.state.schema.marks.linkAnchor;
-
- let startIndex = $start.index();
- let endIndex = $start.indexAfter();
-
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
-
- let startPos = $start.start();
- let endPos = startPos;
- for (let i = 0; i < endIndex; i++) {
- const size = $start.parent.child(i).nodeSize;
- if (i < startIndex) startPos += size;
- endPos += size;
- }
- return { from: startPos, to: endPos };
- }
-
- reference_node(pos: ResolvedPos): ProsNode | null {
- if (!this.view) return null;
-
- let ref_node: ProsNode = this.view.state.doc;
- if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
- ref_node = pos.nodeBefore;
- }
- if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) {
- ref_node = pos.nodeAfter;
- }
- }
- if (!ref_node && pos.pos > 0) {
- let skip = false;
- for (let i: number = pos.pos - 1; i > 0; i--) {
- this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
- if (node.isLeaf && !skip) {
- ref_node = node;
- skip = true;
- }
- });
- }
- }
- if (!ref_node.isLeaf && ref_node.childCount > 0) {
- ref_node = ref_node.child(0);
- }
- return ref_node;
- }
-
render() {
return null;
- // TraceMobx();
- // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
- // //!this.collapsed ? this.getDragger() : (null),
- // // !this.Pinned ? (null) : <div key="frag1"> {[
- // // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- // // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- // // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- // // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- // // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- // // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- // // <div className="richTextMenu-divider" key="divider" />
- // // ]}</div>,
- // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- // this.createColorButton(),
- // this.createHighlighterButton(),
- // this.createLinkButton(),
- // this.createBrushButton(),
- // <div className="collectionMenu-divider" key="divider 2" />,
- // this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
- // this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
- // this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
- // this.createButton("indent", "Inset More", undefined, this.insetParagraph),
- // this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
- // this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
- // this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
- // ]}</div>;
-
- // const row2 = <div className="antimodeMenu-row row-2" key="row2">
- // {this.collapsed ? this.getDragger() : (null)}
- // <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
- // <div className="collectionMenu-divider" key="divider 3" />
- // {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
- // this.activeFontSize = val;
- // SelectionManager.Views.map(dv => dv.Document._text_fontSize = val);
- // })),
- // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
- // this.activeFontFamily = val;
- // SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val);
- // })),
- // <div className="collectionMenu-divider" key="divider 4" />,
- // this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
- // this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
- // this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
- // this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
- // ]}
- // </div>
- // {/* <div key="collapser">
- // {<div key="collapser">
- // <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
- // <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
- // </button>
- // </div> }
- // <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
- // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- // </button>
- // </div> */}
- // </div>;
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index b97141e92..42665830f 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,6 +1,6 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { Doc, FieldResult, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
@@ -137,6 +137,7 @@ export class RichTextRules {
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.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
@@ -243,10 +244,19 @@ export class RichTextRules {
}),
// activate a style by name using prefix '%<color name>'
- new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
+ new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
- const marks = RichTextMenu.Instance._brushMap.get(color);
-
+ const marks = RichTextMenu.Instance?._brushMap.get(color);
+
+ if (
+ DocListCast((Doc.UserDoc().template_notes as Doc).data)
+ .concat(DocListCast((Doc.UserDoc().template_user as Doc).data))
+ .map(d => StrCast(d.title))
+ .includes(color)
+ ) {
+ setTimeout(() => this.TextBox.DocumentView?.().switchViews(true, color, undefined, true));
+ return state.tr.deleteRange(start, end);
+ }
if (marks) {
const tr = state.tr.deleteRange(start, end);
return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
@@ -285,8 +295,8 @@ export class RichTextRules {
// create a hyperlink to a titled document
// @(<doctitle>)
- new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@:\.\? \-0-9]+)\)/), (state, match, start, end) => {
- const docTitle = match[2];
+ new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+ const docTitle = match[1];
const prefixLength = '@('.length;
if (docTitle) {
const linkToDoc = (target: Doc) => {
@@ -307,7 +317,7 @@ export class RichTextRules {
};
const getTitledDoc = (docTitle: string) => {
if (!DocServer.FindDocByTitle(docTitle)) {
- Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+ Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
}
const titledDoc = DocServer.FindDocByTitle(docTitle);
return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
@@ -325,19 +335,14 @@ export class RichTextRules {
// [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
// [@{this,doctitle,}.fieldKey]
new InputRule(
- new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+ new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
(state, match, start, end) => {
const docTitle = match[1].substring(1).replace(/\.$/, '');
const fieldKey = match[2];
const assign = match[4] === ':' ? (match[4] = '') : match[4];
const value = match[5];
const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
- }
- return DocServer.FindDocByTitle(docTitle);
- };
+ const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
// if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
@@ -346,44 +351,50 @@ export class RichTextRules {
} else if (value) {
KeyValueBox.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 ); // prettier-ignore
+ if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
}
const target = docTitle ? getTitledDoc(docTitle) : undefined;
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc });
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false });
return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
},
{ inCode: true }
),
- new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
+ // pass the contents between '((' and '))' to chatGPT and append the result
+ new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => {
var count = 0; // ignore first return value which will be the notation that chat is pending a result
KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
- count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
+ if (count) {
+ 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);
+ }
count++;
});
return null;
}),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // wiki:title
- new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
- const title = match[1];
+ // @(wiki:title)
+ new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+ const title = match[1].trim().replace(/ /g, '_');
this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
const fstate = this.TextBox.EditorView?.state;
if (fstate) {
- const tr = fstate?.tr.deleteRange(start, start + 5);
- return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+ const tr = fstate?.tr.deleteRange(start, start + '@(wiki:'.length);
+ return tr.setSelection(new TextSelection(tr.doc.resolve(end - '@(wiki:'.length))).insertText(' ');
}
return state.tr;
}),
// create an inline equation node
- // eq:<equation>>
- new InputRule(new RegExp(/%eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => {
+ // %eq
+ new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
const fieldKey = 'math' + Utils.GenerateGuid();
- this.TextBox.dataDoc[fieldKey] = match[1];
+ this.TextBox.dataDoc[fieldKey] = 'y=';
const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
return tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1)));
}),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index b68acc8f8..ccf7de4a1 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -235,22 +235,6 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
- metadata: {
- toDOM() {
- return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
- },
- },
- metadataKey: {
- toDOM() {
- return ['span', { style: 'font-style:italic; ' }];
- },
- },
- metadataVal: {
- toDOM() {
- return ['span'];
- },
- },
-
summarizeInclusive: {
parseDOM: [
{
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 905146ee2..62b8b03d6 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -24,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec The top level document node.
doc: {
content: 'block+',
+ marks: '_',
},
paragraph: ParagraphNodeSpec,
@@ -120,7 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = {
...ParagraphNodeSpec.attrs,
level: { default: 1 },
},
- defining: true,
parseDOM: [
{ tag: 'h1', attrs: { level: 1 } },
{ tag: 'h2', attrs: { level: 2 } },
@@ -131,8 +131,7 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node) {
const dom = toParagraphDOM(node) as any;
- const level = node.attrs.level || 1;
- dom[0] = 'h' + level;
+ dom[0] = `h${node.attrs.level || 1}`;
return dom;
},
getAttrs(dom: any) {
@@ -264,9 +263,8 @@ export const nodes: { [index: string]: NodeSpec } = {
fieldKey: { default: '' },
docId: { default: '' },
hideKey: { default: false },
+ hideValue: { default: false },
editable: { default: true },
- expanded: { default: null },
- dataDoc: { default: false },
},
leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
group: 'inline',
@@ -332,12 +330,10 @@ export const nodes: { [index: string]: NodeSpec } = {
...orderedList,
content: 'list_item+',
group: 'block',
+ marks: '_',
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
- fontColor: { default: 'inherit' },
- fontSize: { default: undefined },
- fontFamily: { default: undefined },
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet",
visibility: { default: true },
indent: { default: undefined },
},
@@ -377,9 +373,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
- const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
- const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -387,7 +384,7 @@ export const nodes: { [index: string]: NodeSpec } = {
{
'data-mapStyle': node.attrs.mapStyle,
'data-bulletStyle': node.attrs.bulletStyle,
- style: `${fsize} ${ffam} ${fcol} ${marg}`,
+ style: `${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
},
0,
];
@@ -399,7 +396,7 @@ export const nodes: { [index: string]: NodeSpec } = {
class: `${map}-ol`,
'data-mapStyle': node.attrs.mapStyle,
'data-bulletStyle': node.attrs.bulletStyle,
- style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+ style: `list-style: none; ${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
},
0,
]
@@ -423,16 +420,22 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM(node: Node) {
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',
- { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+ { class: `${map}`, style: `${fhigh} ${fsize} ${ffam} ${fcol} `, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
node.attrs.visibility
? 0
: [
'span',
- { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+ {
+ style: `${fhigh} ${fsize} ${ffam} ${fcol} position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre`,
+ },
`${node.firstChild?.textContent}...`,
],
];
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 87e1b69c3..a485ea4c3 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -13,7 +13,7 @@ import { DocumentManager } from '../../../util/DocumentManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { OpenWhereMod } from '../DocumentView';
-import { ImageBox } from '../ImageBox';
+import { ImageBox, ImageEditorData } from '../ImageBox';
import './GenerativeFill.scss';
import Buttons from './GenerativeFillButtons';
import { BrushHandler } from './generativeFillUtils/BrushHandler';
@@ -419,8 +419,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// Closes the editor view
const handleViewClose = () => {
- ImageBox.setImageEditorOpen(false);
- ImageBox.setImageEditorSource('');
+ ImageEditorData.Open = false;
+ ImageEditorData.Source = '';
if (newCollectionRef.current) {
DocumentManager.Instance.AddViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e34144fae..91fdb90fc 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -18,6 +18,7 @@ import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
+import { dropActionType } from '../../../util/DragManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
import { SerializationHelper } from '../../../util/SerializationHelper';
@@ -32,11 +33,10 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import { dropActionType } from '../../../util/DragManager';
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -324,8 +324,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 2: Last slide and presLoop is toggled ON or it is in Edit mode
this.nextSlide(0);
progressiveReveal(true); // shows first progressive document, but without a transition effect
+ return 0;
}
- return 0;
+ return false;
}
return this.itemIndex;
};
@@ -703,7 +704,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (pinProps.pinData.temporal) {
pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
- pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
+ pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
}
}
if (pinProps?.pinViewport) {
@@ -963,7 +964,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const func = () => {
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()) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
+ if (this.next() === false) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
this.layoutDoc.presentation_status === PresStatus.Autoplay && func();
}, delay);
};
@@ -1065,7 +1066,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
} else if (doc.type !== DocumentType.PRES) {
if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
- doc.presentation_targetDoc = doc.createdFrom; // dropped document will be a new embedding of an embedded document somewhere else.
+ doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else.
doc.presentation_movement = PresMovement.Zoom;
if (this._expandBoolean) doc.presentation_expandInlineButton = true;
}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 5b2aa1cde..28139eb14 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -61,7 +61,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Since this node is being rendered with a template, this method retrieves
// the actual slide being rendered from the auto-generated rendering template
@computed get slideDoc() {
- return this._props.TemplateDataDocument ?? this.Document;
+ return DocCast(this.Document.rootDocument, this.Document);
}
// this is the document in the workspaces that is targeted by the slide
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 59f191af0..0b3ba81d3 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -6,7 +6,6 @@ import * as React from 'react';
import { ColorResult } from 'react-color';
import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
@@ -73,18 +72,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* @param e pointer down event
*/
gptSummarize = async (e: React.PointerEvent) => {
- // move this logic to gptpopup, need to implement generate again
- GPTPopup.Instance.setVisible(true);
- GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
- GPTPopup.Instance.setLoading(true);
-
- try {
- const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
- GPTPopup.Instance.setText(res || 'Something went wrong.');
- } catch (err) {
- console.error(err);
- }
- GPTPopup.Instance.setLoading(false);
+ GPTPopup.Instance?.setSelectedText(this.selectedText);
+ GPTPopup.Instance.generateSummary();
};
pointerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index da8a88803..29b1ca365 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, Type } from 'browndash-components';
+import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -10,7 +10,7 @@ import { Utils } from '../../../../Utils';
import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { Networking } from '../../../Network';
-import { gptImageCall } from '../../../apis/gpt/GPT';
+import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
import { DocUtils, Docs } from '../../../documents/Documents';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { AnchorMenu } from '../AnchorMenu';
@@ -20,6 +20,7 @@ export enum GPTPopupMode {
SUMMARY,
EDIT,
IMAGE,
+ DATA,
}
interface GPTPopupProps {}
@@ -27,6 +28,7 @@ interface GPTPopupProps {}
@observer
export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
static Instance: GPTPopup;
+ @observable private chatMode: boolean = false;
@observable
public visible: boolean = false;
@@ -46,6 +48,20 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
public setText = (text: string) => {
this.text = text;
};
+ @observable
+ public selectedText: string = '';
+ @action
+ public setSelectedText = (text: string) => {
+ this.selectedText = text;
+ };
+ @observable
+ public dataJson: string = '';
+ public dataChatPrompt: string | null = null;
+ @action
+ public setDataJson = (text: string) => {
+ if (text=="") this.dataChatPrompt = "";
+ this.dataJson = text;
+ };
@observable
public imgDesc: string = '';
@@ -79,6 +95,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
@action
public setDone = (done: boolean) => {
this.done = done;
+ this.chatMode = false;
};
// change what can be a ref into a ref
@@ -118,18 +135,46 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
try {
let image_urls = await gptImageCall(this.imgDesc);
+ console.log('Image urls: ', image_urls);
if (image_urls && image_urls[0]) {
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+ console.log('Upload result: ', result);
const source = Utils.prepend(result.accessPaths.agnostic.client);
+ console.log('Upload source: ', source);
this.setImgUrls([[image_urls[0], source]]);
}
} catch (err) {
- console.log(err);
- return '';
+ console.error(err);
}
this.setLoading(false);
};
+ generateSummary = async () => {
+ GPTPopup.Instance.setVisible(true);
+ GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
+ GPTPopup.Instance.setLoading(true);
+
+ try {
+ const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
+ } catch (err) {
+ console.error(err);
+ }
+ GPTPopup.Instance.setLoading(false);
+ }
+
+ generateDataAnalysis = async () => {
+ GPTPopup.Instance.setVisible(true);
+ GPTPopup.Instance.setLoading(true);
+ try {
+ let res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
+ } catch (err) {
+ console.error(err);
+ }
+ GPTPopup.Instance.setLoading(false);
+ }
+
/**
* Transfers the summarization text to a sidebar annotation text document.
*/
@@ -174,6 +219,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' });
};
+ /**
+ * Creates a chatbox for analyzing data so that users can ask specific questions.
+ */
+ private chatWithAI = () => {
+ this.chatMode = true;
+ }
+ dataPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.dataChatPrompt = e.target.value;
+ });
+
private getPreviewUrl = (source: string) => source.split('.').join('_m.');
constructor(props: GPTPopupProps) {
@@ -213,31 +268,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
);
};
- data = () => {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
- {this.heading('GENERATED IMAGE')}
- <div className="image-content-wrapper">
- {this.imgUrls.map(rawSrc => (
- <div className="img-wrapper">
- <div className="img-container">
- <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
- </div>
- <div className="btn-container">
- <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
- </div>
- </div>
- ))}
- </div>
- {!this.loading && (
- <>
- <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
- </>
- )}
- </div>
- );
- };
-
summaryBox = () => (
<>
<div>
@@ -255,7 +285,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}, 500);
},
]}
- //cursor={{ hideWhenDone: true }}
/>
) : (
this.text
@@ -266,7 +295,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
<div className="btns-wrapper">
{this.done ? (
<>
- <IconButton tooltip="Generate Again" onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+ <IconButton tooltip="Generate Again" onClick={this.generateSummary} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
<Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
</>
) : (
@@ -288,6 +317,65 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
</>
);
+ dataAnalysisBox = () => (
+ <>
+ <div>
+ {this.heading("ANALYSIS")}
+ <div className="content-wrapper">
+ {!this.loading &&
+ (!this.done ? (
+ <TypeAnimation
+ speed={50}
+ sequence={[
+ this.text,
+ () => {
+ setTimeout(() => {
+ this.setDone(true);
+ }, 500);
+ },
+ ]}
+ />
+ ) : (
+ this.text
+ ))}
+ </div>
+ </div>
+ {!this.loading && (
+ <div className="btns-wrapper">
+ {this.done?
+ this.chatMode?(
+ <input
+ defaultValue=""
+ autoComplete="off"
+ onChange={this.dataPromptChanged}
+ onKeyDown={e => {
+ e.key === 'Enter' ? this.generateDataAnalysis() : null;
+ e.stopPropagation();
+ }}
+ type="text"
+ placeholder="Ask GPT a question about the data..."
+ id="search-input"
+ className="searchBox-input"
+ style={{width: "100%"}}
+ />
+ )
+ :(
+ <>
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().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.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT}/>
+ </div>
+ )}
+ </div>
+ )}
+ </>
+ );
+
aiWarning = () =>
this.done ? (
<div className="ai-warning">
@@ -308,7 +396,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
render() {
return (
<div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
- {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
+ {this.mode === GPTPopupMode.SUMMARY? this.summaryBox() : this.mode === GPTPopupMode.DATA? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
</div>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a582f8004..aaff2a342 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.379/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
interface IViewerProps extends FieldViewProps {
pdfBox: PDFBox;
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index addad2bbc..eab33114e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -3,6 +3,7 @@ import { Button, IconButton, isDark, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { Flip } from 'react-awesome-reveal';
import { FaBug } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../fields/Doc';
import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
@@ -11,6 +12,7 @@ import { GetEffectiveAcl } from '../../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
+import { dropActionType } from '../../util/DragManager';
import { PingManager } from '../../util/PingManager';
import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
@@ -23,11 +25,9 @@ import { CollectionLinearView } from '../collections/collectionLinear';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider } from '../StyleProvider';
import './TopBar.scss';
-import { dropActionType } from '../../util/DragManager';
-import { Flip } from 'react-awesome-reveal';
-import { ObservableReactComponent } from '../ObservableReactComponent';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user