From e30da29f28f6b3ce71928c6c910e2d1a408cd560 Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 22 Feb 2023 19:04:29 -0500 Subject: added simple AI image generation proof of concept --- .../views/nodes/formattedText/FormattedTextBox.tsx | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 619c59f0e..652971298 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -63,6 +63,7 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); +import { Configuration, OpenAIApi } from 'openai'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; @@ -838,12 +839,57 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; + generateImage = async () => { + console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text); + const newDoc = Docs.Create.ImageDocument("https://media.tenor.com/ffyK3EANQyYAAAAd/loading-chemical.gif", { + x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, + y: NumCast(this.rootDoc.y), + _height: 200, + _width: 200 + }) + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createImage({ + prompt: (this.dataDoc.text as RichTextField)?.Text, + n: 1, + size: "1024x1024", + }); + let image_url = response.data.data[0].url; + console.log(image_url); + if (image_url) { + // const newDoc = Docs.Create.ImageDocument(); + if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + newDoc.overlayX = this.rootDoc.x; + newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + } else { + this.props.addDocument?.(newDoc); + } + setTimeout(() => { + newDoc.data = image_url; + newDoc._height = 200; + newDoc._width = 200; + DocUtils.MakeLink({doc: this.rootDoc}, {doc: newDoc}, "Dall-E"); + }, 500) + } + } catch (err) { + console.log(err); + return ''; + } + + } + + breakupDictation = () => { if (this._editorView && this._recording) { this.stopDictation(true); -- cgit v1.2.3-70-g09d2 From fba9b8e68e19e65e9823c5538b7b8b25baeb4670 Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 22 Feb 2023 19:24:41 -0500 Subject: fixes --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 652971298..683ccb9c4 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -848,12 +848,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text); - const newDoc = Docs.Create.ImageDocument("https://media.tenor.com/ffyK3EANQyYAAAAd/loading-chemical.gif", { - x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, - y: NumCast(this.rootDoc.y), - _height: 200, - _width: 200 - }) try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, @@ -867,7 +861,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Mon, 27 Feb 2023 16:56:02 -0500 Subject: feat: added summarization functionality to WebBox and cleaned up code --- src/client/apis/gpt/Summarization.ts | 20 +++- src/client/views/MainView.tsx | 1 + src/client/views/nodes/PDFBox.tsx | 1 + src/client/views/nodes/WebBox.tsx | 6 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/client/views/pdf/AnchorMenu.tsx | 39 ++++--- src/client/views/pdf/GPTPopup.scss | 91 ++++++++++++++-- src/client/views/pdf/GPTPopup.tsx | 115 +++++++++++++++++++-- src/client/views/pdf/PDFViewer.tsx | 19 ++-- 9 files changed, 243 insertions(+), 50 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index 931e0e48f..c0dcc0267 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -1,22 +1,32 @@ import { Configuration, OpenAIApi } from 'openai'; +/** + * Summarizes the inputted text with OpenAI. + * + * @param text Text to summarize + * @returns Summary of text + */ const gptSummarize = async (text: string) => { text += '.'; + const model = 'text-curie-001'; // Most advanced: text-davinci-003 + const maxTokens = 200; + const temp = 0.8; + try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, }); const openai = new OpenAIApi(configuration); const response = await openai.createCompletion({ - model: 'text-curie-001', - max_tokens: 256, - temperature: 0.7, - prompt: `Summarize this text in one sentence: ${text}`, + model: model, + max_tokens: maxTokens, + temperature: temp, + prompt: `Summarize this text: ${text}`, }); return response.data.choices[0].text; } catch (err) { console.log(err); - return ''; + return 'Error connecting with API.'; } }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index da64e7c12..d7603cf5a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -245,6 +245,7 @@ export class MainView extends React.Component { library.add( ...[ + fa.faExclamationCircle, fa.faEdit, fa.faTrash, fa.faTrashAlt, diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b88ac113e..40c04c08f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -485,6 +485,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); + // Changing which document to add the annotation to (the currently selected WebBox) + GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}-${this._urlHash}-sidebar`); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } }; @@ -780,6 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + console.log(annotationKey); (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url)); return this.addDocument(doc, annotationKey); }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 683ccb9c4..b9327db0d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -888,7 +888,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this._editorView && this._recording) { this.stopDictation(true); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 6bcfbe4c2..8f9261614 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -45,7 +45,7 @@ export class AnchorMenu extends AntimodeMenu { @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; - // GPT additions (flow 2) + // GPT additions @observable private summarizedText: string = ''; @observable private loadingSummary: boolean = false; @observable private showGPTPopup: boolean = false; @@ -63,7 +63,7 @@ export class AnchorMenu extends AntimodeMenu { }; private selectedText: string = ''; - setSelectedText = (txt: string) => { + public setSelectedText = (txt: string) => { this.selectedText = txt; }; @@ -71,9 +71,6 @@ export class AnchorMenu extends AntimodeMenu { public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; - public OnSummary: (e: PointerEvent) => Promise = () => { - return new Promise(() => {}); - }; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; @@ -123,14 +120,27 @@ export class AnchorMenu extends AntimodeMenu { ); } - getGPTSummary = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e)); + /** + * Returns a mock summary simulating an API call. + * @returns A Promise that resolves into a string + */ + mockSummarize = async (): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Mock summary. This is a summary of the highlighted text.'); + }, 1000); + }); }; + /** + * Invokes the API with the selected text and stores it in the summarized text. + * @param e pointer down event + */ invokeGPT = async (e: React.PointerEvent) => { this.setGPTPopupVis(true); this.setLoading(true); const res = await gptSummarize(this.selectedText); + // const res = await this.mockSummarize(); if (res) { this.setSummarizedText(res); } else { @@ -243,12 +253,15 @@ export class AnchorMenu extends AntimodeMenu { - Summarize with AI}> - - - + {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + Summarize with AI}> + + + )} + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation}> + + + ) : ( +
+ + + +
+ )} + + )} + {this.summaryDone && ( +
+ + AI generated responses can contain inaccurate or misleading content.{' '} + + Learn More + +
)} ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 324f31f23..408ba07d1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -25,6 +25,7 @@ import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); import { gptSummarize } from '../../apis/gpt/Summarization'; +import { GPTPopup } from './GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -41,6 +42,7 @@ interface IViewerProps extends FieldViewProps { fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; + sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void); @@ -83,19 +85,6 @@ export class PDFViewer extends React.Component { return AnchorMenu.Instance?.GetAnchor; } - // Fields for using GPT to summarize selected text - private _summaryText: string = ''; - setSummaryText = async () => { - try { - const summary = await gptSummarize(this.selectionText()); - this._summaryText = `Summary: ${summary}`; - } catch (err) { - console.log(err); - this._summaryText = 'Failed to fetch summary.'; - } - }; - summaryText = () => this._summaryText; - selectionText = () => this._selectionText; selectionContent = () => this._selectionContent; @@ -435,6 +424,10 @@ export class PDFViewer extends React.Component { this.createTextAnnotation(sel, sel.getRangeAt(0)); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } + + // Changing which document to add the annotation to (the currently selected PDF) + GPTPopup.Instance.setSidebarId('data-sidebar'); + GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; @action -- cgit v1.2.3-70-g09d2 From f189ce6f25b91fcd402b7e81ba8ed378e39e6142 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 1 Mar 2023 23:33:01 -0500 Subject: Added text completion --- src/client/apis/gpt/Summarization.ts | 45 ++++--- .../nodes/formattedText/FormattedTextBox.scss | 4 + .../views/nodes/formattedText/FormattedTextBox.tsx | 129 ++++++++++++++++----- src/client/views/pdf/AnchorMenu.tsx | 26 ++++- src/client/views/pdf/PDFViewer.tsx | 1 - 5 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index ba98ad591..b65736237 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -1,27 +1,41 @@ import { Configuration, OpenAIApi } from 'openai'; +enum GPTCallType { + SUMMARY = 'summary', + COMPLETION = 'completion', +} + +type GPTCallOpts = { + model: string; + maxTokens: number; + temp: number; + prompt: string; +}; + +const callTypeMap: { [type: string]: GPTCallOpts } = { + summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' }, + completion: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: '' }, +}; + /** - * Summarizes the inputted text with OpenAI. - * - * @param text Text to summarize - * @returns Summary of text + * Calls the OpenAI API. + * + * @param inputText Text to process + * @returns AI Output */ -const gptSummarize = async (text: string) => { - text += '.'; - const model = 'text-curie-001'; // Most advanced: text-davinci-003 - const maxTokens = 200; - const temp = 0.5; - +const gptAPICall = async (inputText: string, callType: GPTCallType) => { + if (callType === GPTCallType.SUMMARY) inputText += '.'; + const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, }); const openai = new OpenAIApi(configuration); const response = await openai.createCompletion({ - model: model, - max_tokens: maxTokens, - temperature: temp, - prompt: `Summarize this text: ${text}`, + model: opts.model, + max_tokens: opts.maxTokens, + temperature: opts.temp, + prompt: `${opts.prompt}${inputText}`, }); return response.data.choices[0].text; } catch (err) { @@ -30,4 +44,5 @@ const gptSummarize = async (text: string) => { } }; -export { gptSummarize }; + +export { gptAPICall, GPTCallType}; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index cbe0a465d..fd7fbb333 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -149,6 +149,10 @@ audiotag:hover { } } +.gpt-typing-wrapper { + padding: 10px; +} + // .menuicon { // display: inline-block; // border-right: 1px solid rgba(0, 0, 0, 0.2); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b9327db0d..35c845deb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,12 +64,21 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { Configuration, OpenAIApi } from 'openai'; +import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; +enum GPTStatus { + LOADING, + TYPING, + NONE, +} + @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { @@ -172,6 +181,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + this.gptStatus = status; + }; + public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not @@ -840,14 +862,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); + optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; + mockGPT = async (): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Mock GPT Call.'); + }, 2000); + }); + }; + + askGPT = action(async () => { + try { + this.gptPrompt = (this.dataDoc.text as RichTextField)?.Text; + this.setGPTStatus(GPTStatus.LOADING); + let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); + // let res = await this.mockGPT(); + if (res) { + this.gptRes = res; + this.setGPTStatus(GPTStatus.TYPING); + } else { + this.setGPTStatus(GPTStatus.NONE); + } + } catch (err) { + console.log(err); + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong'; + this.setGPTStatus(GPTStatus.NONE); + } + }); + + setGPTText = action(() => { + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes; + this.gptRes = ''; + this.setGPTStatus(GPTStatus.NONE); + }); + generateImage = async () => { - console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text); + console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, @@ -856,17 +912,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this._editorView && this._recording) { @@ -1942,29 +1997,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent -
+ {this.gptStatus === GPTStatus.NONE || this.gptStatus === GPTStatus.LOADING ? (
-
+ onScroll={this.onScroll} + onDrop={this.ondrop}> +
+
+ ) : ( +
+
{this.gptPrompt}
+
+ { + this.setGPTText(); + }}> +
{this.gptRes}
+
+
+ )} {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} {this.audioHandle} diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 8f9261614..04904b3b1 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,7 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; -import { gptSummarize } from '../../apis/gpt/Summarization'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization'; import { GPTPopup } from './GPTPopup'; import './AnchorMenu.scss'; @@ -136,16 +136,17 @@ export class AnchorMenu extends AntimodeMenu { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - invokeGPT = async (e: React.PointerEvent) => { + gptSummarize = async (e: React.PointerEvent) => { this.setGPTPopupVis(true); this.setLoading(true); - const res = await gptSummarize(this.selectedText); + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); // const res = await this.mockSummarize(); if (res) { this.setSummarizedText(res); } else { this.setSummarizedText('Something went wrong.'); } + this.setLoading(false); }; @@ -243,6 +244,19 @@ export class AnchorMenu extends AntimodeMenu { this.highlightColor = Utils.colorString(col); }; + /** + * Returns whether the selected text can be summarized. The goal is to have + * all selected text available to summarize but its only supported for pdf and web ATM. + * @returns Whether the GPT icon for summarization should appear + */ + canSummarize = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs[0].type === 'pdf' || docs[0].type === 'web'; + } + return false; + }; + render() { const buttons = this.Status === 'marquee' ? ( @@ -254,14 +268,14 @@ export class AnchorMenu extends AntimodeMenu { {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} - {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( Summarize with AI
}> -
)} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation}> )} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation}> )} + {this.canEdit() && ( + Edit text with AI}> + + + )} Find document to link to selected text}> - - - ) : ( -
- - - -
- )} - - )} - {this.summaryDone && ( -
- - AI generated responses can contain inaccurate or misleading content.{' '} - - Learn More - -
- )} - - ); - } -} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss new file mode 100644 index 000000000..50fbe5211 --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -0,0 +1,132 @@ +$textgrey: #707070; +$lighttextgrey: #a3a3a3; +$greyborder: #d3d3d3; +$lightgrey: #ececec; +$button: #5b97ff; +$highlightedText: #82e0ff; + +.summary-box { + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; + position: fixed; + bottom: 5px; + right: 5px; + width: 250px; + min-height: 200px; + border-radius: 15px; + padding: 15px; + padding-bottom: 0; + z-index: 999; + + .summary-heading { + display: flex; + align-items: center; + border-bottom: 1px solid $greyborder; + padding-bottom: 5px; + + .summary-text { + font-size: 12px; + font-weight: 500; + } + } + + label { + color: $textgrey; + font-size: 12px; + font-weight: 400; + letter-spacing: 1px; + margin: 0; + padding-right: 5px; + } + + a { + cursor: pointer; + } + + .content-wrapper { + padding-top: 10px; + min-height: 50px; + max-height: 150px; + overflow-y: auto; + } + + .btns-wrapper { + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + + .summarizing { + display: flex; + align-items: center; + } + } + + button { + font-size: 9px; + padding: 10px; + color: #ffffff; + background-color: $button; + border-radius: 5px; + } + + .text-btn { + &:hover { + background-color: $button; + } + } + + .btn-secondary { + font-size: 8px; + padding: 10px 5px; + background-color: $lightgrey; + color: $textgrey; + &:hover { + background-color: $lightgrey; + } + } + + .icon-btn { + background-color: #ffffff; + padding: 10px; + border-radius: 50%; + color: $button; + border: 1px solid $button; + } + + .ai-warning { + padding: 10px 0; + font-size: 10px; + color: $lighttextgrey; + border-top: 1px solid $greyborder; + } + + .highlighted-text { + background-color: $highlightedText; + } +} + +// Typist CSS +.Typist .Cursor { + display: inline-block; +} +.Typist .Cursor--blinking { + opacity: 1; + animation: blink 1s linear infinite; +} + +@keyframes blink { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx new file mode 100644 index 000000000..91bc0a7ff --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -0,0 +1,186 @@ +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; +import { Doc } from '../../../../fields/Doc'; +import { Docs } from '../../../documents/Documents'; +import './GPTPopup.scss'; + +export enum GPTPopupMode { + SUMMARY, + EDIT, +} + +interface GPTPopupProps { + visible: boolean; + text: string; + loading: boolean; + mode: GPTPopupMode; + callSummaryApi: (e: React.PointerEvent) => Promise; + callEditApi: (e: React.PointerEvent) => Promise; + replaceText: (replacement: string) => void; + highlightRange?: number[]; +} + +@observer +export class GPTPopup extends React.Component { + static Instance: GPTPopup; + + @observable + private done: boolean = false; + @observable + private sidebarId: string = ''; + + @action + public setDone = (done: boolean) => { + this.done = done; + }; + @action + public setSidebarId = (id: string) => { + this.sidebarId = id; + }; + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + + /** + * Transfers the summarization text to a sidebar annotation text document. + */ + private transferToText = () => { + const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + _width: 200, + _height: 50, + _fitWidth: true, + _autoHeight: true, + }); + this.addDoc(newDoc, this.sidebarId); + }; + + constructor(props: GPTPopupProps) { + super(props); + GPTPopup.Instance = this; + } + + componentDidUpdate = () => { + if (this.props.loading) { + this.setDone(false); + } + }; + + summaryBox = () => ( + <> +
+ {this.heading('SUMMARY')} +
+ {!this.props.loading && + (!this.done ? ( + { + setTimeout(() => { + this.setDone(true); + }, 500); + }}> + {this.props.text} + + ) : ( + this.props.text + ))} +
+
+ {!this.props.loading && ( +
+ {this.done ? ( + <> + + + + ) : ( +
+ Summarizing + + +
+ )} +
+ )} + + ); + + editBox = () => { + const hr = this.props.highlightRange; + return ( + hr && ( + <> +
+ {this.heading('TEXT EDIT SUGGESTIONS')} +
+
+ {this.props.text.slice(0, hr[0])} {this.props.text.slice(hr[0], hr[1])} {this.props.text.slice(hr[1])} +
+
+
+ {!this.props.loading && ( +
+ <> + + + +
+ )} + {this.aiWarning()} + + ) + ); + }; + + aiWarning = () => + this.done ? ( +
+ + AI generated responses can contain inaccurate or misleading content. +
+ ) : ( + <> + ); + + heading = (headingText: string) => ( +
+ + {this.props.loading && } +
+ ); + + render() { + return ( +
+ {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} +
+ ); + } +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3f891789a..d17d0e13c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -24,7 +24,7 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); -import { GPTPopup } from './GPTPopup'; +import { GPTPopup } from './GPTPopup/GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; -- cgit v1.2.3-70-g09d2 From e45cf590cf20d3af2bc07e4af78d69470ebfe665 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 17 Apr 2023 11:40:45 -0400 Subject: updated to master --- package-lock.json | 39 ---------------------- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 2 files changed, 1 insertion(+), 40 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/package-lock.json b/package-lock.json index 8ac3df61b..69d112d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5567,16 +5567,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", @@ -6957,28 +6947,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6990,7 +6958,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -22663,12 +22630,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 8d6659f07..37ede931d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -911,7 +911,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Mon, 17 Apr 2023 15:15:46 -0400 Subject: minor cleanup --- src/client/views/nodes/WebBox.tsx | 4 ++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 9 --------- src/client/views/pdf/AnchorMenu.tsx | 14 +------------- 3 files changed, 3 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 94724c006..e05b48c0b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -14,6 +14,7 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -27,6 +28,7 @@ import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; +import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; @@ -35,8 +37,6 @@ import { LinkDocPreview } from './LinkDocPreview'; import { PinProps, PresBox } from './trails'; import './WebBox.scss'; import React = require('react'); -import { DragManager } from '../../util/DragManager'; -import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ff4e725ed..3e60441aa 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -858,14 +858,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve('Mock GPT Call.'); - }, 2000); - }); - }; - animateRes = (resIndex: number) => { if (resIndex < this.gptRes.length) { this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex]; @@ -878,7 +870,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { try { let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); - // let res = await this.mockGPT(); if (res) { this.gptRes = res; this.animateRes(0); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 620750f36..d6dddf71a 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -149,18 +149,6 @@ export class AnchorMenu extends AntimodeMenu { ); } - /** - * Returns a mock api response. - * @returns A Promise that resolves into a string - */ - mockGPTCall = async (): Promise => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve('test'); - }, 1000); - }); - }; - /** * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event @@ -203,7 +191,7 @@ export class AnchorMenu extends AntimodeMenu { try { let res = await gptAPICall(selectedText, GPTCallType.EDIT); - // let res = await this.mockGPTCall(); + // let res = await this.mockGPTCall(); if (!res) return; res = res.trim(); const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1); -- cgit v1.2.3-70-g09d2 From 0a66b0f369a13d5f399bf125727aff73cd6fd1b4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Apr 2023 13:01:37 -0400 Subject: fixed image importing metadata for gpt. fixed showTitle UI to be able to turn off titles on templates (like icon views). added ui for choosing primary/alternate image and a drop target for setting alternate. --- src/client/documents/Documents.ts | 2 + src/client/views/MainView.tsx | 2 + src/client/views/PropertiesButtons.tsx | 6 ++- src/client/views/nodes/ImageBox.scss | 8 +++ src/client/views/nodes/ImageBox.tsx | 58 +++++++++++++++++++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++- src/client/views/nodes/trails/PresBox.tsx | 4 +- 7 files changed, 73 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 09b45a481..a0149eadf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -147,6 +147,8 @@ export class DocumentOptions { _panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); _width?: NUMt = new NumInfo('displayed width of a document'); _height?: NUMt = new NumInfo('displayed height of document'); + 'data-nativeWidth'?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)'); + 'data-nativeHeight'?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)'); _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)'); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)'); _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index acbe0cbc3..4cbf8a811 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -314,6 +314,8 @@ export class MainView extends React.Component { fa.faArrowUp, fa.faBolt, fa.faBullseye, + fa.faTurnUp, + fa.faTurnDown, fa.faCaretUp, fa.faCat, fa.faCheck, diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 031d501ad..98dcf4f21 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -177,7 +177,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { '_showTitle', on => 'Switch between title styles', on => 'text-width', - (dv, doc) => ((dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? 'title' : (dv?.rootDoc || doc)._showTitle === 'title' ? 'title:hover' : undefined) + (dv, doc) => { + const tdoc = dv?.rootDoc || doc; + const newtitle = !tdoc._showTitle ? 'title' : tdoc._showTitle === 'title' ? 'title:hover' : ''; + tdoc._showTitle = newtitle; + } ); } @computed get autoHeightButton() { diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 6359a9491..22dbc1e80 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -119,6 +119,14 @@ } } } +.imageBox-alternateDropTarget { + position: absolute; + color: white; + background: black; + right: 0; + bottom: 0; + z-index: 2; +} .imageBox-fader img { position: absolute; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index e8d4be1fd..98df777cb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,3 +1,5 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; @@ -8,7 +10,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, 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'; @@ -19,6 +21,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../../views/ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -57,6 +60,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent>, addAsAnnotation: boolean) => Opt = () => undefined; + private _overlayIconRef = React.createRef(); @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; @@ -131,10 +135,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent { if (de.complete.docDragData) { - if (de.metaKey) { + const targetIsBullseye = (ele: HTMLElement): boolean => { + if (!ele) return false; + if (ele === this._overlayIconRef.current) return true; + return targetIsBullseye(ele.parentElement as HTMLElement); + }; + if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) { de.complete.docDragData.droppedDocuments.forEach( action((drop: Doc) => { Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop); + this.rootDoc[this.fieldKey + '-usePath'] = 'alternate:hover'; e.stopPropagation(); }) ); @@ -154,8 +164,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); - @undoBatch - setUseAlt = () => (this.layoutDoc[this.fieldKey + '-useAlt'] = !this.layoutDoc[this.fieldKey + '-useAlt']); @undoBatch setNativeSize = action(() => { @@ -239,7 +247,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: 'copy' }); if (!Doc.noviceMode) { funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); @@ -356,6 +363,41 @@ export class ImageBox extends ViewBoxAnnotatableComponent + toggle between + + primary, + + + alternate, + + and show + + alternate on hover + + + }> +
setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))} + style={{ + display: (SnappingManager.GetIsDragging() && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none', + width: 'min(10%, 25px)', + height: 'min(10%, 25px)', + background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', + color: usePath === undefined ? 'black' : 'white', + }}> + +
+
+ ); + } @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 @@ -389,15 +431,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
{fadepath === srcpath ? null : ( -
+
)} @@ -405,6 +446,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent + {this.overlayImageIcon}
); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3e60441aa..bbe38cf99 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -885,13 +885,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { bestTarget._dataTransition = `all ${transTime}ms`; const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; + bestTarget[fkey + '-usePath'] = activeItem.presUsePath; setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); } if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { @@ -613,7 +613,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinProps?.activeFrame !== undefined; const fkey = Doc.LayoutFieldKey(targetDoc); if (pinProps.pinData.dataview) { - pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; + pinDoc.presUsePath = targetDoc[fkey + '-usePath']; pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; } if (pinProps.pinData.dataannos) { -- cgit v1.2.3-70-g09d2 From 33299a19d86948051eef442825c0b3241d1ac619 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 24 Apr 2023 11:55:53 -0400 Subject: fixed isContentActive=false to apply to stacking collections. fixed pile view to be faster and to work in fit content panels. fixed issues with hidden docs and freeformviews that act as lightboxes - hidden docs can be shown as the lightbox doc without modifying the hidden flag to allow collection state to be restored. --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 12 +-- src/client/util/DocumentManager.ts | 15 ++- src/client/views/DocComponent.tsx | 104 ++++++++++---------- src/client/views/LightboxView.tsx | 6 +- src/client/views/MainView.tsx | 18 ++-- src/client/views/PropertiesButtons.tsx | 7 +- src/client/views/StyleProvider.tsx | 5 +- .../views/collections/CollectionPileView.tsx | 35 +++---- .../views/collections/CollectionStackingView.tsx | 15 ++- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 1 + src/client/views/collections/TreeView.tsx | 2 - .../CollectionFreeFormLayoutEngines.tsx | 23 ++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 107 ++++++++++++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 8 +- src/client/views/nodes/DocumentView.tsx | 57 ++++++----- src/client/views/nodes/ImageBox.scss | 4 +- src/client/views/nodes/ImageBox.tsx | 1 + src/client/views/nodes/WebBox.scss | 1 + .../views/nodes/formattedText/DashDocView.tsx | 2 + .../nodes/formattedText/FormattedTextBox.scss | 30 ++++++ .../views/nodes/formattedText/FormattedTextBox.tsx | 47 ++++++++- src/fields/util.ts | 3 + 24 files changed, 315 insertions(+), 192 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a0149eadf..dee5feebc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1066,7 +1066,7 @@ export namespace Docs { } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index abf7313a4..cdb12624f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -619,12 +619,12 @@ export class CurrentUserUtils { } static viewTools(): Button[] { return [ - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e01457b4f..4542c1c05 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -8,8 +8,7 @@ import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; -import { MainView } from '../views/MainView'; -import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; @@ -227,7 +226,7 @@ export class DocumentManager { let rootContextView = docViewPath.shift(); await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; - else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation); + else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation); }; // shows a document by first: @@ -247,9 +246,17 @@ export class DocumentManager { const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); options.didMove = true; - docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); + 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) { + // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox) + const target = DocCast(targetDoc.annotationOn, targetDoc); + const contextView = this.getDocumentView(DocCast(target.context)); + if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) { + await new Promise(waitres => setTimeout(() => waitres())); + } + } docContextPath.shift(); const childViewIterator = async (docView: DocumentView) => { const innerDoc = docContextPath.shift(); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a59189fd2..d60ad68c6 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -141,29 +141,25 @@ export function ViewBoxAnnotatableComponent

() const effectiveAcl = GetEffectiveAcl(this.dataDoc); const indocs = doc instanceof Doc ? [doc] : doc; const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin); - if (docs.length) { - docs.map(doc => { - Doc.SetInPlace(doc, 'followLinkToggle', undefined, true); - doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true); + + docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true)); + const targetDataDoc = this.dataDoc; + const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); + const toRemove = value.filter(v => docs.includes(v)); + + if (toRemove.length !== 0) { + const recent = Doc.MyRecentlyClosed; + toRemove.forEach(doc => { + leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); + Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + doc.context = undefined; + if (recent) { + Doc.RemoveDocFromList(recent, 'data', doc); + doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); + } }); - const targetDataDoc = this.dataDoc; - const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); - const toRemove = value.filter(v => docs.includes(v)); - - if (toRemove.length !== 0) { - const recent = Doc.MyRecentlyClosed; - toRemove.forEach(doc => { - leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); - Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - doc.context = undefined; - if (recent) { - Doc.RemoveDocFromList(recent, 'data', doc); - doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); - } - }); - this.isAnyChildContentActive() && this.props.select(false); - return true; - } + this.isAnyChildContentActive() && this.props.select(false); + return true; } return false; @@ -190,46 +186,44 @@ export function ViewBoxAnnotatableComponent

() return false; } const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); - const added = docs.filter(d => !docList.includes(d)); const effectiveAcl = GetEffectiveAcl(targetDataDoc); + if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { + return false; + } + const added = docs; if (added.length) { - if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { - return false; - } else { - if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) { - added.forEach(d => { - for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d); - } - }); - } + if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) { + added.forEach(d => { + for (const key of Object.keys(this.props.Document[AclSym])) { + if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d); + } + }); + } - if (effectiveAcl === AclAugment) { - added.map(doc => { - if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); + if (effectiveAcl === AclAugment) { + added.map(doc => { + if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); + doc.context = this.props.Document; + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; + Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + }); + } else { + added + .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) + .map(doc => { + // only make a pushpin if we have acl's to edit the document + //DocUtils.LeavePushpin(doc); + doc._stayInCollection = undefined; doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + + Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); }); - } else { - added - .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) - .map(doc => { - // only make a pushpin if we have acl's to edit the document - //DocUtils.LeavePushpin(doc); - doc._stayInCollection = undefined; - doc.context = this.props.Document; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - - Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); - }); - const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; - if (annoDocs instanceof List) annoDocs.push(...added); - else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); - targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now())); - } + const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; + if (annoDocs instanceof List) annoDocs.push(...added); + else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); + targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now())); } } return true; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 69eec8456..c18a89481 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -63,10 +63,8 @@ export class LightboxView extends React.Component { Doc.ActiveTool = InkTool.None; MainView.Instance._exploreMode = false; } else { - if (doc) { - const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); - l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); - } + const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); + l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); //TabDocView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4cbf8a811..e4554c339 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,7 +49,7 @@ import { LinkMenu } from './linking/LinkMenu'; import './MainView.scss'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; @@ -222,6 +222,7 @@ export class MainView extends React.Component { constructor(props: Readonly<{}>) { super(props); + DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl; MainView.Instance = this; DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); @@ -245,6 +246,7 @@ export class MainView extends React.Component { ...[ fa.faExclamationCircle, fa.faEdit, + fa.faArrowDownShortWide, fa.faTrash, fa.faTrashAlt, fa.faShare, @@ -584,7 +586,7 @@ export class MainView extends React.Component { Document={this.headerBarDoc} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={DefaultStyleProvider} @@ -619,7 +621,7 @@ export class MainView extends React.Component { Document={this.mainContainer!} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={this._hideUI ? DefaultStyleProvider : undefined} @@ -688,7 +690,7 @@ export class MainView extends React.Component { sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); - static addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => { + static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => { const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); const keyValue = whereFields[1]?.includes('KeyValue'); const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; @@ -716,7 +718,7 @@ export class MainView extends React.Component { Document={this._sidebarContent.proto || this._sidebarContent} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} @@ -748,7 +750,7 @@ export class MainView extends React.Component { Document={Doc.MyLeftSidebarMenu} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} rootSelected={returnTrue} removeDocument={returnFalse} @@ -810,7 +812,7 @@ export class MainView extends React.Component {

)}
- {this.propertiesWidth() < 10 ? null : } + {this.propertiesWidth() < 10 ? null : }
@@ -889,7 +891,7 @@ export class MainView extends React.Component { docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} addDocument={this.addButtonDoc} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} removeDocument={this.remButtonDoc} ScreenToLocalTransform={this.buttonBarXf} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 98dcf4f21..cf808f801 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -136,10 +136,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { // containerDoc.noShadow = // containerDoc.disableDocBrushing = // containerDoc._forceActive = - containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox; - containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; + //containerDoc._fitContentsToBox = + containerDoc._isLightbox = !containerDoc._isLightbox; + //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); - dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); + //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.linkDisplay = false))); }); } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5f16e0ebd..d98e5aa80 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -30,7 +30,6 @@ export enum StyleProp { TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view Opacity = 'opacity', // opacity of the document view - Hidden = 'hidden', // whether the document view should not be isplayed BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views BorderRounding = 'borderRounding', // border radius of the document view Color = 'color', // foreground color of Document view items @@ -172,8 +171,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt this.childDocs.length, - num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document) - ); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; @@ -48,13 +42,15 @@ export class CollectionPileView extends CollectionSubView() { @undoBatch removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d))); - return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d)); + const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc); + return ret; }; - toggleIcon = () => { + @computed get toggleIcon() { return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); - }; + } // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { @@ -63,11 +59,11 @@ export class CollectionPileView extends CollectionSubView() {
@@ -87,15 +83,12 @@ export class CollectionPileView extends CollectionSubView() { this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = computePassLayout.name; } else { - const defaultSize = 25; - !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250); + const defaultSize = 500; !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); - if (this.layoutEngine() === computePassLayout.name) { - this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; - this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; - this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); - this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); - } + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; + this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); + this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); this.layoutDoc._panX = this.layoutDoc._panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; this.props.Document._pileLayoutEngine = computeStarburstLayout.name; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index bdad325d5..020fe1cb4 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -10,7 +10,7 @@ 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 { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -238,9 +238,7 @@ export class CollectionStackingView extends CollectionSubView this.props.childClickScript || ScriptCast(this.Document.onChildClick); - } + onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } @@ -300,7 +298,13 @@ export class CollectionStackingView extends CollectionSubView - this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; + this.props.isContentActive?.() === false + ? false + : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) + ? true + : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false + ? false + : undefined; isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list @@ -320,6 +324,7 @@ export class CollectionStackingView extends CollectionSubView boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childContentsActive?: () => boolean | undefined; childFitWidth?: (child: Doc) => boolean; childShowTitle?: () => string; childOpacity?: () => number; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8fb610b87..4adf86683 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -801,7 +801,6 @@ export class TreeView extends React.Component { case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; - case StyleProp.Hidden: return false; case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc); @@ -827,7 +826,6 @@ export class TreeView extends React.Component { }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return null; - if (property.startsWith(StyleProp.Hidden)) return false; return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 81b0c4d8a..2549ee0b3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -93,6 +93,7 @@ export function computePassLayout(poolData: Map, pivotDoc: Doc width: layout[WidthSym](), height: layout[HeightSym](), pair: { layout, data }, + transition: 'all .3s', replica: '', }); }); @@ -100,28 +101,27 @@ export function computePassLayout(poolData: Map, pivotDoc: Doc } export function computeStarburstLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { - const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map(); - const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75 - const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize]; - const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize]; + const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)]; childPairs.forEach(({ layout, data }, i) => { - const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75 + const aspect = layout[HeightSym]() / layout[WidthSym](); + const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect); const deg = (i / childPairs.length) * Math.PI * 2; docMap.set(layout[Id], { - x: Math.cos(deg) * burstRadius[0] - docSize / 2, - y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2, - width: docSize, //layout[WidthSym](), - height: (docSize * layout[HeightSym]()) / layout[WidthSym](), + x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)), + y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)), + width: docSize, + height: docSize * aspect, zIndex: NumCast(layout.zIndex), pair: { layout, data }, replica: '', color: 'white', backgroundColor: 'white', + transition: 'all 0.3s', }); }); - const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined }; - return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); + const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined }; + return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } export function computePivotLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { @@ -424,6 +424,7 @@ function normalizeResults( color: newPosRaw.color, pair: ele[1].pair, }; + if (newPosRaw.transition) newPos.transition = newPosRaw.transition; poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos }); } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ff0d01f29..1fc4d9259 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -308,6 +308,7 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this._lightboxDoc) 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.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); @@ -327,7 +328,7 @@ export class CollectionFreeFormView extends CollectionSubView> => { return new Promise>(res => { - doc.hidden && (doc.hidden = false); + if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false; const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -778,7 +779,9 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this._lightboxDoc) this._lightboxDoc = undefined; if (this.onBrowseClickHandler()) { if (this.props.DocumentView?.()) { this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY }); @@ -1274,7 +1277,7 @@ export class CollectionFreeFormView extends CollectionSubView(doc instanceof Doc ? [doc] : doc); + this._lightboxDoc = doc; + return true; + } else if (this.childDocList?.includes(doc)) { + if (doc.hidden) doc.hidden = false; return true; } } return this.props.addDocTab(doc, where); }); + @observable _lightboxDoc: Opt; getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { const childDoc = params.pair.layout; @@ -1936,7 +1942,9 @@ export class CollectionFreeFormView extends CollectionSubView Math.max(0, this.props.PanelWidth() - 30); + lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30); + lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15); render() { TraceMobx(); return ( @@ -1965,36 +1973,65 @@ export class CollectionFreeFormView extends CollectionSubView - {this._firstRender ? this.placeholder : this.marqueeView} - {this.props.noOverlay ? null : } - - {/* // uncomment to show snap lines */} -
- - {this._hLines?.map(l => ( - - ))} - {this._vLines?.map(l => ( - - ))} - -
+ {this._lightboxDoc ? ( +
+ +
+ ) : ( + <> + {this._firstRender ? this.placeholder : this.marqueeView} + {this.props.noOverlay ? null : } + + {/* // uncomment to show snap lines */} +
+ + {this._hLines?.map(l => ( + + ))} + {this._vLines?.map(l => ( + + ))} + +
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( -
- ) : null} + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( +
+ ) : null} + + )}
); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index d443df0f3..eaeb5f933 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -371,10 +371,10 @@ export class MarqueeView extends React.Component { + delete = (e?: React.PointerEvent | KeyboardEvent | undefined, hide?: boolean) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(doc => this.props.removeDocument?.(doc)); + selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc))); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -550,11 +550,11 @@ export class MarqueeView extends React.Component this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined }; + public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse; + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; @@ -446,24 +447,33 @@ export class DocumentViewInternal extends DocComponent any); if (!this.disableClickScriptFunc && this.onClickHandler?.script) { const { clientX, clientY, shiftKey, altKey, metaKey } = e; - const func = () => - this.onClickHandler?.script.run( - { - this: this.layoutDoc, - self: this.rootDoc, - _readOnly_: false, - scriptContext: this.props.scriptContext, - documentView: this.props.DocumentView(), - clientX, - clientY, - shiftKey, - altKey, - metaKey, - }, - console.log - ).result?.select === true - ? this.props.select(false) - : ''; + const func = () => { + // replace default add doc func with this view's add doc func. + // to allow override behaviors for how to display links to undisplayed documents. + // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place + // instead of in the global lightbox + const oldFunc = DocumentViewInternal.addDocTabFunc; + DocumentViewInternal.addDocTabFunc = this.props.addDocTab; + const res = + this.onClickHandler?.script.run( + { + this: this.layoutDoc, + self: this.rootDoc, + _readOnly_: false, + scriptContext: this.props.scriptContext, + documentView: this.props.DocumentView(), + clientX, + clientY, + shiftKey, + altKey, + metaKey, + }, + console.log + ).result?.select === true + ? this.props.select(false) + : ''; + DocumentViewInternal.addDocTabFunc = oldFunc; + }; clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -1318,9 +1328,6 @@ export class DocumentView extends React.Component { const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton; return hideCount ? null : ; } - @computed get hidden() { - return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); - } @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @@ -1497,7 +1504,7 @@ export class DocumentView extends React.Component { const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; - return this.hidden ? null : ( + return (
(this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}> {!this.props.Document || !this.props.PanelWidth() ? null : (
{ height: this._height, position: 'absolute', display: 'inline-block', + left: 0, + top: 0, }} onPointerLeave={this.onPointerLeave} onPointerEnter={this.onPointerEnter} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index fd7fbb333..3b42c41a5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -24,6 +24,27 @@ audiotag:hover { transform: scale(2); transform-origin: bottom center; } +.formattedTextBox { + touch-action: none; + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $medium-gray; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: inherit; + display: flex; + flex-direction: row; + transition: opacity 1s; + width: 100%; + position: absolute; + top: 0; + left: 0; +} .formattedTextBox-cont { touch-action: none; @@ -51,6 +72,15 @@ audiotag:hover { position: absolute; } } +.formattedTextBox-alternateButton { + position: absolute; + color: white; + background: black; + right: 0; + bottom: 0; + width: 15; + height: 15; +} .formattedTextBox-outer-selected, .formattedTextBox-outer { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index bbe38cf99..2755d5100 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,9 +1,9 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; import { isEqual } from 'lodash'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { Configuration, OpenAIApi } from 'openai'; import { baseKeymap, selectAll } from 'prosemirror-commands'; import { history } from 'prosemirror-history'; import { inputRules } from 'prosemirror-inputrules'; @@ -1928,6 +1928,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ); } + @computed get overlayAlternateIcon() { + const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`]; + return ( + + toggle between + + primary, + + + alternate, + + and show + + alternate on hover + +
+ }> +
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined)) + } + style={{ + display: this.props.isContentActive() ? 'block' : 'none', + background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', + color: usePath === undefined ? 'black' : 'white', + }}> + +
+ + ); + } + @computed get fieldKey() { + const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]); + return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : ''); + } + @observable _isHovering = false; render() { TraceMobx(); const active = this.props.isContentActive() || this.props.isSelected(); @@ -1944,7 +1983,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent return styleFromLayoutString?.height === '0px' ? null : (
(this._isHovering = true))} + onPointerLeave={action(() => (this._isHovering = false))} ref={r => r?.addEventListener( 'wheel', // 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) @@ -1966,6 +2007,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
); diff --git a/src/fields/util.ts b/src/fields/util.ts index 70d9ed61f..92f3a69eb 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -107,8 +107,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number redo: () => (receiver[prop] = value), undo: () => { const wasUpdate = receiver[UpdatingFromServer]; + const wasForce = receiver[ForceServerWrite]; + receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. receiver[prop] = curValue; + receiver[ForceServerWrite] = wasForce; receiver[UpdatingFromServer] = wasUpdate; }, prop: prop?.toString(), -- cgit v1.2.3-70-g09d2 From abddb2104fe8b23c4b3f86c2ed46c20bcd0777e6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 24 Apr 2023 12:24:02 -0400 Subject: resized alternate text icon --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 6 ++++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 3b42c41a5..eb9dd1e66 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -73,13 +73,15 @@ audiotag:hover { } } .formattedTextBox-alternateButton { + align-items: center; + flex-direction: column; position: absolute; color: white; background: black; right: 0; bottom: 0; - width: 15; - height: 15; + width: 11; + height: 11; } .formattedTextBox-outer-selected, diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2755d5100..82a26fa86 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1953,11 +1953,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined)) } style={{ - display: this.props.isContentActive() ? 'block' : 'none', + display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none', background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', color: usePath === undefined ? 'black' : 'white', }}> - +
); -- cgit v1.2.3-70-g09d2 From 4846ff09a02cce1da4f0b8984e387d7d204837f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 26 Apr 2023 10:58:58 -0400 Subject: fixed filters - checkboxes generated when options are less than 20, added -undefined- as distinct value option. fixed pointer evcents for docs on pdfs --- src/client/util/CurrentUserUtils.ts | 7 +- src/client/util/DragManager.ts | 18 +- src/client/views/FilterPanel.scss | 12 +- src/client/views/FilterPanel.tsx | 41 +++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/FilterBox.scss | 189 --------------------- src/client/views/nodes/FilterBox.tsx | 0 .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 3 +- src/fields/Doc.ts | 2 +- 11 files changed, 57 insertions(+), 224 deletions(-) delete mode 100644 src/client/views/nodes/FilterBox.scss delete mode 100644 src/client/views/nodes/FilterBox.tsx (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4a90edd49..5475873a9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -49,6 +49,7 @@ interface Button { ignoreClick?: boolean; buttonText?: string; backgroundColor?: string; + waitForDoubleClickToClick?: boolean; // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } @@ -613,8 +614,9 @@ export class CurrentUserUtils { static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform ] } static viewTools(): Button[] { @@ -689,7 +691,6 @@ export class CurrentUserUtils { { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 404c85eb2..b6de5604d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -11,6 +11,7 @@ import * as globalCssVariables from '../views/global/globalCssVariables.scss'; import { Colors } from '../views/global/globalEnums'; import { DocumentView } from '../views/nodes/DocumentView'; import { ScriptingGlobals } from './ScriptingGlobals'; +import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; @@ -597,7 +598,18 @@ export namespace DragManager { } } -ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { - if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent'; - DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); +ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) { + if (readOnly) { + if (SelectionManager.Views().length) + return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged) + ? Colors.MEDIUM_BLUE + : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false) + ? 'transparent' + : DragManager.GetRaiseWhenDragged() + ? Colors.MEDIUM_BLUE_ALT + : Colors.PINK; + return DragManager.GetRaiseWhenDragged() ? Colors.PINK : 'transparent'; + } + if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false))); + else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); }); diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index 7f907c8d4..c903f29ee 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -33,9 +33,10 @@ // } .filterBox-select { - // width: 90%; + display: flex; + width: 100%; margin-top: 5px; - // margin-bottom: 15px; + background: white; } .filterBox-saveBookmark { @@ -150,8 +151,8 @@ .filterBox-treeView { display: flex; flex-direction: column; - width: 200px; - position: absolute; + width: 100%; + position: relative; right: 0; top: 0; z-index: 1; @@ -184,6 +185,7 @@ display: inline-block; width: 100%; margin-bottom: 10px; - //height: calc(100% - 30px); + margin-left: 5px; + overflow: auto; } } diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index d35494f26..a237249c1 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -51,10 +51,12 @@ export class FilterPanel extends React.Component { const keys = new Set(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()) + const sortedKeys = Array.from(keys.keys()) .filter(key => key[0]) .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode) .sort(); + noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); + return [...noviceFields, ...sortedKeys]; } /** @@ -129,7 +131,7 @@ export class FilterPanel extends React.Component { maxVal = Math.max(num, maxVal); } }); - if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.rtFields > 20)) { + if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { this._chosenFacets.set(facetHeader, 'text'); } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { } else { @@ -140,7 +142,7 @@ export class FilterPanel extends React.Component { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set(); + const set = new Set([String.fromCharCode(127) + '--undefined--']); if (facetHeader === 'tags') allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field) @@ -158,32 +160,29 @@ export class FilterPanel extends React.Component { let nonNumbers = 0; facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); - const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { - return facetValue; - }); - return facetValueDocSet; + return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); return ( -
-
- -
filters together
-
- +
- this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} /> +
+
+ +
{' '}
-
+
{Array.from(this.activeFacets.keys()).map(facetHeader => (
{facetHeader} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3333befc6..719a39e8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1972,6 +1972,7 @@ export class CollectionFreeFormView extends CollectionSubView [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)]; contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); @@ -898,6 +899,7 @@ export class DocumentViewInternal extends DocComponent div, - > div > div { - width: 100%; - height: 100%; - } - } - - .filterBox-tree { - display: inline-block; - width: 100%; - margin-bottom: 10px; - //height: calc(100% - 30px); - } -} diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 82a26fa86..399e6bfdf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -852,7 +852,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); - optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' }); + optionItems.push({ + description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', + event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), + icon: !this.Document._singleLine ? 'grip-lines' : 'bars', + }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 68241e61f..20803bba8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -536,7 +536,8 @@ export class PDFViewer extends React.Component { NativeWidth={returnZero} NativeHeight={returnZero} setContentView={emptyFunction} // override setContentView to do nothing - pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} + pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. + childPointerEvents={'all'} // but freeform children need to get events to allow text editing, etc renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b0033b977..0c2ee35fd 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1391,7 +1391,7 @@ export namespace Doc { return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { -- cgit v1.2.3-70-g09d2 From 1150296b546bb3628c3dac5704150dfb4295434b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 26 Apr 2023 16:03:49 -0400 Subject: added cheatsheet and shortcut for viewing text shortcuts. changed menu organization slightly for viewability. --- src/client/util/RTFMarkup.tsx | 137 +++++++++++++++++++++ src/client/util/ServerStats.tsx | 3 +- src/client/views/MainView.tsx | 8 +- src/client/views/MainViewModal.tsx | 37 +++--- src/client/views/nodes/DocumentView.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 + .../views/nodes/formattedText/RichTextRules.ts | 7 ++ 7 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 src/client/util/RTFMarkup.tsx (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx new file mode 100644 index 000000000..306d151d7 --- /dev/null +++ b/src/client/util/RTFMarkup.tsx @@ -0,0 +1,137 @@ +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { MainViewModal } from '../views/MainViewModal'; + +@observer +export class RTFMarkup extends React.Component<{}> { + static Instance: RTFMarkup; + @observable private isOpen = false; // whether the SharingManager modal is open or not + + // private get linkVisible() { + // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; + // } + + @action + public open = () => (this.isOpen = true); + + @action + public close = () => (this.isOpen = false); + + constructor(props: {}) { + super(props); + RTFMarkup.Instance = this; + } + + @observable _stats: { [key: string]: any } | undefined; + + /** + * @returns the main interface of the SharingManager. + */ + @computed get cheatSheet() { + return ( +
+

+ {`wiki:phrase`} + {` display wikipedia page for entered text (terminate with carriage return)`} +

+

+ {`#tag `} + {` add hashtag metadata to document. e.g, #idea`} +

+

+ {`#, ## ... ###### `} + {` set heading style based on number of '#'s between 1 and 6`} +

+

+ {`#tag `} + {` add hashtag metadata to document. e.g, #idea`} +

+

+ {`>> `} + {` add a sidebar text document inline`} +

+

+ {`\`\` `} + {` create a code snippet block`} +

+

+ {`%% `} + {` restore default styling`} +

+

+ {`%color `} + {` changes text color styling. e.g., %green.`} +

+

+ {`%num `} + {` set font size. e.g., %10 for 10pt font`} +

+

+ {`%eq `} + {` creates an equation block for typeset math`} +

+

+ {`%alt `} + {` switch between primary and alternate text (see bottom right Button for hover options).`} +

+

+ {`%f `} + {` create an inline footnote`} +

+

+ {`%> `} + {` create a bockquote section. Terminate with 2 carriage returns`} +

+

+ {`%( `} + {` start a section of inline elidable text. Terminate the inline text with %)`} +

+

+ {`%q `} + {` start a quoted block of text that’s indented on the left and right. Terminate with %q`} +

+

+ {`%d `} + {` start a block text where the first line is indented`} +

+

+ {`%h `} + {` start a block of text that begins with a hanging indent`} +

+

+ {`%[ `} + {` left justify text`} +

+

+ {`%^ `} + {` center text`} +

+

+ {`%] `} + {` right justify text`} +

+

+ {`[:doctitle]] `} + {` hyperlink to document specified by it’s title`} +

+

+ {`[[fieldname]] `} + {` display value of fieldname`} +

+

+ {`[[fieldname=value]] `} + {` assign value to fieldname of document and display it`} +

+

+ {`[[fieldname:doctitle]] `} + {` show value of fieldname from doc specified by it’s title`} +

+
+ ); + } + + render() { + return ; + } +} diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx index 0ab411bff..f84ad8598 100644 --- a/src/client/util/ServerStats.tsx +++ b/src/client/util/ServerStats.tsx @@ -1,8 +1,7 @@ -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { MainViewModal } from '../views/MainViewModal'; -import { DocumentView } from '../views/nodes/DocumentView'; import './SharingManager.scss'; @observer diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 43f56dd8c..5af9a9f9a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -14,7 +14,7 @@ import { StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; -import { Docs, DocUtils } from '../documents/Documents'; +import { Docs } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { CaptureManager } from '../util/CaptureManager'; import { DocumentManager } from '../util/DocumentManager'; @@ -22,11 +22,12 @@ import { GroupManager } from '../util/GroupManager'; import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; import { ReportManager } from '../util/ReportManager'; +import { RTFMarkup } from '../util/RTFMarkup'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; +import { ServerStats } from '../util/ServerStats'; import { ColorScheme, SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; -import { ServerStats } from '../util/ServerStats'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; @@ -41,7 +42,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; +import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; @@ -971,6 +972,7 @@ export class MainView extends React.Component { + diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 55dee005d..32997a944 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import "./MainViewModal.scss"; +import './MainViewModal.scss'; import { observer } from 'mobx-react'; export interface MainViewOverlayProps { @@ -15,31 +15,38 @@ export interface MainViewOverlayProps { @observer export class MainViewModal extends React.Component { - render() { const p = this.props; const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; - return !p.isDisplayed ? (null) : ( -
-
+ return !p.isDisplayed ? null : ( +
+
{p.contents}
-
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 38aa8db55..d58a4c199 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -767,6 +767,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() }); (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); @@ -774,11 +775,10 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); @@ -825,7 +825,7 @@ export class DocumentViewInternal extends DocComponent, dataDoc: Doc) => void; @@ -858,6 +859,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' }); + optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 599f96bca..20ce929ad 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -8,6 +8,7 @@ import { normalizeEmail } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; +import { RTFMarkup } from '../../../util/RTFMarkup'; import { FormattedTextBox } from './FormattedTextBox'; import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; @@ -228,6 +229,12 @@ export class RichTextRules { return null; }), + // show markup help + new InputRule(new RegExp(/%\?$/), (state, match, start, end) => { + setTimeout(RTFMarkup.Instance.open); + return state.tr.deleteRange(start, end); + }), + // stop using active style new InputRule(new RegExp(/%alt$/), (state, match, start, end) => { setTimeout(() => (this.Document[this.TextBox.props.fieldKey + '-usePath'] = this.Document[this.TextBox.props.fieldKey + '-usePath'] ? undefined : 'alternate')); -- cgit v1.2.3-70-g09d2 From 76f4456d652e124e733eb9b93272384d53ac3d28 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 2 May 2023 09:27:08 -0400 Subject: fixed ink stroke text box placement. fixed opening metadata from OverlayView. Made trail videos a novice feature. --- src/client/views/InkStroke.scss | 3 +++ src/client/views/OverlayView.tsx | 6 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresElementBox.tsx | 21 ++++++++------------- 4 files changed, 15 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index bed7caf7f..f504890a5 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -39,5 +39,8 @@ &:hover { background: #9f9f9f0a; } + > .formattedTextBox { + position: relative; + } } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index bdc48d03a..9baedae6d 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -7,14 +7,14 @@ import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; -import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; +import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import './OverlayView.scss'; import { DefaultStyleProvider } from './StyleProvider'; @@ -224,7 +224,7 @@ export class OverlayView extends React.Component { focus={emptyFunction} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0610d1e45..eb28ffbd7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2065,7 +2065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 502eef373..62e5314c3 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -353,14 +353,11 @@ export class PresElementBox extends ViewBoxBaseComponent() { @undoBatch @action showRecording = (activeItem: Doc, iconClick: boolean = false) => { - // if (iconClick) PresElementBox.showVideo = true; - // if (!PresElementBox.showVideo) return; - // remove the overlays on switch *IF* not opened from the specific icon if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay(); if (activeItem.recording) { - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, DocCast(activeItem.recording)); } }; @@ -437,15 +434,13 @@ export class PresElementBox extends ViewBoxBaseComponent() {
); - if (!Doc.noviceMode) { - items.push( - {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> -
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> - e.stopPropagation()} /> -
- - ); - } + items.push( + {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> +
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + e.stopPropagation()} /> +
+ + ); if (this.indexInPres !== 0) { items.push( {activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}
}> -- cgit v1.2.3-70-g09d2