diff options
Diffstat (limited to 'src')
147 files changed, 1669 insertions, 1346 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 372e2a4e5..7878e9bfe 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -11,6 +11,10 @@ export enum GPTDocCommand { export const DescriptionSeperator = '======'; export const DocSeperator = '------'; +export enum TextClassifications { + Title = 'word', //a few words + Caption = 'sentence', //few sentences + LengthyDescription = 'paragraphs' } enum GPTCallType { SUMMARY = 'summary', @@ -36,6 +40,9 @@ enum GPTCallType { SUBSETDOCS = 'subset_docs', // select a subset of documents based on their descriptions DOCINFO = 'doc_info', // provide information about a document SORTDOCS = 'sort_docs', + CLASSIFYTEXTMINIMAL = 'classify_text_minimal', // classify text into one of the three categories: title, caption, lengthy description + CLASSIFYTEXTFULL = 'classify_text_full', //tags pdf content + GENERATESCRAPBOOK = 'generate_scrapbook' } type GPTCallOpts = { @@ -48,6 +55,23 @@ type GPTCallOpts = { const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { // newest model: gpt-4 summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' }, + + + sort_docs: { + model: 'gpt-4o', + maxTokens: 2048, + temp: 0.25, + prompt: + `The user is going to give you a list of descriptions. + Each one is separated by '${DescriptionSeperator}' on either side. + Descriptions will vary in length, so make sure to only separate when you see '${DescriptionSeperator}'. + Sort them by the user's specifications. + Make sure each description is only in the list once. Each item should be separated by '${DescriptionSeperator}'. + Immediately afterward, surrounded by '${DocSeperator}' on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). + It is VERY important that you format it exactly as described, ensuring the proper number of '${DescriptionSeperator[0]}' and '${DocSeperator[0]}' (${DescriptionSeperator.length} of each) and NO commas`, + }, + + edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, stack: { model: 'gpt-4o', @@ -69,17 +93,23 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please only return a JSON with a correlation column 1 propert, a correlation column 2 property, and an analysis property. ", }, - sort_docs: { + //new + classify_text_minimal: { model: 'gpt-4o', maxTokens: 2048, temp: 0.25, - prompt: `The user is going to give you a list of descriptions. - Each one is separated by '${DescriptionSeperator}' on either side. - Descriptions will vary in length, so make sure to only separate when you see '${DescriptionSeperator}'. - Sort them by the user's specifications. - Make sure each description is only in the list once. Each item should be separated by '${DescriptionSeperator}'. - Immediately afterward, surrounded by '${DocSeperator}' on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). - It is VERY important that you format it exactly as described, ensuring the proper number of '${DescriptionSeperator[0]}' and '${DocSeperator[0]}' (${DescriptionSeperator.length} of each) and NO commas`, + prompt: `Based on the content of the the text, classify it into the + most appropriate category: '${TextClassifications.Title}' if it is a few words, '${TextClassifications.Caption}' if it is a couple sentences or less, or '${TextClassifications.LengthyDescription}' if it is a lengthy description. Output exclusively the classification in your response. + ` + }, + classify_text_full: { + model: 'gpt-4o', + maxTokens: 2048, + temp: 0.25, + prompt: `Based on the content of the the text, classify it into the + most appropriate category: '${TextClassifications.Title}', '${TextClassifications.Caption}', or '${TextClassifications.LengthyDescription}'. + Then provide five more descriptive tags (single words) separated by spaces. + Finally, include a more detailed summary phrase tag using underscores, for a total of seven tags.` }, describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' }, flashcard: { @@ -107,6 +137,7 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { temp: 0.5, prompt: 'You will be given a list of field descriptions for one or more templates in the format {field #0: “description”}{field #1: “description”}{...}, and a list of column descriptions in the format {“title”: “description”}{...}. Your job is to match columns with fields based on their descriptions. Your output should be in the following JSON format: {“template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}, “template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}} where “template_title” is the templates title as specified in the description provided, # represents the field # and “title” the title of the column assigned to it. A filled out example might look like {“fivefield2”:{“0”:”Name”, “1”:”Image”, “2”:”Caption”, “3”:”Position”, “4”:”Stats”}, “fivefield3”:{0:”Image”, 1:”Name”, 2:”Caption”, 3:”Stats”, 4:”Position”}. Include one object for each template. IT IS VERY IMPORTANT THAT YOU ONLY INCLUDE TEXT IN THE FORMAT ABOVE, WITH NO ADDITIONS WHATSOEVER. Do not include extraneous ‘#’ characters, ‘column’ for columns, or ‘template’ for templates: ONLY THE TITLES AND NUMBERS. There should never be one column assigned to more than one field (ie. if the “name” column is assigned to field 1, it can’t be assigned to any other fields) . Do this for each template whose fields are described. The descriptions are as follows:', }, + vizsum: { model: 'gpt-4.1', maxTokens: 512, @@ -138,6 +169,32 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { temp: 0.5, prompt: 'You will be coloring drawings. You will be given what the drawing is, then a list of descriptions for parts of the drawing. Based on each description, respond with the stroke and fill color that it should be. Follow the rules: 1. Avoid using black for stroke color 2. Make the stroke color 1-3 shades darker than the fill color 3. Use the same colors when possible. Format as {#abcdef #abcdef}, making sure theres a color for each description, and do not include any additional text.', }, + + generate_scrapbook: { + model: 'gpt-4o', + maxTokens: 2048, + temp: 0.5, + prompt: `Generate an aesthetically pleasing scrapbook layout preset based on these items. + Return your response as JSON in the format: + [{ + "type": rich text or image or pdf or video or collection + "tag": a singular tag summarizing the document + "acceptTags": [a list of all relevant tags that this document accepts, like ['PERSON', 'LANDSCAPE']] + "x": number, + "y": number, + "width": number, **note: if it is in an image, please respect existing aspect ratio if it is provided + "height": number + }, ...] + If there are mutliple documents and you wish to nest documents into a collection for aesthetic purposes, you may include + "children": [ + { type: + tag: + x: , y: , width: , height: + } + ] ` + + }, + command_type: { model: 'gpt-4-turbo', maxTokens: 1024, diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index dee929c89..9704867d5 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -587,6 +587,15 @@ export namespace DocUtils { doc.onClick = FollowLinkScript(); } + /** + * iterate through all items and their children and return a flat list of leaf placeholder content Docs + * @param items + * @returns list of placeholder content Docs + */ + export function unwrapPlaceholders(items: Doc[]): Doc[] { + return items.flatMap(d => (d.$type === DocumentType.COL ? unwrapPlaceholders(DocListCast(d[Doc.LayoutDataKey(d)])) : [d])); + } + export function LeavePushpin(doc: Doc, annotationField: string) { if (doc.followLinkToggle) return undefined; const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 683a18aee..2289224cc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -331,6 +331,9 @@ export class DocumentOptions { toolType?: string; // type of pen tool expertMode?: BOOLt = new BoolInfo('something available only in expert (not novice) mode'); + placeholder_docType?: STRt = new StrInfo('type of document that this document accepts as a child', false, false, Array.from(Object.keys(DocumentType))); + placeholder_acceptTags?: List<string>; + contextMenuFilters?: List<ScriptField>; contextMenuScripts?: List<ScriptField>; contextMenuLabels?: List<string>; diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx index 65336812d..a0871514c 100644 --- a/src/client/util/BranchingTrailManager.tsx +++ b/src/client/util/BranchingTrailManager.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/no-unused-class-component-methods */ -/* eslint-disable react/no-array-index-key */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -52,7 +50,6 @@ export class BranchingTrailManager extends React.Component { this.slideHistoryStack = newArr; }); - // eslint-disable-next-line react/sort-comp observeDocumentChange = (targetDoc: Doc, pres: PresBox) => { const presId = pres.Document[Id]; if (this.prevPresId === presId) { diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss index e7cbb4287..9e81f2669 100644 --- a/src/client/util/CaptureManager.scss +++ b/src/client/util/CaptureManager.scss @@ -67,7 +67,7 @@ font-weight: bold; text-align: left; color: black; - width: 80; + width: 80px; margin-right: 50px; margin-bottom: 5px; } @@ -114,8 +114,8 @@ .buttons { display: flex; position: absolute; - bottom: 0; - right: 15; + bottom: 0px; + right: 15px; justify-content: flex-end; align-items: center; height: 60px; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a25c491d9..45feb0b2f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -562,7 +562,7 @@ pie title Minerals in my tap water const reqdToolOps:DocumentOptions = { title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0", layout_explainer: "This is a palette of documents that can be created.", _layout_dontCenter: "y", - _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, layout_hideContextMenu: true, _chromeHidden: true, + _layout_showTitle: "title", _width: 500, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, layout_hideContextMenu: true, _chromeHidden: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]); } @@ -994,7 +994,7 @@ pie title Minerals in my tap water const reqdOpts:DocumentOptions = { title: "My Imports", _forceActive: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'), _dragOnlyWithinContainer: true, layout_hideContextMenu: true, childLimitHeight: 0, onClickScriptDisable:"never", - childDragAction: dropActionType.copy, _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true, + childDragAction: dropActionType.copy, _layout_autoHeight: true, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go." }; const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.MasonryDocument([], opts), reqdOpts, undefined, {onClick: "deselectAll()"}); diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss index 673af16ee..66f11c529 100644 --- a/src/client/util/GroupManager.scss +++ b/src/client/util/GroupManager.scss @@ -21,7 +21,6 @@ } .group-input { - input { padding: 8px; width: 100%; @@ -33,13 +32,13 @@ border-color: inherit; background: white; color: black; - height: 30; + height: 30px; } } .select-users { - margin-right: 3; - max-height: 30; + margin-right: 3px; + max-height: 30px; width: 100%; display: inline-flex; flex-direction: row; @@ -51,7 +50,6 @@ } } - button { align-self: center; outline: none; @@ -108,7 +106,7 @@ .sort-groups { text-align: left; - margin-left: 5; + margin-left: 5px; width: 50px; cursor: pointer; display: flex; @@ -122,7 +120,7 @@ .group-body { justify-content: space-between; - height: 225; + height: 225px; padding-right: 1em; justify-content: space-around; @@ -149,8 +147,8 @@ button { position: absolute; width: 30%; - right: 2; - margin-top: 0; + right: 2px; + margin-top: 0px; } } @@ -161,7 +159,6 @@ min-width: 100%; margin: 2px 0; } - } } -}
\ No newline at end of file +} diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss index d50569b26..cb1a4e098 100644 --- a/src/client/util/GroupMemberView.scss +++ b/src/client/util/GroupMemberView.scss @@ -6,7 +6,7 @@ overflow: visible; hr { - margin-top: 20; + margin-top: 20px; } button { @@ -31,7 +31,7 @@ } .editing-header { - margin-bottom: 5; + margin-bottom: 5px; .group-title { font-weight: bold; @@ -39,7 +39,7 @@ text-align: center; border: none; outline: none; - height: 20; + height: 20px; text-overflow: ellipsis; background: none; @@ -57,12 +57,12 @@ .group-buttons { display: flex; - margin-top: 5; - margin-bottom: 25; + margin-top: 5px; + margin-bottom: 25px; .add-member-dropdown { - margin-right: 3; - max-height: 30; + margin-right: 3px; + max-height: 30px; width: 250px; display: inline-flex; flex-direction: row; @@ -108,11 +108,9 @@ .remove-button { position: absolute; - right: 10; + right: 10px; cursor: pointer; } } } - - -}
\ No newline at end of file +} diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 6081c3fae..a4f54f1cf 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -134,6 +134,6 @@ export class LinkFollower { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { - DocumentView.DeselectAll(); + // DocumentView.DeselectAll(); return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true }; }); diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts index 0e4f8cab0..593978b4a 100644 --- a/src/client/util/PingManager.ts +++ b/src/client/util/PingManager.ts @@ -27,7 +27,7 @@ export class PingManager { this.IsBeating = status; setTimeout(this.showAlert, 100); }); - Networking.PostToServer('/ping', { date: new Date() }) + Networking.PostToServer('/DashPing', { date: new Date() }) .then(res => { SnappingManager.SetServerVersion((res as { message: string }).message); !this.IsBeating && setIsBeating(true); diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index f81f17589..51b719200 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -26,7 +26,7 @@ font-size: 16; font-weight: bold; text-align: left; - width: 80; + width: 80px; margin-right: 50px; } @@ -81,7 +81,7 @@ .acl-text { color: black; - margin-top: 2; + margin-top: 2px; text-align: left; } } @@ -119,7 +119,7 @@ .preferences-color-text { margin-top: 3px; - margin-right: 4; + margin-right: 4px; flex: 1 1 auto; text-align: left; } @@ -134,8 +134,8 @@ margin-top: 2px; .preferences-font-text { - margin-top: 4; - margin-right: 4; + margin-top: 4px; + margin-right: 4px; margin-bottom: 2px; text-align: left; } @@ -149,7 +149,7 @@ .font-select { height: 35px; font-size: 9; - margin-right: 6; + margin-right: 6px; border-radius: 5px; width: 65%; @@ -171,9 +171,9 @@ } .preferences-check { - margin-right: 4; + margin-right: 4px; margin-bottom: -3; - margin-left: 5; + margin-left: 5px; margin-top: -1px; display: inline-block; padding-left: 5px; @@ -205,14 +205,14 @@ .error-text { color: #c40233; font-size: 10; - margin-bottom: 4; + margin-bottom: 4px; } .success-text { - width: 300; + width: 300px; margin-left: -20; font-size: 10; - margin-bottom: 4; + margin-bottom: 4px; margin-top: -3; color: #009f6b; } @@ -225,7 +225,7 @@ text-transform: uppercase; letter-spacing: 2px; font-size: 19; - margin-top: 0; + margin-top: 0px; font-weight: bold; } @@ -246,7 +246,7 @@ font-style: normal; font-size: 15; font-weight: normal; - padding: 0; + padding: 0px; .padding { padding: 0 0 0 20px; @@ -273,8 +273,8 @@ align-items: center; flex-direction: column; bottom: 10px; - left: 0; - right: 0; + left: 0px; + right: 0px; .settings-username { padding-right: 0px; diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index c9f86f0e1..553f3a184 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -4,7 +4,7 @@ .select { text-align: justify; - text-align-last: end + text-align-last: end; } .sharing-contents { @@ -36,15 +36,14 @@ } .share-container { - .share-setup { display: flex; margin-bottom: 20px; align-items: center; - max-height: 28; + max-height: 28px; .user-search { - max-height: 30; + max-height: 30px; width: 90%; display: inline-flex; flex-direction: row; @@ -68,7 +67,7 @@ text-align-last: end; font-size: 13px; min-width: 90px; - height: 28; + height: 28px; margin-left: 2px; } @@ -99,8 +98,8 @@ display: flex; float: right; align-items: baseline; - margin-top: -12; - margin-bottom: 10; + margin-top: -12px; + margin-bottom: 10px; .layoutDoc-acls, .myDocs-acls { @@ -109,7 +108,7 @@ label { font-weight: normal; font-style: italic; - padding-right: 12; + padding-right: 12px; } input { @@ -127,13 +126,13 @@ .group-container { width: 50%; display: flex; - top:0; + top: 0px; flex-direction: column; border-radius: 4px; .user-sort { text-align: left; - margin-left: 10; + margin-left: 10px; width: 100%; cursor: pointer; } @@ -180,7 +179,7 @@ padding: 0 10; margin: 0 5; transition: transform 0.2s; - height: 25; + height: 25px; } } @@ -205,7 +204,7 @@ .people-with { font-size: 14px; - margin: 0; + margin: 0px; padding-top: 3px; font-style: normal; } @@ -216,30 +215,30 @@ } } - .title-individual{ + .title-individual { height: 25px; - padding-left: 2; + padding-left: 2px; width: 97%; border-radius: 4px; margin-top: 10px; margin-left: -8px; font-size: 14; - margin-bottom: -4; + margin-bottom: -4px; border: 2px solid; border-bottom: none; align-items: center; display: flex; } - .title-group{ + .title-group { height: 25px; - padding-left: 2; + padding-left: 2px; width: 97%; border-radius: 4px; margin-top: 10px; margin-left: -8px; font-size: 14; - margin-bottom: -4; + margin-bottom: -4px; border: 2px solid; border-bottom: none; align-items: center; @@ -289,18 +288,17 @@ text-align: right; margin-left: auto; margin-right: -12px; - } .edit-actions { display: flex; position: absolute; align-items: flex-end; - right: -10; + right: -10px; } } - .permissions-dropdown-None{ + .permissions-dropdown-None { height: 100%; min-width: 85px; text-align: right; @@ -325,7 +323,7 @@ border-radius: 6px; border: 1px solid rgb(75, 75, 5); } - .permissions-dropdown-Augment{ + .permissions-dropdown-Augment { height: 100%; min-width: 85px; text-align: right; @@ -333,12 +331,11 @@ padding: 0px; padding-left: 3px; background: rgb(208, 255, 208); - color:rgb(19, 80, 19); + color: rgb(19, 80, 19); border-radius: 6px; border: 1px solid rgb(19, 80, 19); - } - .permissions-dropdown-View{ + .permissions-dropdown-View { height: 100%; min-width: 85px; text-align: right; @@ -350,7 +347,7 @@ border-radius: 6px; border: 1px solid rgb(25, 25, 101); } - .permissions-dropdown-Not-Shared{ + .permissions-dropdown-Not-Shared { height: 100%; min-width: 85px; text-align: right; @@ -398,4 +395,4 @@ // padding-top: 12px; // } // } -}
\ No newline at end of file +} diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 7da0281c0..74a3be83d 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -98,7 +98,6 @@ export class TrackMovements { // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { - // eslint-disable-next-line no-restricted-syntax for (const DashDoc of tabbedDocs) { if (!this.recordingFFViews.has(DashDoc)) { if (isFFView(DashDoc)) { @@ -112,7 +111,6 @@ export class TrackMovements { } // tab was removed - need to remove it from recordingFFViews else if (tabbedFFViews.size < this.recordingFFViews.size) { - // eslint-disable-next-line no-restricted-syntax for (const [doc] of this.recordingFFViews) { if (!tabbedFFViews.has(doc)) { this.removeRecordingFFView(doc); diff --git a/src/client/util/clamp.js b/src/client/util/clamp.js index 9c7fd78a4..8d74b5636 100644 --- a/src/client/util/clamp.js +++ b/src/client/util/clamp.js @@ -1,15 +1,15 @@ -"use strict"; +'use strict'; -Object.defineProperty(exports, "__esModule", { - value: true +Object.defineProperty(exports, '__esModule', { + value: true, }); -exports.default = clamp; function clamp(min, val, max) { - if (val < min) { - return min; - } - if (val > max) { - return max; - } - return val; -}
\ No newline at end of file + if (val < min) { + return min; + } + if (val > max) { + return max; + } + return val; +} +exports.default = clamp; diff --git a/src/client/util/convertToCSSPTValue.js b/src/client/util/convertToCSSPTValue.js index 66f8db5a1..032385d81 100644 --- a/src/client/util/convertToCSSPTValue.js +++ b/src/client/util/convertToCSSPTValue.js @@ -3,11 +3,8 @@ Object.defineProperty(exports, '__esModule', { value: true, }); -exports.PT_TO_PX_RATIO = exports.PX_TO_PT_RATIO = undefined; -exports.default = convertToCSSPTValue; -exports.toClosestFontPtSize = toClosestFontPtSize; -var SIZE_PATTERN = /([\d\.]+)(px|pt)/i; +var SIZE_PATTERN = /([\d.]+)(px|pt)/i; var PX_TO_PT_RATIO = (exports.PX_TO_PT_RATIO = 0.7518796992481203); // 1 / 1.33. var PT_TO_PX_RATIO = (exports.PT_TO_PX_RATIO = 1.33); @@ -39,3 +36,7 @@ function toClosestFontPtSize(styleValue) { return Math.abs(curr - originalPTValue) < Math.abs(prev - originalPTValue) ? curr : prev; }, Number.NEGATIVE_INFINITY); } + +exports.PT_TO_PX_RATIO = exports.PX_TO_PT_RATIO = undefined; +exports.default = convertToCSSPTValue; +exports.toClosestFontPtSize = toClosestFontPtSize; diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss index 806741c22..65894fbd6 100644 --- a/src/client/util/reportManager/ReportManager.scss +++ b/src/client/util/reportManager/ReportManager.scss @@ -14,8 +14,8 @@ } h2 { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; font-size: 24px; } } @@ -27,8 +27,8 @@ gap: 4px; h2 { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; padding-bottom: 8px; font-size: 24px; } @@ -92,7 +92,7 @@ outline: none; border-bottom: 1px solid; padding: 8px; - padding-left: 0; + padding-left: 0px; transition: all 0.2s ease; background: transparent; @@ -178,15 +178,15 @@ cursor: pointer; font-size: 14px; font-weight: 400; - padding: 0; - margin: 0; + padding: 0px; + margin: 0px; } .issue-title { font-size: 16px; font-weight: 500; - padding: 0; - margin: 0; + padding: 0px; + margin: 0px; } } @@ -211,8 +211,8 @@ .file-list { box-sizing: border-box; - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; font-size: 14px; width: 100%; overflow-x: auto; @@ -251,8 +251,8 @@ .issue-title { font-size: 24px; - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; } .issue-date { @@ -314,8 +314,8 @@ .report-media-content::after { content: ''; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */ diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index a6b5911f7..87e8a8bf1 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-unused-class-component-methods */ import { Octokit } from '@octokit/core'; import { Button, Dropdown, DropdownType, IconButton, Type } from '@dash/components'; import { action, makeObservable, observable } from 'mobx'; @@ -31,7 +30,6 @@ export class ReportManager extends React.Component<object> { @observable private isOpen = false; @observable private query = ''; - // eslint-disable-next-line react/sort-comp @action private setQuery = (q: string) => { this.query = q; }; diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index d22c4d096..337c976cb 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -68,8 +68,8 @@ width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 0; filter: opacity(0); } @@ -187,10 +187,10 @@ display: flex; .close-menu { - margin-top: 0; - margin-bottom: 0; - margin-right: 0; - padding: 0; + margin-top: 0px; + margin-bottom: 0px; + margin-right: 0px; + padding: 0px; margin-left: auto; z-index: 999999999; width: 20px; diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index daa711bc4..d74441b9c 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -79,8 +79,8 @@ $dashboard-container-width: 250px; position: absolute; width: 100%; height: 100%; - left: 0; - top: 0; + left: 0px; + top: 0px; z-index: -1; } } @@ -137,8 +137,8 @@ $dashboard-container-width: 250px; position: absolute; width: 100%; height: 100%; - left: 0; - top: 0; + left: 0px; + top: 0px; z-index: -1; } } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 55eb0b127..25a806abd 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -11,8 +11,8 @@ $resizeHandler: 8px; // Rotation handler .documentDecorations-rotation { border-radius: 100%; - height: 30; - width: 30; + height: 30px; + width: 30px; right: -40; bottom: -20; //top: calc(50% - 15px); @@ -45,15 +45,15 @@ $resizeHandler: 8px; width: 100%; pointer-events: all; border-radius: 50%; - top: 30; // offset by height of documentButtonBar so that items can be clicked without overlap interference + top: 30px; // offset by height of documentButtonBar so that items can be clicked without overlap interference color: black; } } .documentDecorations-container { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; transform-origin: 50% calc(50% + 10px); display: grid; grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler; @@ -121,7 +121,7 @@ $resizeHandler: 8px; } > svg { - margin: 0; + margin: 0px; } } @@ -150,7 +150,7 @@ $resizeHandler: 8px; } > svg { - margin: 0; + margin: 0px; } } &:hover { @@ -185,7 +185,7 @@ $resizeHandler: 8px; } > svg { - margin: 0; + margin: 0px; } } @@ -246,7 +246,7 @@ $resizeHandler: 8px; display: inline; position: relative; top: -2.5; - left: 35; + left: 35px; zoom: 0.7; } @@ -360,8 +360,8 @@ $resizeHandler: 8px; left: 7px; top: 7px; background: global.$medium-gray; - height: 10; - width: 10; + height: 10px; + width: 10px; opacity: 0.5; pointer-events: all; cursor: nwse-resize; @@ -371,8 +371,8 @@ $resizeHandler: 8px; position: relative; background: black; color: rgb(145, 144, 144); - height: 20; - width: 20; + height: 20px; + width: 20px; pointer-events: all; margin: auto; display: flex; @@ -380,8 +380,8 @@ $resizeHandler: 8px; border-radius: 100%; cursor: default; svg { - width: 10; - height: 10; + width: 10px; + height: 10px; margin: auto; } } @@ -389,7 +389,7 @@ $resizeHandler: 8px; .documentDecorations-rotationPath { position: absolute; width: 100%; - height: 0; + height: 0px; transform: translate(0px, -25%); padding-bottom: 100%; border-radius: 100%; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 69c2467a3..7a9f6c514 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,7 +1,7 @@ +import { IconButton } from '@dash/components'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { IconButton } from '@dash/components'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -27,15 +27,13 @@ import './DocumentDecorations.scss'; import { InkStrokeProperties } from './InkStrokeProperties'; import { InkingStroke } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { TagsView } from './TagsView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { Colors } from './global/globalEnums'; import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from './nodes/DocumentView'; -import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; -import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { TagsView } from './TagsView'; interface DocumentDecorationsProps { PanelWidth: number; @@ -430,7 +428,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onPointerDown = (e: React.PointerEvent): void => { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them DocumentView.Selected() - .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .filter(dv => e.shiftKey && dv.ComponentView?.isOutpaintable?.()) .forEach(dv => { dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width); dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height); @@ -486,7 +484,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora this._interactionLock = true; this._snapPt = thisPt; - const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : []; + const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView?.isOutpaintable?.()) : []; const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected(); // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI) @@ -750,7 +748,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; // Radius constants - const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; + const useRounding = seldocview.ComponentView?.showBorderRounding?.(); const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null)); const docMax = Math.min(NumCast(seldocview.Document._width) / 2, NumCast(seldocview.Document._height) / 2); const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); @@ -911,8 +909,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora transform: `rotate(${rotation}deg)`, width: this.Bounds.r - this.Bounds.x + 'px', height: this.Bounds.b - this.Bounds.y + 'px', - left: this.Bounds.x, - top: this.Bounds.y, + left: this.Bounds.r, + top: this.Bounds.b, pointerEvents: 'none', }}> {this._isRotating ? null : ( diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss index fa4542ac4..333939d03 100644 --- a/src/client/views/EditableView.scss +++ b/src/client/views/EditableView.scss @@ -6,14 +6,14 @@ overflow-y: auto; height: 100%; width: 100%; - min-width: 20; + min-width: 20px; text-overflow: ellipsis; - -ms-overflow-style: none; - scrollbar-width: none; + -ms-overflow-style: none; + scrollbar-width: none; } .editableView-container-editing::-webkit-scrollbar { - display: none; + display: none; } .editableView-container-editing-oneLine { @@ -44,4 +44,3 @@ border: none; outline: none; } - diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index 508b1ee1f..e32db000f 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -1,4 +1,3 @@ - .filterBox-flyout { display: block; text-align: left; @@ -29,7 +28,7 @@ // .filterBox-bottom { // // position: fixed; -// // bottom: 0; +// // bottom: 0px; // // width: 100%; // } @@ -88,7 +87,7 @@ // padding-bottom: 20px; // border-bottom: 2px solid black; // position: fixed; -// top: 0; +// top: 0px; // width: 100%; // } @@ -154,8 +153,8 @@ flex-direction: column; width: 100%; position: relative; - right: 0; - top: 0; + right: 0px; + top: 0px; z-index: 1; // background-color: #9f9f9f; @@ -240,60 +239,52 @@ transition: all 0.3s ease-out; display: flex; flex-direction: row; - padding: 5px; - + padding: 5px; - &:hover{ + &:hover { border-color: #e9e9e9; - background-color: #6d6c6c + background-color: #6d6c6c; } - .hotKey-icon, .hotKey-close{ + .hotKey-icon, + .hotKey-close { background-color: transparent; border-radius: 10%; padding: 5px; - - &:hover{ + &:hover { background-color: #616060; } } - .hotKey-close{ + .hotKey-close { right: 30px; - position: fixed; + position: fixed; padding-top: 10px; - -} + } - .hotkey-title{ + .hotkey-title { top: 6px; position: relative; cursor: text; - } - .hotkey-title-input{ + .hotkey-title-input { background-color: transparent; border: none; border-color: transparent; outline: none; cursor: text; - } } .hotKeyButtons { position: relative; width: 100%; - } .hotKey-icon-button { - - background-color: transparent; - - + background-color: transparent; } .icon-panel { @@ -305,24 +296,19 @@ border-radius: 10%; background-color: #323232; - .icon-panel-button{ + .icon-panel-button { background-color: #323232; border-radius: 10%; - - &:hover{ - background-color:#7a7878 + &:hover { + background-color: #7a7878; } } - - - } - // .sliderBox-outerDiv { // width: 30%;// width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box -// height: 40; // height: 100%; +// height: 40px; // height: 100%; // border-radius: inherit; // display: flex; // flex-direction: column; diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index bfe2d5c64..0fa3fd973 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -3,7 +3,7 @@ height: 100%; position: absolute; touch-action: none; - top: 0; + top: 0px; .pointerBubbles { width: 100%; diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index c672824bf..0595283fc 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -8,8 +8,8 @@ svg:not(:root) { overflow: visible !important; position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; } } diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 577acc4d1..0af46df5d 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -105,7 +105,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> { return ( <> {tangentHandles.map((pts, i) => ( - // eslint-disable-next-line react/no-array-index-key <svg height="10" width="10" key={`hdl${i}`}> <circle cx={pts.X} @@ -135,7 +134,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> { /> ); return ( - // eslint-disable-next-line react/no-array-index-key <svg height="100" width="100" key={`line${i}`}> {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss index 3e65843df..7a481d887 100644 --- a/src/client/views/LightboxView.scss +++ b/src/client/views/LightboxView.scss @@ -1,12 +1,12 @@ .lightboxView-navBtn { margin: auto; position: absolute; - right: 19; - top: 10; + right: 19px; + top: 10px; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 25; + width: 25px; flex-direction: column; display: flex; &:hover { @@ -16,12 +16,12 @@ .lightboxView-tabBtn { margin: auto; position: absolute; - right: 54; - top: 10; + right: 54px; + top: 10px; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 25; + width: 25px; flex-direction: column; display: flex; &:hover { @@ -31,12 +31,12 @@ .lightboxView-paletteBtn { margin: auto; position: absolute; - right: 89; - top: 10; + right: 89px; + top: 10px; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 25; + width: 25px; flex-direction: column; display: flex; &:hover { @@ -47,12 +47,12 @@ .lightboxView-penBtn { margin: auto; position: absolute; - right: 124; - top: 10; + right: 124px; + top: 10px; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 25; + width: 25px; flex-direction: column; display: flex; &:hover { @@ -62,12 +62,12 @@ .lightboxView-exploreBtn { margin: auto; position: absolute; - right: 159; - top: 10; + right: 159px; + top: 10px; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 25; + width: 25px; flex-direction: column; display: flex; &:hover { @@ -76,8 +76,8 @@ } .lightboxView-frame { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; z-index: 1000; @@ -91,9 +91,9 @@ margin: auto; position: relative; background: transparent; - border-radius: 8; + border-radius: 8px; opacity: 0.7; - width: 35; + width: 35px; &:hover { opacity: 1; } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index bea1de435..df4160fc1 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -12,10 +12,10 @@ body { overflow: hidden; font-family: global.$sans-serif; font-size: global.$body-text; - margin: 0; + margin: 0px; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } // div { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index db949285b..d5e6b8998 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -10,7 +10,7 @@ body { .dash-tooltip { font-size: 11px; padding: 2px; - max-width: 150; + max-width: 150px; line-height: 150%; } @@ -49,8 +49,8 @@ body { .mainView-snapLines { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; pointer-events: none; @@ -61,8 +61,8 @@ body { height: 100%; position: absolute; pointer-events: all; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 1; touch-action: none; } @@ -86,8 +86,8 @@ body { .properties-container { height: 100%; position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; } .mainView-propertiesDragger-minified, @@ -98,7 +98,7 @@ body { width: 17px; position: absolute; top: 50%; - border-radius: 0; + border-radius: 0px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-right: unset; @@ -141,7 +141,7 @@ body { } .propertiesView { - left: 0; + left: 0px; position: absolute; z-index: 2; // background-color: linen; //$light-gray; @@ -165,7 +165,7 @@ body { } ::-webkit-scrollbar { - width: 0; + width: 0px; } } @@ -173,19 +173,19 @@ body { width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; overflow: hidden; } .buttonContainer { position: absolute; - bottom: 0; + bottom: 0px; .mainView-settings { // position: absolute; - // left: 0; - // bottom: 0; + // left: 0px; + // bottom: 0px; border-radius: 25%; margin-left: -5px; background: darkblue; @@ -198,8 +198,8 @@ body { .mainView-logout { position: absolute; - right: 0; - bottom: 0; + right: 0px; + bottom: 0px; font-size: 8px; } @@ -223,12 +223,12 @@ body { } .mainView-libraryFlyout-close { - right: 6; - top: 5; + right: 6px; + top: 5px; position: absolute; margin-right: 6px; z-index: 10; - margin-bottom: 10; + margin-bottom: 10px; } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 686f8ed88..13b14617c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1039,7 +1039,7 @@ export class MainView extends ObservableReactComponent<object> { @computed get inkResources() { return ( - <svg width={0} height={0}> + <svg width={0} height={0} style={{ display: 'block' }}> <defs> <filter id="inkSelectionHalo"> <feColorMatrix diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss index 28de0b7a5..924476a30 100644 --- a/src/client/views/MetadataEntryMenu.scss +++ b/src/client/views/MetadataEntryMenu.scss @@ -3,7 +3,7 @@ width: 310px; flex-direction: column; - input[type=checkbox] { + input[type='checkbox'] { margin-left: 5px; } } @@ -11,7 +11,7 @@ .metadataEntry-autoSuggester { width: 80%; height: 100%; - margin: 0; + margin: 0px; display: inline-block; } @@ -20,13 +20,13 @@ } .metadataEntry-keys { - max-height: 80; - overflow-y: auto; + max-height: 80px; + overflow-y: auto; display: flex; flex-direction: column; } .metadataEntry-inputArea { - display:inline-block; + display: inline-block; flex-direction: row; } @@ -53,8 +53,8 @@ } .react-autosuggest__input--open { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; } .react-autosuggest__suggestions-container { @@ -78,8 +78,8 @@ } .react-autosuggest__suggestions-list { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; list-style-type: none; } @@ -90,4 +90,4 @@ .react-autosuggest__suggestion--highlighted { background-color: #ddd; -}
\ No newline at end of file +} diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 2e8621b5b..44203e38f 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -1,7 +1,7 @@ .overlayView { position: absolute; pointer-events: none; - top: 0; + top: 0px; width: 100vw; height: 100vh; z-index: 2002; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear @@ -14,8 +14,8 @@ overflow: hidden; display: flex; flex-direction: column; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: all; box-shadow: black 5px 5px 5px; } @@ -46,7 +46,7 @@ float: right; height: 20px; width: 20px; - padding: 0; + padding: 0px; background-color: inherit; } @@ -62,6 +62,6 @@ .overlayView-doc { z-index: 9002; //so that it appears above chroma position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss index 82488c750..eef120c58 100644 --- a/src/client/views/PreviewCursor.scss +++ b/src/client/views/PreviewCursor.scss @@ -2,8 +2,8 @@ color: black; position: absolute; transform-origin: left top; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; opacity: 1; z-index: 1001; diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx index 12a46c7a4..9ea9c3a3d 100644 --- a/src/client/views/PropertiesSection.tsx +++ b/src/client/views/PropertiesSection.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 280de4893..de3012948 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -5,7 +5,7 @@ } .propertiesView-presentationTrails-title-icon { position: absolute; - right: 4; + right: 4px; } .propertiesView-palette { cursor: pointer; @@ -24,7 +24,7 @@ } .propertiesView { height: 100%; - width: 250; + width: 250px; font-family: 'Roboto'; font-size: 12px; cursor: auto; @@ -61,7 +61,7 @@ } .propertiesView-info { - margin-top: -5; + margin-top: -5px; float: right; font-size: 20; path { @@ -78,10 +78,10 @@ display: flex; button { - width: 15; - height: 15; - padding: 0; - margin-top: -5; + width: 15px; + height: 15px; + padding: 0px; + margin-top: -5px; } } @@ -89,8 +89,8 @@ display: flex; button { - width: 5; - height: 5; + width: 5px; + height: 5px; } input { @@ -171,16 +171,16 @@ display: flex; button { - width: 15; - height: 15; - padding: 0; - margin-top: -5; + width: 15px; + height: 15px; + padding: 0px; + margin-top: -5px; } } button { - width: 5; - height: 5; + width: 5px; + height: 5px; } input { @@ -301,8 +301,8 @@ padding: 7px; border-radius: 7px; margin-right: 32px; - width: 32; - height: 32; + width: 32px; + height: 32px; padding-top: 9px; margin-left: 18px; @@ -318,8 +318,8 @@ padding: 7px; border-radius: 7px; margin-right: 32px; - width: 32; - height: 32; + width: 32px; + height: 32px; padding-top: 9px; padding-left: 10px; @@ -334,8 +334,8 @@ background-color: #333333; padding: 7px; border-radius: 7px; - width: 32; - height: 32; + width: 32px; + height: 32px; padding-top: 9px; padding-left: 10px; @@ -410,7 +410,7 @@ .color-palette { width: 160px; - height: 360; + height: 360px; } .strokeAndFill { @@ -465,7 +465,7 @@ .propertiesView-selectedList { min-width: max-content; width: 100%; - max-height: 180; + max-height: 180px; overflow: hidden; overflow-y: scroll; border-left: solid 1px darkgrey; @@ -475,7 +475,7 @@ .selectedList-items { font-size: 12; font-weight: 300; - margin-top: 1; + margin-top: 1px; } } } @@ -500,7 +500,7 @@ .width-range { margin-right: 1px; - margin-bottom: 6; + margin-bottom: 6px; } } diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss index abfd04f11..c2b9dcce5 100644 --- a/src/client/views/SidebarAnnos.scss +++ b/src/client/views/SidebarAnnos.scss @@ -2,7 +2,7 @@ position: absolute; width: 100%; height: 100%; - right: 0; + right: 0px; .sidebarAnnos-stacking { width: 100%; position: relative; @@ -20,12 +20,12 @@ .sidebarAnnos-filterUser-active { font-weight: bold; font-size: 10px; - padding-left: 5; - padding-right: 5; + padding-left: 5px; + padding-right: 5px; box-shadow: black 1px 1px 3px; - border-radius: 5; - margin: 2; - height: 15; + border-radius: 5px; + margin: 2px; + height: 15px; background-color: lightgrey; } .sidebarAnnos-filterUser, diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 99796f1fb..cbb1fd5d5 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -4,11 +4,11 @@ .styleProvider-lock { z-index: 2; // has to be above title which is z-index 1 font-size: 10; - width: 20; - height: 20; + width: 20px; + height: 20px; position: absolute; - right: -20; - top: 0; + right: -20px; + top: 0px; background: black; pointer-events: all; opacity: 0.3; @@ -20,10 +20,10 @@ cursor: default; } .styleProvider-filter { - right: 20; + right: 20px; .styleProvider-filterShift { - left: 0; - top: 0; + left: 0px; + top: 0px; position: absolute; } .dropdown-container { @@ -33,10 +33,10 @@ } .styleProvider-paint-selected, .styleProvider-paint { - top: 15; + top: 15px; } .styleProvider-paint-selected { - right: -40; + right: -40px; } .styleProvider-lock:hover, .styleProvider-filter:hover { @@ -45,8 +45,8 @@ .styleProvider-treeView-icon, .styleProvider-treeView-icon-active { - margin-left: 0; - margin-right: 0; + margin-left: 0px; + margin-right: 0px; } .styleProvider-treeView-icon { diff --git a/src/client/views/StyleProviderQuiz.scss b/src/client/views/StyleProviderQuiz.scss index 84b3f1fef..53ca34c1b 100644 --- a/src/client/views/StyleProviderQuiz.scss +++ b/src/client/views/StyleProviderQuiz.scss @@ -13,8 +13,8 @@ .check-icon { position: absolute; - right: 40; - bottom: 10; + right: 40px; + bottom: 10px; color: green; display: inline-block; font-size: 20px; @@ -23,8 +23,8 @@ .redo-icon { position: absolute; - right: 10; - bottom: 10; + right: 10px; + bottom: 10px; color: black; display: inline-block; font-size: 20px; diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index d8dab8e89..514dc4ae8 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -24,6 +24,9 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React promoteCollection?: () => void; // moves contents of collection to parent hasChildDocs?: () => Doc[]; docEditorView?: () => void; + autoTag?: () => void; // auto tag the document + isOutpaintable?: () => boolean; // can document be resized and outpainted + showBorderRounding?: () => boolean; // can document borders be rounded showSmartDraw?: (x: number, y: number, regenerate?: boolean) => void; updateIcon?: (usePanelDimensions?: boolean) => Promise<void>; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) diff --git a/src/client/views/animationtimeline/Timeline.scss b/src/client/views/animationtimeline/Timeline.scss index e1d3b190c..3a50183d2 100644 --- a/src/client/views/animationtimeline/Timeline.scss +++ b/src/client/views/animationtimeline/Timeline.scss @@ -112,8 +112,8 @@ $timelineDark: #77a1aa; input { position: absolute; opacity: 0; - height: 0; - width: 0; + height: 0px; + width: 0px; } .round-toggle-slider { diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx index 0d7873931..79283479c 100644 --- a/src/client/views/animationtimeline/TimelineMenu.tsx +++ b/src/client/views/animationtimeline/TimelineMenu.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -19,7 +17,7 @@ export class TimelineMenu extends React.Component { @observable private _y = 0; @observable private _currentMenu: JSX.Element[] = []; - constructor(props: any) { + constructor(props: object) { super(props); makeObservable(this); TimelineMenu.Instance = this; diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx index 7bf685c9e..fff756980 100644 --- a/src/client/views/animationtimeline/TimelineOverview.tsx +++ b/src/client/views/animationtimeline/TimelineOverview.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-unused-prop-types */ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -14,7 +13,7 @@ interface TimelineOverviewProps { isAuthoring: boolean; parent: Timeline; changeCurrentBarX: (pixel: number) => void; - movePanX: (pixel: number) => any; + movePanX: (pixel: number) => void; time: number; tickSpacing: number; tickIncrement: number; @@ -139,7 +138,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> { const percentVisible = this.visibleTime / this.props.time; const visibleBarWidth = percentVisible * this.activeOverviewWidth; - const percentScrubberStart = this.currentX / this.props.time; + // const percentScrubberStart = this.currentX / this.props.time; let scrubberStart = (this.props.currentBarX / this.props.totalLength) * this.activeOverviewWidth; if (scrubberStart > this.activeOverviewWidth) scrubberStart = this.activeOverviewWidth; diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index e6cc398af..981e528cc 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -18,7 +18,7 @@ } .collectionCardView-flashcardUI { - top: 0; + top: 0px; position: absolute; width: 100%; height: 100%; @@ -33,7 +33,7 @@ } .collectionCardView-cardSizeDragger { position: absolute; - top: 0; + top: 0px; width: 28px; height: 28px; > svg { diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss index 13e6b54c2..361d88cb6 100644 --- a/src/client/views/collections/CollectionCarousel3DView.scss +++ b/src/client/views/collections/CollectionCarousel3DView.scss @@ -12,7 +12,7 @@ position: absolute; top: global.$CAROUSEL3D_TOP * 1%; height: (global.$CAROUSEL3D_SIDE_SCALE * 100) * 1%; - align-items: center; + //align-items: center; transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); .collectionCarousel3DView-item, @@ -67,8 +67,8 @@ .carousel3DView-fwd-scroll-hidden { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -78,7 +78,7 @@ .carousel3DView-fwd, .carousel3DView-back { - top: 0; + top: 0px; background: transparent; width: calc((1 - #{global.$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%); height: 100%; @@ -94,13 +94,13 @@ .carousel3DView-fwd, .carousel3DView-fwd-scroll, .carousel3DView-fwd-scroll-hidden { - right: 0; + right: 0px; } .carousel3DView-back, .carousel3DView-back-scroll, .carousel3DView-back-scroll-hidden { - left: 0; + left: 0px; } .carousel3DView-fwd-scroll-hidden, diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 962b590c8..4c999b6dd 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -6,10 +6,10 @@ transform-origin: top left; .collectionCarouselView-caption { - height: 50; + height: 50px; display: inline-block; width: 100%; - bottom: 0; + bottom: 0px; position: absolute; } .collectionCarouselView-image { @@ -18,8 +18,8 @@ width: 100%; user-select: none; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } } .collectionCarouselView-recentlyMissed { @@ -34,8 +34,8 @@ .carouselView-fwd { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -47,12 +47,12 @@ } .carouselView-fwd { top: calc(50% - 15px); - right: 0; + right: 0px; transform-origin: right top; } .carouselView-back { top: calc(50% - 15px); - left: 0; + left: 0px; transform-origin: top left; } .carouselView-back:hover, diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 7c19d39da..de214e2ef 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -17,8 +17,8 @@ } .lm_maximised { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 40; } .lm_maximise_placeholder { @@ -62,8 +62,8 @@ text-align: center; } .lm_header ul { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; list-style-type: none; } .lm_header .lm_tab { @@ -81,11 +81,11 @@ position: absolute; } .lm_header .lm_tab i.lm_left { - top: 0; + top: 0px; left: -2px; } .lm_header .lm_tab i.lm_right { - top: 0; + top: 0px; right: -2px; } .lm_header .lm_tab .lm_title { @@ -97,8 +97,8 @@ width: 14px; height: 14px; position: absolute; - top: 0; - right: 0; + top: 0px; + right: 0px; text-align: center; } .lm_stack.lm_left .lm_header, @@ -118,14 +118,14 @@ .lm_stack.lm_left .lm_header .lm_tabs, .lm_stack.lm_right .lm_header .lm_tabs { transform-origin: left top; - top: 0; + top: 0px; width: 1000px; } .lm_dragProxy.lm_left .lm_header .lm_controls, .lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_left .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls { - bottom: 0; + bottom: 0px; } .lm_dragProxy.lm_left .lm_items, .lm_dragProxy.lm_right .lm_items, @@ -136,7 +136,7 @@ .lm_dragProxy.lm_left .lm_header .lm_tabs, .lm_stack.lm_left .lm_header .lm_tabs { transform: rotate(-90deg) scaleX(-1); - left: 0; + left: 0px; } .lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab, .lm_stack.lm_left .lm_header .lm_tabs .lm_tab { @@ -156,7 +156,7 @@ .lm_stack.lm_right .lm_header .lm_tabs { transform: rotate(90deg) scaleX(1); left: 100%; - margin-left: 0; + margin-left: 0px; } .lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls { @@ -169,7 +169,7 @@ } .lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab { - margin-top: 0; + margin-top: 0px; border-top: none; } .lm_dragProxy.lm_bottom .lm_header .lm_controls, @@ -189,8 +189,8 @@ } .lm_header .lm_controls .lm_tabdropdown:before { content: ''; - width: 0; - height: 0; + width: 0px; + height: 0px; vertical-align: middle; display: inline-block; border-top: 5px dashed; @@ -201,14 +201,14 @@ .lm_header .lm_tabdropdown_list { position: absolute; top: 20px; - right: 0; + right: 0px; z-index: 5; overflow: hidden; } .lm_header .lm_tabdropdown_list .lm_tab { clear: both; padding-right: 10px; - margin: 0; + margin: 0px; } .lm_header .lm_tabdropdown_list .lm_tab .lm_title { width: 100px; @@ -218,8 +218,8 @@ } .lm_dragProxy { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 30; } .lm_dragProxy .lm_header { @@ -238,32 +238,32 @@ width: 100%; height: 100%; position: relative; - top: 0; - left: 0; + top: 0px; + left: 0px; } .lm_transition_indicator { display: none; width: 20px; height: 20px; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 20; } .lm_popin { width: 20px; height: 20px; position: absolute; - bottom: 0; - right: 0; + bottom: 0px; + right: 0px; z-index: 9999; } .lm_popin > * { width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } .lm_popin > .lm_bg { z-index: 10; @@ -307,7 +307,7 @@ width: max-content; height: 100%; display: flex; - max-width: 100; + max-width: 100px; text-overflow: ellipsis; } @@ -328,7 +328,7 @@ ul.lm_tabs::before { content: ' '; position: absolute; - bottom: 0; + bottom: 0px; width: 100%; z-index: 1; pointer-events: none; @@ -349,9 +349,9 @@ ul.lm_tabs::before { } } .lm_header .lm_tab.lm_active { - padding: 0; + padding: 0px; opacity: 1; - margin: 0; + margin: 0px; box-shadow: none; height: 27px; margin-right: 2px; @@ -405,7 +405,7 @@ ul.lm_tabs::before { } .lm_drag_tab { - padding: 0; + padding: 0px; width: 15px !important; height: 15px !important; position: relative !important; @@ -418,7 +418,7 @@ ul.lm_tabs::before { .lm_close_tab { display: inline-flex !important; - padding: 0; + padding: 0px; opacity: 1 !important; align-self: center; margin-right: 5px; @@ -455,7 +455,7 @@ ul.lm_tabs::before { content: 'x'; margin: auto; position: relative; - top: -2; + top: -2px; font-size: medium; font-family: sans-serif; } @@ -478,8 +478,8 @@ ul.lm_tabs::before { width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; .collectionDockingView-drag { touch-action: none; @@ -514,7 +514,7 @@ ul.lm_tabs::before { border-bottom-left-radius: 10px; background: #93939347; z-index: 100; - //left: -3; + //left: -3px; &:hover { background: gray; color: white !important; @@ -524,7 +524,7 @@ ul.lm_tabs::before { content: '+'; margin: auto; font-size: x-large; - top: -4; + top: -4px; position: relative; } .lm_maximise { @@ -548,10 +548,10 @@ ul.lm_tabs::before { } .flexlayout__layout { - left: 0; - top: 0; - right: 0; - bottom: 0; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; position: absolute; overflow: hidden; } @@ -691,8 +691,8 @@ ul.lm_tabs::before { .flexlayout__tabset_header { position: absolute; - left: 0; - right: 0; + left: 0px; + right: 0px; color: #eee; background-color: #212121; padding: 3px 3px 3px 5px; @@ -702,17 +702,17 @@ ul.lm_tabs::before { .flexlayout__tab_header_inner { position: absolute; - left: 0; - top: 0; - bottom: 0; + left: 0px; + top: 0px; + bottom: 0px; width: 10000px; } .flexlayout__tab_header_outer { background-color: global.$dark-gray; position: absolute; - left: 0; - right: 0; + left: 0px; + right: 0px; /*top: 0px;*/ /*height: 100px;*/ overflow: hidden; @@ -731,23 +731,23 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } .flexlayout__tab_toolbar_button-min { width: 20px; height: 20px; border: none; - outline-width: 0; + outline-width: 0px; } .flexlayout__tab_toolbar_button-max { width: 20px; height: 20px; border: none; - outline-width: 0; + outline-width: 0px; } .flexlayout__popup_menu_item { @@ -870,9 +870,9 @@ ul.lm_tabs::before { display: flex; flex-direction: column-reverse; align-items: center; - bottom: 0; - left: 0; - right: 0; + bottom: 0px; + left: 0px; + right: 0px; } .flexlayout__border_toolbar_right { @@ -880,9 +880,9 @@ ul.lm_tabs::before { display: flex; flex-direction: column-reverse; align-items: center; - bottom: 0; - left: 0; - right: 0; + bottom: 0px; + left: 0px; + right: 0px; } .flexlayout__border_toolbar_top { @@ -890,9 +890,9 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } .flexlayout__border_toolbar_bottom { @@ -900,8 +900,8 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } } diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index d1f7971d4..164c6e831 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -7,6 +7,7 @@ import { emptyFunction, numberRange } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; +import { StrCast } from '../../../fields/Types'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; @@ -16,6 +17,7 @@ import { undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView } from '../nodes/DocumentView'; +import { ImportElementBox } from '../nodes/importBox/ImportElementBox'; import { CollectionStackingView } from './CollectionStackingView'; import './CollectionStackingView.scss'; @@ -254,7 +256,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor(this._props.panelWidth() / this._props.columnWidth()))); return this.collapsed ? null : ( <div style={{ position: 'relative' }}> - {!this._props.chromeHidden ? ( + {!this._props.chromeHidden && !StrCast(this._props.Doc.childLayoutString).includes(ImportElementBox.name) ? ( <div className="collectionStackingView-addDocumentButton"> <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" /> </div> diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index 0d24a56b5..231085338 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -82,7 +82,7 @@ height: 100%; width: 100%; position: absolute; - top: 0; + top: 0px; overflow-y: auto; overflow-x: hidden; transition: top 0.5s; @@ -130,8 +130,8 @@ display: flex; flex-direction: column; align-items: center; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; position: absolute; margin: auto; @@ -152,20 +152,20 @@ } .collectionNoteTakingView-columnDragger { - width: 15; - height: 15; + width: 15px; + height: 15px; position: absolute; - margin-left: -5; + margin-left: -5px; } .collectionNoteTakingView-sectionDelete { display: none; position: absolute; - right: 0; + right: 0px; width: max-content; height: max-content; - top: 10; - padding: 2; + top: 10px; + padding: 2px; } // Documents in NoteTaking view @@ -210,8 +210,8 @@ height: 5px; &.active { - margin-left: 0; - margin-right: 0; + margin-left: 0px; + margin-right: 0px; background: red; } } @@ -303,8 +303,8 @@ .collectionNoteTakingView-sectionColor { position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; height: 100%; display: none; @@ -345,8 +345,8 @@ .collectionNoteTakingView-sectionOptions { position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; height: 100%; display: none; diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index d05c0ffde..e71df2164 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -38,7 +38,7 @@ height: 100%; background-color: global.$dark-gray; opacity: 0.3; - top: 0; + top: 0px; } .collectionStackedTimeline-trim-controls { @@ -49,8 +49,8 @@ display: flex; justify-content: space-between; max-width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; .collectionStackedTimeline-trim-handle { background-color: global.$medium-blue; @@ -106,18 +106,18 @@ .collectionStackedTimeline-resizer { background: global.$dark-gray; position: absolute; - top: 0; + top: 0px; height: 100%; width: 10px; pointer-events: all; z-index: 100; } .collectionStackedTimeline-resizer { - right: 0; + right: 0px; cursor: e-resize; } .collectionStackedTimeline-left-resizer { - left: 0; + left: 0px; cursor: w-resize; } } @@ -126,8 +126,8 @@ position: absolute; width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; } } diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index d6e4943ff..2cf361847 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -16,10 +16,10 @@ width: 100%; .collectionStackingView-columnDragger { - width: 28; - height: 28; - position: absolute; - margin-left: -5; + width: 28px; + height: 28px; + position: relative; + margin-left: -5px; z-index: 10; > svg { width: 100%; @@ -55,7 +55,7 @@ height: 100%; width: 100%; position: absolute; - top: 0; + top: 0px; overflow-y: auto; overflow-x: hidden; transition: top 0.5s; @@ -93,8 +93,8 @@ .collectionStackingView-masonryGrid { width: 100%; display: grid; - top: 0; - left: 0; + top: 0px; + left: 0px; } .collectionStackingView-masonrySingle { @@ -115,8 +115,8 @@ position: absolute; display: flex; flex-direction: column; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; position: absolute; } @@ -159,12 +159,12 @@ width: 100%; display: none; position: absolute; - top: 0; + top: 0px; cursor: default; &.active { - margin-left: 0; - margin-right: 0; + margin-left: 0px; + margin-right: 0px; background: red; } } @@ -262,8 +262,8 @@ .collectionStackingView-sectionColor { position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; height: 100%; display: none; @@ -304,8 +304,8 @@ .collectionStackingView-sectionOptions { position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; height: 100%; display: none; @@ -339,7 +339,7 @@ .collectionStackingView-sectionDelete { position: absolute; right: 0px; - top: 0; + top: 0px; height: 100%; display: none; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4a0ddc631..bdeb7d944 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -64,7 +64,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @observable _cursor: CSS.Property.Cursor = 'ew-resize'; // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling - // does this mean whether the browser is hidden? Or is chrome something else entirely? + // whether ui/editing controls are shown @computed get chromeHidden() { return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } @@ -566,7 +566,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return ( <div key={(heading?.heading ?? '') + 'head'}> {this._props.isContentActive() && !this.isStackingView && !this.chromeHidden ? this.columnDragger : null} - <div style={{ top: this.yMargin }}> + <div style={{ position: 'relative' }}> <CollectionMasonryViewFieldRow showHandle={first} Doc={this.Document} @@ -715,9 +715,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string, pointerEvents: this._props.pointerEvents?.() ?? this.backgroundEvents, }} - onScroll={action(e => { - this._scroll = e.currentTarget.scrollTop; - })} + onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.isContentActive() && e.stopPropagation()}> diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 8c7cb8276..8c535534a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { DivHeight, DivWidth, returnEmptyString, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; @@ -26,7 +27,6 @@ import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackingView.scss'; -import { DocData } from '../../../fields/DocSymbols'; // So this is how we are storing a column interface CSVFieldColumnProps { diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss index d995cbcd2..d56999974 100644 --- a/src/client/views/collections/CollectionTimeView.scss +++ b/src/client/views/collections/CollectionTimeView.scss @@ -27,7 +27,7 @@ transform: rotate(45deg); display: inline-block; background: gray; - bottom: 0; + bottom: 0px; margin-bottom: -17px; border-radius: 9px; opacity: 0.25; @@ -67,9 +67,9 @@ pointer-events: all; padding: 5px; border: 1px solid black; - display:none; + display: none; span { - margin-left : 10px; + margin-left: 10px; } } @@ -86,8 +86,9 @@ } } -.collectionTimeView:hover, .collectionTimeView-pivot:hover { +.collectionTimeView:hover, +.collectionTimeView-pivot:hover { .pivotKeyEntry { - display:unset; + display: unset; } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 2a03ea708..95faaa3f0 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -11,7 +11,7 @@ height: 100%; width: 100%; position: relative; - top: 0; + top: 0px; // background: global.$light-gray; font-size: 13px; overflow: auto; @@ -33,12 +33,12 @@ } .no-indent { - padding-left: 0; + padding-left: 0px; //width: max-content; } .no-indent-outline { - padding-left: 0; + padding-left: 0px; width: 100%; } diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss index 06c324bd0..837219e1d 100644 --- a/src/client/views/collections/CollectionView.scss +++ b/src/client/views/collections/CollectionView.scss @@ -1,7 +1,7 @@ @use '../global/globalCssVariables.module.scss' as global; .collectionView { - border-width: 0; + border-width: 0px; border-color: global.$light-gray; border-style: solid; border-radius: 0 0 global.$border-radius global.$border-radius; @@ -18,7 +18,7 @@ position: absolute; top: 55%; border: 1px black solid; - border-radius: 0; + border-radius: 0px; border-top-left-radius: 20px; border-bottom-left-radius: 20px; border-right: unset; @@ -31,8 +31,8 @@ width: 200px; height: 100%; position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; border-left: solid 1px; z-index: 1; diff --git a/src/client/views/collections/FlashcardPracticeUI.scss b/src/client/views/collections/FlashcardPracticeUI.scss index 210c6798f..0cc4711b3 100644 --- a/src/client/views/collections/FlashcardPracticeUI.scss +++ b/src/client/views/collections/FlashcardPracticeUI.scss @@ -8,8 +8,8 @@ .FlashcardPracticeUI-check { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -39,7 +39,7 @@ display: flex; top: 0px; left: 0px; - width: 30; + width: 30px; transform-origin: top left; border-radius: 5px; color: rgba(255, 255, 255, 0.5); @@ -49,7 +49,7 @@ width: 100%; display: flex; flex-direction: column; - top: 0; + top: 0px; position: relative; .FlashcardPracticeUI-quiz, .FlashcardPracticeUI-practice { diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx deleted file mode 100644 index 7dc08389b..000000000 --- a/src/client/views/collections/KeyRestrictionRow.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-disable react/button-has-type */ -import { observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; - -interface IKeyRestrictionProps { - contains: boolean; - script: (value: string) => void; - field: string; - value: string; -} - -@observer -export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> { - @observable private _key = this.props.field; - @observable private _value = this.props.value; - @observable private _contains = this.props.contains; - - render() { - if (this._key && this._value) { - let parsedValue: string | number = `"${this._value}"`; - const parsed = parseInt(this._value); - let type = 'string'; - if (!isNaN(parsed)) { - parsedValue = parsed; - type = 'number'; - } - const scriptText = `${this._contains ? '' : '!'}(((doc.${this._key} && (doc.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))) || - ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))))`; - // let doc = new Doc(); - // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello")); - this.props.script(scriptText); - } else { - this.props.script(''); - } - - return ( - <div className="collectionViewBaseChrome-viewSpecsMenu-row"> - <input - className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" - value={this._key} - onChange={e => - runInAction(() => { - this._key = e.target.value; - }) - } - placeholder="KEY" - /> - <button - className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" - style={{ background: this._contains ? '#77dd77' : '#ff6961' }} - onClick={() => - runInAction(() => { - this._contains = !this._contains; - }) - }> - {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'} - </button> - <input - className="collectionViewBaseChrome-viewSpecsMenu-rowRight" - value={this._value} - onChange={e => - runInAction(() => { - this._value = e.target.value; - }) - } - placeholder="VALUE" - /> - </div> - ); - } -} diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 931cdac2b..b705d17f3 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -92,6 +92,6 @@ input.lm_title { .miniMap-hidden { cursor: pointer; position: absolute; - bottom: 5; - right: 5; + bottom: 5px; + right: 5px; } diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 78794d112..542b0cc87 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -136,8 +136,8 @@ width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 0; filter: opacity(0); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index 7951aff65..32cf3586f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -18,5 +18,5 @@ color: black; // fontStyle: "italic", margin-left: -12; - margin-top: 4; + margin-top: 4px; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 6c47a71b0..ac1ef7d65 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -2,8 +2,8 @@ .collectionfreeformview-none { position: inherit; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; transform-origin: left top; @@ -12,10 +12,10 @@ border-radius: inherit; } .collectionFreeForm-groupDropper { - width: 10000; - height: 10000; - left: -5000; - top: -5000; + width: 10000px; + height: 10000px; + left: -5000px; + top: -5000px; position: absolute; background: transparent; pointer-events: all; @@ -24,8 +24,8 @@ .collectionfreeformview-grid { transform-origin: top left; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; } @@ -219,8 +219,8 @@ border-radius: inherit; box-sizing: border-box; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; align-items: center; @@ -264,7 +264,7 @@ .collectionFreeform-infoUI { position: absolute; display: block; - top: 0; + top: 0px; color: white; background-color: #5075ef; @@ -275,19 +275,19 @@ padding: 10px; .collectionFreeform-infoUI-close { position: absolute; - top: -10; - left: -10; + top: -10px; + left: -10px; } .collectionFreeform-infoUI-msg { position: relative; - max-width: 500; - margin: 10; + max-width: 500px; + margin: 10px; } .collectionFreeform-infoUI-button { border-radius: 50px; font-size: 12px; - padding: 6; + padding: 6px; position: relative; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3571dab1a..32ace463d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; +import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnTrue, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols'; @@ -2065,6 +2065,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); + showBorderRounding = returnTrue; showPresPaths = () => SnappingManager.ShowPresPaths; brushedView = () => this._brushedView; gridColor = () => DashColor(lightOrDark(this.backgroundColor)).fade(0.5).toString(); // prettier-ignore diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss index 0a001d84c..d0685e419 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss @@ -2,7 +2,7 @@ display: flex; height: max-content; flex-direction: column; - top: 0; + top: 0px; position: absolute; width: 100%; height: 100%; @@ -31,9 +31,9 @@ } .face-document-top { position: relative; - top: 0; + top: 0px; width: 100%; - left: 0; + left: 0px; } .face-document-image-container { @@ -69,8 +69,8 @@ .remove-item { position: absolute; - bottom: -5; - right: -5; + bottom: -5px; + right: -5px; background-color: rgba(0, 0, 0, 0.5); // Optional: to add a background behind the icon for better visibility border-radius: 30%; width: 10px; // Adjust size as needed @@ -98,7 +98,7 @@ .faceCollectionBox { width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; } diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index ff9fb14e7..e3a3f9b05 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -160,15 +160,16 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { classifyImagesInBox = async () => { this.startLoading(); + const selectedImages = this._selectedImages; // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them. - const imageInfos = this._selectedImages.map(async doc => { + const imageInfos = selectedImages.map(async doc => { if (!doc.$tags_chat) { const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? ''; return imageUrlToBase64(url).then(hrefBase64 => !hrefBase64 ? undefined : - gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => + gptImageLabel(hrefBase64, 'Give three labels to describe this image.').then(labels => ({ doc, labels }))) ; // prettier-ignore } }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index abd828945..2ec59e5d5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -16,6 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => Doc | void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public generateScrapbook: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -38,6 +39,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} /> <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} /> + <IconButton tooltip="Generate Scrapbook" onPointerDown={this.generateScrapbook} icon={<FontAwesomeIcon icon="palette" />} color={this.userColor} /> <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> <IconButton tooltip="Classify and Sort Images" onPointerDown={this.classifyImages} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 7c9d0f6e1..135f4deac 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,7 +1,7 @@ .marqueeView { position: inherit; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; overflow: hidden; @@ -20,7 +20,7 @@ pointer-events: none; .marquee-legend { bottom: -18px; - left: 0; + left: 0px; position: absolute; font-size: 9; white-space: nowrap; @@ -28,4 +28,4 @@ .marquee-legend::after { content: 'Press <space> for lasso'; } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c120cddf0..ff78b332a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,6 +28,9 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { SubCollectionViewProps } from '../CollectionSubView'; import { ImageLabelBoxData } from './ImageLabelBox'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; +import { StrListCast } from '../../../../fields/Doc'; +import { requestAiGeneratedPreset, DocumentDescriptor } from '../../nodes/scrapbook/AIPresetGenerator'; +import { buildPlaceholdersFromConfigs, slotRealDocIntoPlaceholders } from '../../nodes/scrapbook/ScrapbookBox'; import './MarqueeView.scss'; interface MarqueeViewProps { @@ -76,6 +79,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @observable _labelsVisibile: boolean = false; @observable _lassoPts: [number, number][] = []; @observable _lassoFreehand: boolean = false; + // ─── New Observables for “Pick 1 of N AI Scrapbook” ─── + @observable aiChoices: Doc[] = []; // temporary hidden Scrapbook docs + @observable pickerX = 0; // popup x coordinate + @observable pickerY = 0; // popup y coordinate + @observable pickerVisible = false; // show/hide ScrapbookPicker @computed get Transform() { return this._props.getTransform(); @@ -276,6 +284,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.generateScrapbook = this.generateScrapbook; MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); @@ -518,6 +527,77 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.fadeOut(true); }); + getAiPresetsDescriptors = (): DocumentDescriptor[] => + this.marqueeSelect(false).map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: Array.from(new Set(StrListCast(doc.$tags_chat))), + })); + + generateScrapbook = action(async () => { + const selectedDocs = this.marqueeSelect(false); + if (!selectedDocs.length) return; + + const descriptors = this.getAiPresetsDescriptors(); + if (descriptors.length === 0) { + alert('No documents selected to generate a scrapbook from!'); + return; + } + + const aiPreset = await requestAiGeneratedPreset(descriptors); + if (!aiPreset.length) { + alert('Failed to generate preset'); + return; + } + const scrapbookPlaceholders: Doc[] = buildPlaceholdersFromConfigs(aiPreset); + /* + const scrapbookPlaceholders: Doc[] = aiPreset.map(cfg => { + const placeholderDoc = Docs.Create.TextDocument(cfg.tag); + placeholderDoc.placeholder_docType = cfg.type as DocumentType; + placeholderDoc.placeholder_acceptTags = new List<string>(cfg.acceptTags ?? [cfg.tag]); + + const placeholder = new Doc(); + placeholder.proto = placeholderDoc; + placeholder.original = placeholderDoc; + placeholder.x = cfg.x; + placeholder.y = cfg.y; + if (cfg.width != null) placeholder._width = cfg.width; + if (cfg.height != null) placeholder._height = cfg.height; + + return placeholder; + });*/ + + const scrapbook = Docs.Create.ScrapbookDocument(scrapbookPlaceholders, { + backgroundColor: '#e2ad32', + x: this.Bounds.left, + y: this.Bounds.top, + _width: 500, + _height: 500, + title: 'AI-generated Scrapbook', + }); + + // 3) Now grab that new scrapbook’s flat placeholders + const allPlaceholders = DocUtils.unwrapPlaceholders(scrapbookPlaceholders); + + // 4) Slot each selectedDocs[i] into the first matching placeholder + selectedDocs.forEach(realDoc => slotRealDocIntoPlaceholders(realDoc, allPlaceholders)); + + const selected = selectedDocs.map(d => { + this._props.removeDocument?.(d); + d.x = NumCast(d.x) - this.Bounds.left; + d.y = NumCast(d.y) - this.Bounds.top; + return d; + }); + + this._props.addDocument?.(scrapbook); + const portal = Docs.Create.FreeformDocument(selected, { title: 'docs in scrapbook', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); + DocUtils.MakeLink(scrapbook, portal, { link_relationship: 'scrapbook of:in scrapbook' }); + + portal.hidden = true; + this._props.addDocument?.(portal); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + }); + @action marqueeCommand = (e: KeyboardEvent) => { const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean }; @@ -539,6 +619,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps if (e.key === 'g') this.collection(e, true); if (e.key === 'c' || e.key === 't') this.collection(e); if (e.key === 's' || e.key === 'S') this.summary(); + if (e.key === 'g' || e.key === 'G') this.generateScrapbook(); // ← scrapbook shortcut if (e.key === 'p') this.pileup(); this.cleanupInteractions(false); } @@ -683,25 +764,27 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; render() { return ( - <div - className="marqueeView" - ref={r => { - r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); - this.MarqueeRef = r; - }} - style={{ - overflow: StrCast(this._props.Document._overflow), - cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', - }} - onDragOver={e => e.preventDefault()} - onScroll={e => { - e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; - }} - onClick={this.onClick} - onPointerDown={this.onPointerDown}> - {this._visible ? this.marqueeDiv : null} - {this.props.children} - </div> + <> + <div + className="marqueeView" + ref={r => { + r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); + this.MarqueeRef = r; + }} + style={{ + overflow: StrCast(this._props.Document._overflow), + cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', + }} + onDragOver={e => e.preventDefault()} + onScroll={e => { + e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; + }} + onClick={this.onClick} + onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} + </div> + </> ); } } diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss index 4edaf9745..b95d3ea44 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.scss +++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss @@ -39,8 +39,8 @@ background: #d3d3d3; position: absolute; - height: 3; - left: 5; + height: 3px; + left: 5px; transform-origin: left; transform: rotate(90deg); outline: none; @@ -133,7 +133,7 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; - margin: 0; + margin: 0px; } /* Firefox */ diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index 0dfaed38a..0053d3e60 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -47,7 +47,7 @@ background: global.$medium-blue; display: flex; border-radius: 10px; - height: 35; + height: 35px; transform: translate3d(6px, 0px, 0px); align-content: center; justify-content: center; @@ -95,11 +95,11 @@ pointer-events: all; cursor: pointer; background-color: global.$medium-blue; - padding: 5; + padding: 5px; border-radius: 2px; height: 100%; - min-width: 25; - margin: 0; + min-width: 25px; + margin: 0px; color: global.$white; display: flex; font-weight: 100; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index 9ed247d50..1dc46102f 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,8 +1,8 @@ .collectionMulticolumnView_drop { height: 100%; width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; .collectionMulticolumnView_contents { @@ -18,8 +18,8 @@ align-items: center; position: relative; > .iconButton-container { - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index 91779065d..eb157d155 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,8 +1,8 @@ .collectionMultirowView_drop { height: 100%; width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; .collectionMultirowView_contents { diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 10a6fa2e9..2ff99f134 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx index 66215f109..4f57e1656 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 918365700..1954b4743 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -66,7 +65,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { style={{ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none', height: this.props.height, - backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string, + backgroundColor: !this.props.isContentActive?.() ? '' : (this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string), }}> <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 53c0823ea..e975ae6f6 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -18,7 +18,7 @@ .schema-add { position: relative; - height: 35; + height: 35px; display: flex; align-items: center; top: -10px; @@ -147,7 +147,7 @@ flex-direction: row; justify-content: space-between; align-items: center; - padding: 0; + padding: 0px; z-index: 1; border: 1px solid global.$medium-gray; @@ -231,7 +231,7 @@ overflow-x: hidden; overflow-y: auto; display: inline-flex; - padding: 0; + padding: 0px; align-items: center; input[type='text'] { border: unset; @@ -272,8 +272,8 @@ .row-menu-infos { position: absolute; - top: 3; - left: 3; + top: 3px; + left: 3px; z-index: 1; display: flex; justify-content: flex-end; diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss index 82f6caa52..7641d4929 100644 --- a/src/client/views/global/globalCssVariables.module.scss +++ b/src/client/views/global/globalCssVariables.module.scss @@ -75,7 +75,7 @@ $CAROUSEL3D_CENTER_SCALE: 1.3; $CAROUSEL3D_SIDE_SCALE: 0.6; $CAROUSEL3D_TOP: 15; -$DATA_VIZ_TABLE_ROW_HEIGHT: 30; +$DATA_VIZ_TABLE_ROW_HEIGHT: 30px; :export { contextMenuZindex: $contextMenu-zindex; diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index 3cd60c87f..cc8c168cf 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -114,7 +114,7 @@ .linkMenu-deleteButton { width: 20px; height: 20px; - margin: 0; + margin: 0px; margin-right: 4px; padding-right: 6px; border-radius: 50%; @@ -134,7 +134,7 @@ } &:last-child { - margin-right: 0; + margin-right: 0px; } &:hover { diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss index 4bfb4b0b9..f8d724767 100644 --- a/src/client/views/linking/LinkPopup.scss +++ b/src/client/views/linking/LinkPopup.scss @@ -1,7 +1,9 @@ .linkPopup-container { background: white; - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); - top: 0; + box-shadow: + 0 10px 20px rgba(0, 0, 0, 0.19), + 0 6px 6px rgba(0, 0, 0, 0.23); + top: 0px; height: 200px; width: 200px; // padding: 15px; diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index b654f9bd0..760850241 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; diff --git a/src/client/views/newlightbox/NewLightboxView.scss b/src/client/views/newlightbox/NewLightboxView.scss index 76c34bcf9..c76a7d60d 100644 --- a/src/client/views/newlightbox/NewLightboxView.scss +++ b/src/client/views/newlightbox/NewLightboxView.scss @@ -2,8 +2,8 @@ .newLightboxView-frame { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; background: #474545bb; diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss index cf6b5ccb1..09d3ccc62 100644 --- a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss +++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss @@ -135,7 +135,7 @@ font-size: 10px; width: 100%; background: newstyles.$blue-l1; - border-radius: 0; + border-radius: 0px; padding: 10px; .concepts-container { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 933a383ea..c25c09af9 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -138,7 +138,7 @@ input[type='range']::-webkit-slider-thumb { box-shadow: 0; - border: 0; + border: 0px; height: 10px; width: 10px; border-radius: 10px; @@ -168,7 +168,7 @@ .audiobox-button { width: 15px; height: 15px; - margin: 0; + margin: 0px; svg { width: 10px; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss index 7f0a39550..300533df8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss @@ -3,7 +3,7 @@ position: absolute; background-color: transparent; touch-action: manipulation; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; } diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index d2ba9796b..cbbd6bde3 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -18,7 +18,7 @@ } .input-box { position: absolute; - top: 50; + top: 50px; padding: 10px; width: 100%; height: 70%; @@ -33,7 +33,7 @@ padding-right: 5px; border-radius: 2px; height: 17%; - bottom: 0; + bottom: 0px; overflow: hidden; display: flex; width: 100%; @@ -101,7 +101,7 @@ position: absolute; display: inline-block; margin-top: 150px; - bottom: 0; + bottom: 0px; } .dropup-content { @@ -145,8 +145,8 @@ .clip-div { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; height: 100%; overflow: hidden; @@ -180,8 +180,8 @@ .afterBox-cont { position: absolute; - top: 0; - right: 0; + top: 0px; + right: 0px; height: 100%; width: 100%; overflow: hidden; @@ -331,8 +331,8 @@ justify-content: space-between; height: max-content; position: absolute; - bottom: 0; - right: 2; + bottom: 0px; + right: 2px; flex-direction: row-reverse; display: flex; cursor: pointer; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index 9825d926f..32a01355e 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -37,25 +37,25 @@ margin-left: 10px; margin-bottom: -10px; } - + .displaySchemaLive { margin-bottom: 20px; } .dataviz-sidebar { position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; height: 100%; } .button-container { pointer-events: unset; } - .dataVizBox-annotationLayer{ + .dataVizBox-annotationLayer { position: absolute; transform-origin: left top; - top: 0; + top: 0px; width: 100%; pointer-events: none; mix-blend-mode: multiply; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss index 6eb7fa96a..e2261b9e2 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss @@ -110,12 +110,12 @@ &::before { content: ''; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; border-bottom: 20px solid rgb(50, 50, 50); border-left: 12px solid transparent; border-right: 12px solid transparent; - height: 0; + height: 0px; width: 50px; } @@ -127,7 +127,7 @@ border-bottom: 22px solid rgb(180, 180, 180); border-left: 12px solid transparent; border-right: 12px solid transparent; - height: 0; + height: 0px; width: 52px; z-index: -1; } @@ -418,8 +418,8 @@ } .div { - width: 200; - height: 200; + width: 200px; + height: 200px; border: solid 1px white; } @@ -588,7 +588,7 @@ } .docCreatorMenu-configuration-bar { - width: 200; + width: 200px; gap: 5px; display: flex; flex-direction: row; @@ -709,8 +709,8 @@ width: 100%; aspect-ratio: 1; //height: auto; - // max-width: 240; - // max-height: 240; + // max-width: 240px; + // max-height: 240px; border: 1px solid rgb(180, 180, 180); border-radius: 5px; background-color: rgb(34, 34, 37); @@ -718,8 +718,8 @@ scrollbar-width: none; &.small { - max-width: 100; - max-height: 100; + max-width: 100px; + max-height: 100px; } .docCreatorMenu-layout-preview-item { @@ -1009,8 +1009,8 @@ } &:hover .operator-dropdown-current { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; } &:hover .operator-dropdown-option { diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss index 63a693918..0acc2c847 100644 --- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss +++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss @@ -13,7 +13,7 @@ $highlightedText: #82e0ff; min-height: 200px; border-radius: 15px; padding: 15px; - padding-bottom: 0; + padding-bottom: 0px; z-index: 999; display: flex; flex-direction: column; @@ -40,7 +40,7 @@ $highlightedText: #82e0ff; font-size: 12px; font-weight: 400; letter-spacing: 1px; - margin: 0; + margin: 0px; padding-right: 5px; } @@ -124,8 +124,8 @@ $highlightedText: #82e0ff; .img-container::after { content: ''; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index ff1fa343d..a22e1153c 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -91,7 +91,7 @@ margin: 5px; margin-left: 25px; margin-right: 10px; - margin-bottom: 0; + margin-bottom: 0px; .tableBox-table { height: 100%; width: 100%; @@ -101,7 +101,7 @@ text-overflow: ellipsis; width: 100%; white-space: pre; - max-width: 150; + max-width: 150px; overflow: hidden; margin-left: 2px; } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 9e0868cd5..cc08cf269 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -401,7 +401,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> { } })}> <thead> - <tr style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} /> + <tr style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT.replace("px","")) }} /> <tr> {this.columns.map((col, i) => ( <th @@ -470,7 +470,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> { })} </tr> ))} - <tr style={{ display: this._tableDataIds.length - this.endID ? undefined : 'none', height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} /> + <tr style={{ display: this._tableDataIds.length - this.endID ? undefined : 'none', height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT.replace("px","")) }} /> </tbody> </table> </div> diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index e1b83dc59..43b1e083f 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -17,8 +17,8 @@ } .documentLinksButton-cont { - min-width: 20; - min-height: 20; + min-width: 20px; + min-height: 20px; position: absolute; } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index c4351a200..98ca76339 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -1,6 +1,7 @@ @use '../global/globalCssVariables.module.scss' as global; .documentView-effectsWrapper { + height: 100%; border-radius: inherit; transition: inherit; } @@ -14,13 +15,13 @@ width: 100%; height: 100%; position: absolute; - top: 0; + top: 0px; } .documentView-node { position: inherit; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; border-radius: inherit; @@ -55,7 +56,7 @@ .documentView-htmlOverlay { position: absolute; display: flex; - top: 0; + top: 0px; height: 100%; width: 100%; .documentView-htmlOverlayInner { @@ -79,9 +80,9 @@ .documentView-audioBackground { display: inline-block; width: 25px; - height: 25; + height: 25px; position: absolute; - top: 0; + top: 0px; left: 50%; border-radius: 25px; background: white; @@ -130,7 +131,7 @@ width: 30px; border-radius: 50%; position: absolute; - right: -15; + right: -15px; opacity: 0.9; pointer-events: auto; background-color: #9dca96; @@ -147,8 +148,8 @@ .documentView-anchorCont { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; display: inline-block; @@ -160,8 +161,8 @@ position: absolute; width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; } .documentView-styleWrapper { @@ -183,9 +184,9 @@ .documentView-titleWrapper-hover { color: global.$black; transform-origin: top left; - top: 0; + top: 0px; width: 100%; - height: 14; + height: 14px; opacity: 0.5; text-align: center; text-overflow: ellipsis; @@ -211,7 +212,7 @@ .documentView-captionWrapper { position: absolute; - bottom: 0; + bottom: 0px; width: 100%; overflow-y: auto; transform-origin: bottom left; @@ -275,20 +276,20 @@ .documentView-noAIWidgets { transform-origin: top left; position: absolute; - bottom: 0; + bottom: 0px; pointer-events: none; } .documentView-widgetDecorations { transform-origin: top right; position: absolute; - top: 0; - right: 0; + top: 0px; + right: 0px; } .documentView-editorView-history { position: absolute; transform-origin: top right; - right: 0; + right: 0px; top: 0; overflow-y: scroll; scrollbar-width: thin; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9b73cc073..fe95f15af 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -720,7 +720,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field @computed get tagsOverlay() { return ( <div - className="documentView-noAiWidgets" + className="documentView-noAIWidgets" style={{ width: `${100 / this.uiBtnScaling}%`, // transform: `scale(${this.uiBtnScaling})`, diff --git a/src/client/views/nodes/FontIconBox/FontIconBadge.scss b/src/client/views/nodes/FontIconBox/FontIconBadge.scss index 2ff5c651f..e741936db 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBadge.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBadge.scss @@ -6,7 +6,7 @@ color: black; display: block; position: absolute; - right: 5; + right: 5px; border-radius: 50%; text-align: center; } diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index 8bc68c131..52eebba54 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -42,7 +42,7 @@ letter-spacing: normal; background-color: inherit; border-radius: 8px; - padding: 0; + padding: 0px; width: 100%; font-family: 'system-ui'; text-transform: uppercase; @@ -96,22 +96,22 @@ display: inline-block; width: 100%; height: 25px; - margin: 0; + margin: 0px; } .switch input { opacity: 0; - width: 0; - height: 0; + width: 0px; + height: 0px; } .slider { position: absolute; cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; background-color: lightgrey; -webkit-transition: 0.4s; transition: 0.4s; @@ -223,7 +223,7 @@ height: fit-content; color: black; top: 100%; - left: 0; + left: 0px; z-index: 21; background-color: #e3e3e3; box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); @@ -430,7 +430,7 @@ border-radius: 0px 7px 7px 0px; width: 13px; height: 100%; - right: 0; + right: 0px; } .menuButton-dropdown-header { diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss index d6cf95958..c0977dfc5 100644 --- a/src/client/views/nodes/IconTagBox.scss +++ b/src/client/views/nodes/IconTagBox.scss @@ -15,7 +15,7 @@ width: 20px; height: 20px; margin: auto; - padding: 0; + padding: 0px; border-radius: 50%; background-color: global.$dark-gray; background-color: transparent; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 5a6292fab..90ede69dc 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -3,14 +3,14 @@ width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; transform-origin: top left; .imageBox-annotationLayer { position: absolute; transform-origin: left top; - top: 0; + top: 0px; width: 100%; pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! @@ -24,8 +24,8 @@ #upload-icon { position: absolute; - bottom: 0; - right: 0; + bottom: 0px; + right: 0px; width: 20px; height: 20px; } @@ -51,8 +51,8 @@ .imageBox-dot { position: absolute; - bottom: 10; - left: 0; + bottom: 10px; + left: 0px; border-radius: 10px; width: 20px; height: 20px; @@ -131,8 +131,8 @@ position: absolute; color: white; background: black; - right: 0; - bottom: 0; + right: 0px; + bottom: 0px; z-index: 2; transform-origin: bottom right; cursor: default; @@ -142,13 +142,13 @@ } } .imageBox-regenerateDropTarget { - right: 35; + right: 35px; transform-origin: 70px 35px; } .imageBox-fader img { position: absolute; - left: 0; + left: 0px; } .imageBox-fadeBlocker-hover { @@ -223,7 +223,7 @@ max-width: 90%; width: 100%; .imageBox-aiView-similarity { - max-width: 65; + max-width: 65px; overflow: hidden; text-overflow: ellipsis; width: 100%; @@ -250,7 +250,7 @@ z-index: 10000; h3 { - margin-top: 0; + margin-top: 0px; } input { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8ed59c6e1..5b738ee19 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { extname } from 'path'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils'; +import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon, returnTrue } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -16,7 +16,7 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; +import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast, ImageCastWithSuffix } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -45,6 +45,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; +import { gptImageLabel } from '../../apis/gpt/GPT'; const DefaultPath = '/assets/unknown-file-icon-hi.png'; export class ImageEditorData { @@ -112,7 +113,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._props.setContentViewBox?.(this); } - @computed get oupaintOriginalSize(): { width: number; height: number } { + @computed get outpaintOriginalSize(): { width: number; height: number } { return { width: NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']), height: NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']), @@ -139,6 +140,42 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document)); }; + + autoTag = async () => { + if (this.Document.$tags_chat) return; + try { + // 1) grab the full-size URL + const layoutKey = Doc.LayoutDataKey(this.Document); + const url = ImageCastWithSuffix(this.Document[layoutKey], '_o'); + if (!url) throw new Error('No image URL found'); + + // 2) convert to base64 + const base64 = await imageUrlToBase64(url); + if (!base64) throw new Error('Failed to load image data'); + + // 3) ask GPT for labels one label: PERSON or LANDSCAPE + const label = await gptImageLabel( + base64, + `Classify this image as PERSON or LANDSCAPE. You may only respond with one of these two options. + Then provide five additional descriptive tags to describe the image for a total of 6 words outputted, delimited by spaces. + For example: "LANDSCAPE BUNNY NATURE FOREST PEACEFUL OUTDOORS". + Then add one final lengthier summary tag (separated by underscores) that describes the image.` + ).then(raw => raw.trim().toUpperCase()); + + const { nativeWidth, nativeHeight } = this.nativeSize; + const aspectRatio = ((nativeWidth || 1) / (nativeHeight || 1)).toFixed(2); + + // 5) stash it on the Doc + // overwrite any old tags so re-runs still work + this.Document.$tags_chat = new List<string>([...label.split(/\s+/), `ASPECT_${aspectRatio}`]); + + // 6) flip on “show tags” in the layout + this.Document._layout_showTags = true; + } catch (err) { + console.error('autoTag failed:', err); + } + }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor const anchor = @@ -225,9 +262,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - handleSelection = async (selection: string) => { - this._searchInput = selection; - }; + handleSelection = (selection: string) => (this._searchInput = selection); drop = undoable( action((e: Event, de: DragManager.DropEvent) => { @@ -385,16 +420,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action cancelOutpaintPrompt = () => { - [this.Document._width, this.Document._height] = [this.oupaintOriginalSize.width, this.oupaintOriginalSize.height]; + [this.Document._width, this.Document._height] = [this.outpaintOriginalSize.width, this.outpaintOriginalSize.height]; this._outpaintingInProgress = false; this.outpaintOriginalSize = undefined; this.closeOutpaintPrompt(); }; @action - handlePromptChange = (val: string | number) => { - this._outpaintPromptInput = '' + val; - }; + handlePromptChange = (val: string | number) => (this._outpaintPromptInput = '' + val); @action submitOutpaintPrompt = () => { @@ -435,7 +468,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; this._mainCont?.appendChild(loadingOverlay); - const { width: origWidth, height: origHeight } = this.oupaintOriginalSize; + const { width: origWidth, height: origHeight } = this.outpaintOriginalSize; const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, @@ -495,6 +528,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight; } + isOutpaintable = () => true; + componentUI = (/* boundsLeft: number, boundsTop: number*/) => !this._showOutpaintPrompt ? null : ( <div @@ -959,11 +994,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return { width, height }; }; savedAnnotations = () => this._savedAnnotations; + showBorderRounding = returnTrue; rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView | undefined) => (this.dataDoc[this.fieldKey] === undefined ? true : (this._props.rejectDrop?.(de, subView) ?? false)); render() { TraceMobx(); - const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; - const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad; + const borderRadius = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; return ( <> <div diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss index 441fceba4..80ace6ae0 100644 --- a/src/client/views/nodes/KeyValueBox.scss +++ b/src/client/views/nodes/KeyValueBox.scss @@ -93,9 +93,9 @@ $header-height: 30px; height: 30px; width: 5px; z-index: 20; - right: 0; - top: 0; - border-radius: 0; + right: 0px; + top: 0px; + border-radius: 0px; background: black; pointer-events: all; } @@ -105,8 +105,8 @@ $header-height: 30px; float: left; height: 37px; z-index: 20; - right: 0; - top: 0; + right: 0px; + top: 0px; background: transparent; pointer-events: none; } diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index 913ab641c..154fbdcfa 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -17,7 +17,7 @@ } .keyValuePair-td-key-check { position: relative; - margin: 0; + margin: 0px; } .keyValuePair-keyField { width: 100%; diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index 889cdc0ca..e1974d6a0 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -24,8 +24,8 @@ .answer-icon { position: absolute; - right: 8; - bottom: 5; + right: 8px; + bottom: 5px; color: black; display: inline-block; font-size: 10px; @@ -36,8 +36,8 @@ .q-icon { position: absolute; - right: 6; - bottom: 5; + right: 6px; + bottom: 5px; color: white; display: inline-block; font-size: 10px; @@ -48,8 +48,8 @@ .edit-icon { position: absolute; - right: 20; - bottom: 5; + right: 20px; + bottom: 5px; display: inline-block; font-size: 10px; cursor: pointer; diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss index 28216394d..7d99247e7 100644 --- a/src/client/views/nodes/LinkDocPreview.scss +++ b/src/client/views/nodes/LinkDocPreview.scss @@ -42,7 +42,7 @@ .linkDocPreview-button { display: inline-flex; - margin: 0; + margin: 0px; margin-right: 3px; border-radius: 50%; pointer-events: auto; diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index cabd4de05..5a8f49000 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -26,7 +26,7 @@ } .loadingBox-spinner { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } } diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss index c36d98afe..217576203 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.scss +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss @@ -6,19 +6,19 @@ } .anchorMenu-highlighter { padding-right: 5px; - .antimodeMenu-button { - padding: 0; - padding: 0; + .antimodeMenu-button { + padding: 0px; + padding: 0px; padding-right: 0px; padding-left: 0px; width: 5px; } } -.anchor-color-preview-button { - width: 25px !important; +.anchor-color-preview-button { + width: 25px !important; .anchor-color-preview { display: flex; - flex-direction: column; + flex-direction: column; padding-right: 3px; width: unset !important; .color-preview { @@ -72,12 +72,11 @@ } } - .MuiInputBase-input{ + .MuiInputBase-input { color: white !important; } - - - .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled{ + + .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled { -webkit-text-fill-color: #b3b2b2 !important; } @@ -91,7 +90,7 @@ gap: 5px; } - .selected-route-details-container{ + .selected-route-details-container { display: flex; flex-direction: column; gap: 3px; @@ -99,33 +98,25 @@ align-items: flex-start; padding: 5px; } - - } - .customized-marker-container{ + .customized-marker-container { display: flex; flex-direction: column; gap: 10px; - .current-marker-container{ + .current-marker-container { display: flex; align-items: center; gap: 5px; } - .all-markers-container{ + .all-markers-container { display: flex; - align-items: center; + align-items: center; gap: 10px; flex-wrap: wrap; max-width: 400px; } } - - - - } - - diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss index bd4b51038..89d381070 100644 --- a/src/client/views/nodes/MapBox/MapBox.scss +++ b/src/client/views/nodes/MapBox/MapBox.scss @@ -21,7 +21,7 @@ .mapBox-infoWindow { background-color: white; opacity: 0.75; - padding: 12; + padding: 12px; font-size: 17; } .mapBox-searchbar { @@ -119,7 +119,7 @@ width: 100%; label { - margin-bottom: 0; + margin-bottom: 0px; } .speed-label { @@ -197,12 +197,12 @@ } .mapBox-sidebar { position: absolute; - right: 0; + right: 0px; height: 100%; } .mapBox-sidebar-handle { - top: 0; + top: 0px; //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views width: 10px; height: 100%; @@ -215,7 +215,7 @@ left: 50%; margin-left: 120px; right: unset !important; - margin-top: -10; + margin-top: -10px; height: max-content; } .searchbox { diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index f09a2630a..e34ca61d4 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -17,8 +17,8 @@ height: 100%; z-index: 1; pointer-events: none; - top: 0; - left: 0; + top: 0px; + left: 0px; // glr: This should really be the same component as text and PDFs .pdfBox-sidebarBtn { @@ -72,16 +72,16 @@ align-items: center; height: 20px; background: none; - padding: 0; + padding: 0px; position: absolute; pointer-events: all; color: white; - bottom: 0; - right: 0; + bottom: 0px; + right: 0px; .pdfBox-overlayButton-arrow { - width: 0; - height: 0; + width: 0px; + height: 0px; border-top: 10px solid transparent; border-bottom: 10px solid transparent; border-right: 15px solid #121721; @@ -122,8 +122,8 @@ .pdfBox-settingsCont { position: absolute; - right: 0; - top: 3; + right: 0px; + top: 3px; pointer-events: all; .pdfBox-settingsButton { @@ -133,11 +133,11 @@ align-items: center; height: 20px; background: none; - padding: 0; + padding: 0px; .pdfBox-settingsButton-arrow { - width: 0; - height: 0; + width: 0px; + height: 0px; border-top: 10px solid transparent; border-bottom: 10px solid transparent; border-right: 15px solid #121721; @@ -189,7 +189,7 @@ width: calc(100% - 40px); height: 20px; background: #121721; - bottom: 0; + bottom: 0px; display: flex; justify-content: center; align-items: center; @@ -253,13 +253,13 @@ .pdfBox-container { position: absolute; transform-origin: top left; - top: 0; + top: 0px; } .pdfBox-sidebarContainer { position: absolute; height: 100%; - right: 0; - top: 0; + right: 0px; + top: 0px; } .pdfBox-interactive { @@ -290,7 +290,7 @@ } .pdfBox-settingsButton-arrow { - height: 60; + height: 60px; border-top: 30px solid transparent; border-bottom: 30px solid transparent; border-right: 30px solid #121721; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 45fa5cc12..5501f0a31 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -33,6 +33,9 @@ import { ImageBox } from './ImageBox'; import { OpenWhere } from './OpenWhere'; import './PDFBox.scss'; import { CreateImage } from './WebBoxRenderer'; +import { gptAPICall } from '../../apis/gpt/GPT'; +import { List } from '../../../fields/List'; +import { GPTCallType } from '../../apis/gpt/GPT'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -78,6 +81,36 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } } + autoTag = async () => { + if (!this.Document.$tags_chat && this._pdf) { + if (!this.dataDoc.text) { + // 1) Extract text from the first few pages (e.g., first 2 pages) + const maxPages = Math.min(2, this._pdf.numPages); + const promises: Promise<string>[] = []; + for (let pageNum = 1; pageNum <= maxPages; pageNum++) { + promises.push( + this._pdf + .getPage(pageNum) + .then(page => page.getTextContent()) + .then(content => content.items.map(item => ('str' in item ? item.str : '')).join(' ')) + ); + } + this.dataDoc.text = (await Promise.all(promises)).join(' '); + } + + const text = StrCast(this.dataDoc.text).trim().slice(0, 2000); + if (text) { + // 2) Ask GPT to classify and provide descriptive tags, then normalize the results + const label = await gptAPICall(`"${text}"`, GPTCallType.CLASSIFYTEXTFULL).then(raw => raw.trim().toUpperCase()); + + this.Document.$tags_chat = new List<string>(label.split(/\s+/)); + + // 4) Show tags in layout + this.Document._layout_showTags = true; + } + } + }; + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { if (oldDiv.childNodes) { for (let i = 0; i < oldDiv.childNodes.length; i++) { diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss index ac2c611c7..78aa526bf 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss @@ -29,8 +29,8 @@ .wedge { pointer-events: none; position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; } } diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.scss b/src/client/views/nodes/RecordingBox/ProgressBar.scss index 28ad25ffa..ec01f0241 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.scss +++ b/src/client/views/nodes/RecordingBox/ProgressBar.scss @@ -1,36 +1,34 @@ - .progressbar { - touch-action: none; - vertical-align: middle; - text-align: center; - - align-items: center; - cursor: default; - - - position: absolute; - display: flex; - justify-content: flex-start; - bottom: 2px; - width: 99%; - height: 30px; - background-color: gray; - - &.done { - top: 0; - width: 0px; - height: 5px; - background-color: red; - z-index: 2; - } - - &.mark { - top: 0; - background-color: transparent; - border-right: 2px solid white; - z-index: 3; - pointer-events: none; - } + touch-action: none; + vertical-align: middle; + text-align: center; + + align-items: center; + cursor: default; + + position: absolute; + display: flex; + justify-content: flex-start; + bottom: 2px; + width: 99%; + height: 30px; + background-color: gray; + + &.done { + top: 0px; + width: 0px; + height: 5px; + background-color: red; + z-index: 2; + } + + &.mark { + top: 0px; + background-color: transparent; + border-right: 2px solid white; + z-index: 3; + pointer-events: none; + } } .progressbar-disabled { @@ -43,37 +41,41 @@ // citation: https://codepen.io/_Master_/pen/PRdjmQ @keyframes blinker { - from {opacity: 1.0;} - to {opacity: 0.0;} + from { + opacity: 1; + } + to { + opacity: 0; + } } .blink { - text-decoration: blink; - animation-name: blinker; - animation-duration: 0.6s; - animation-iteration-count:infinite; - animation-timing-function:ease-in-out; - animation-direction: alternate; + text-decoration: blink; + animation-name: blinker; + animation-duration: 0.6s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + animation-direction: alternate; } .segment { - border: 3px solid black; - background-color: red; - margin: 1px; - padding: 0; - cursor: pointer; - transition-duration: .5s; - user-select: none; - - vertical-align: middle; - text-align: center; + border: 3px solid black; + background-color: red; + margin: 1px; + padding: 0px; + cursor: pointer; + transition-duration: 0.5s; + user-select: none; + + vertical-align: middle; + text-align: center; } .segment-expanding { -border-color: red; - background-color: white; - transition-duration: 0s; - opacity: .75; - pointer-events: none; + border-color: red; + background-color: white; + transition-duration: 0s; + opacity: 0.75; + pointer-events: none; } .segment-expanding:hover { @@ -82,10 +84,10 @@ border-color: red; } .segment-disabled { - pointer-events: none; - opacity: 0.5; - transition-duration: 0s; - /* Hide the text. */ + pointer-events: none; + opacity: 0.5; + transition-duration: 0s; + /* Hide the text. */ text-indent: 100%; white-space: nowrap; overflow: hidden; @@ -99,25 +101,26 @@ border-color: red; } .segment:first-child { - margin-left: 2px; + margin-left: 2px; } .segment:last-child { - margin-right: 2px; + margin-right: 2px; } .segment:hover { background-color: white; } -.segment:hover, .segment-selected { - margin: 0px; - border: 4px solid red; - border-radius: 2px; +.segment:hover, +.segment-selected { + margin: 0px; + border: 4px solid red; + border-radius: 2px; } .segment-selected { - border: 4px solid #202020; - background-color: red; - opacity: .75; - cursor: grabbing; + border: 4px solid #202020; + background-color: red; + opacity: 0.75; + cursor: grabbing; } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss index f2d5a980d..15b48c111 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.scss +++ b/src/client/views/nodes/RecordingBox/RecordingView.scss @@ -28,7 +28,7 @@ video { justify-content: center; // overflow: hidden; border-radius: 10px; - margin: 0; + margin: 0px; } .video-wrapper:hover .controls { @@ -108,7 +108,7 @@ video { .timer { font-size: 15px; color: white; - margin: 0; + margin: 0px; } .dot { @@ -148,7 +148,7 @@ video { height: 80%; width: 80%; align-self: center; - margin: 0; + margin: 0px; &:hover { height: 85%; @@ -163,7 +163,7 @@ video { height: 70%; width: 70%; align-self: center; - margin: 0; + margin: 0px; // &:hover { // width: 40px; @@ -178,8 +178,8 @@ video { flex-direction: row; align-content: center; position: relative; - top: 0; - bottom: 0; + top: 0px; + bottom: 0px; &.video-edit-wrapper { // right: 50% - 15; diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss index 1e9b64a0b..1714d87c2 100644 --- a/src/client/views/nodes/ScreenshotBox.scss +++ b/src/client/views/nodes/ScreenshotBox.scss @@ -32,10 +32,10 @@ .screenshotBox-uiButtons { position: absolute; - right: 25; - top: 0; - width: 22; - height: 25; + right: 25px; + top: 0px; + width: 22px; + height: 25px; .screenshotBox-recorder { color: white; diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss index 9789da55a..de70dbe74 100644 --- a/src/client/views/nodes/ScriptingBox.scss +++ b/src/client/views/nodes/ScriptingBox.scss @@ -82,14 +82,14 @@ } .rta__autocomplete--top { - margin-top: 0; + margin-top: 0px; margin-bottom: 1em; max-height: 100px; } .rta__list { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; background: #fff; border: 1px solid #dfe2e5; border-radius: 3px; diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index b5405f0fb..27f419198 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -3,8 +3,8 @@ .mini-viewer { cursor: grab; position: absolute; - right: 10; - top: 10; + right: 10px; + top: 10px; opacity: 0.1; transition: all 0.4s; color: white; @@ -38,7 +38,7 @@ .videoBox-annotationLayer { position: relative; transform-origin: left top; - top: 0; + top: 0px; width: 100%; pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! @@ -81,8 +81,8 @@ // } .videoBox-ui-wrapper { - width: 0; - height: 0; + width: 0px; + height: 0px; position: relative; z-index: 2000; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b3cb0e1db..f994bdbb5 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -30,6 +30,7 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; +import { gptImageLabel } from '../../apis/gpt/GPT'; import './VideoBox.scss'; /** @@ -109,6 +110,52 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return this._videoRef; } + autoTag = async () => { + if (this.Document.$tags_chat) return; + try { + if (!this.player) throw new Error('Video element not available.'); + + // 1) Extract a frame at the video's midpoint + const videoDuration = this.player.duration; + const snapshotTime = videoDuration / 2; + + // Seek the video element to the midpoint + await new Promise<void>(resolve => { + const onSeeked = () => { + this.player!.removeEventListener('seeked', onSeeked); + resolve(); + }; + this.player!.addEventListener('seeked', onSeeked); + this.player!.currentTime = snapshotTime; + }); + + // 2) Draw the frame onto a canvas and get a base64 representation + const canvas = document.createElement('canvas'); + canvas.width = this.player.videoWidth; + canvas.height = this.player.videoHeight; + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to create canvas context.'); + ctx.drawImage(this.player, 0, 0, canvas.width, canvas.height); + const base64Image = canvas.toDataURL('image/png'); + + // 3) Send the image data to GPT for classification and descriptive tags + const label = await gptImageLabel( + base64Image, + `Classify this video frame as either a PERSON or LANDSCAPE. + Then provide five additional descriptive tags (single words) separated by spaces. + Finally, add one detailed summary phrase using underscores.` + ).then(raw => raw.trim().toUpperCase()); + + // 4) Normalize and store labels in the Document's tags + const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1); + this.Document.$tags_chat = new List<string>([...label.split(/\s+/), `ASPECT_${aspect}`]); + // 5) Turn on tag display in layout + this.Document._layout_showTags = true; + } catch (err) { + console.error('Video autoTag failed:', err); + } + }; + componentDidMount() { this.unmounting = false; this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link. diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 05d5babf9..e7c9cf095 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -3,8 +3,8 @@ .webBox { height: 100%; width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: relative; display: flex; overflow: hidden; @@ -28,8 +28,8 @@ height: 100%; z-index: 1; pointer-events: none; - top: 0; - left: 0; + top: 0px; + left: 0px; overflow: hidden; .webBox-overlayButton { @@ -39,16 +39,16 @@ align-items: center; height: 20px; background: none; - padding: 0; + padding: 0px; position: absolute; pointer-events: all; color: white; - bottom: 0; - right: 0; + bottom: 0px; + right: 0px; .webBox-overlayButton-arrow { - width: 0; - height: 0; + width: 0px; + height: 0px; border-top: 10px solid transparent; border-bottom: 10px solid transparent; border-right: 15px solid #121721; @@ -92,7 +92,7 @@ width: calc(100% - 40px); height: 20px; background: #121721; - bottom: 0; + bottom: 0px; display: flex; justify-content: center; align-items: center; @@ -137,7 +137,7 @@ .webBox-annotationLayer { position: absolute; transform-origin: left top; - top: 0; + top: 0px; width: 100%; pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! @@ -156,8 +156,8 @@ .webBox-htmlSpan { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; cursor: text; padding: 15px; height: 100%; @@ -171,8 +171,8 @@ .webBox-cont-interactive { padding: 0vw; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; transform-origin: top left; @@ -181,8 +181,8 @@ width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; body { ::selection { color: white; @@ -203,8 +203,8 @@ height: 100%; position: absolute; transform-origin: top left; - top: 0; - left: 0; + top: 0px; + left: 0px; overflow: auto; .webBox-innerContent { @@ -224,7 +224,7 @@ } .webBox-buttons { - margin-left: 44; + margin-left: 44px; background: lightGray; width: 100%; } @@ -232,8 +232,8 @@ .webBox-annotationToggle { z-index: 901; position: absolute; - top: 2; - left: 2; + top: 2px; + left: 2px; cursor: pointer; box-shadow: black 0.3em 0.3em 1em; border-radius: 5px; diff --git a/src/client/views/nodes/audio/AudioWaveform.scss b/src/client/views/nodes/audio/AudioWaveform.scss index 6cbd1759a..c6b0da9c8 100644 --- a/src/client/views/nodes/audio/AudioWaveform.scss +++ b/src/client/views/nodes/audio/AudioWaveform.scss @@ -1,17 +1,17 @@ -.audioWaveform { +.audioWaveform { position: relative; width: 100%; height: 200%; overflow: hidden; z-index: -1000; - bottom: 0; + bottom: 0px; pointer-events: none; div { height: 100% !important; - width: 100% !important; + width: 100% !important; } - canvas { + canvas { height: 100% !important; - width: 100% !important; + width: 100% !important; } } diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss index 3d27fa887..4db5cec3d 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss @@ -28,7 +28,7 @@ $transition: all 0.2s ease-in-out; box-shadow: 0 1px 4px $shadow-color; h2 { - margin: 0; + margin: 0px; font-size: 1.5em; font-weight: 500; } @@ -126,7 +126,7 @@ $transition: all 0.2s ease-in-out; animation: fadeIn 0.3s ease-in-out; p { - margin: 0; + margin: 0px; font-size: 14px; } @@ -263,10 +263,10 @@ $transition: all 0.2s ease-in-out; .uploading-overlay { position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; background-color: rgba(255, 255, 255, 0.8); display: flex; justify-content: center; @@ -285,7 +285,7 @@ $transition: all 0.2s ease-in-out; @media (max-width: 768px) { .chat-box { - border-radius: 0; + border-radius: 0px; } .message { diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss index ff5be4a38..77d452830 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss @@ -21,8 +21,8 @@ background-color: #4a90e2; opacity: 0.6; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; animation: bounce 2s infinite ease-in-out; } @@ -42,10 +42,10 @@ .uploading-overlay { position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; background-color: rgba(255, 255, 255, 0.8); display: flex; align-items: center; diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 3734ad9cc..1b2f76bbe 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -17,7 +17,7 @@ min-width: 12px; position: relative; display: inline-block; - margin: 0; + margin: 0px; transform: scale(0.7); background-color: rgba(155, 155, 155, 0.24); } diff --git a/src/client/views/nodes/formattedText/EquationEditor.scss b/src/client/views/nodes/formattedText/EquationEditor.scss index b0c17e56e..602135a30 100644 --- a/src/client/views/nodes/formattedText/EquationEditor.scss +++ b/src/client/views/nodes/formattedText/EquationEditor.scss @@ -32,7 +32,7 @@ margin-left: -1px; position: relative; z-index: 1; - padding: 0; + padding: 0px; display: -moz-inline-box; display: inline-block; } @@ -128,8 +128,8 @@ .mq-math-mode * { font-size: inherit; line-height: inherit; - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; border-color: black; -webkit-user-select: none; -moz-user-select: none; @@ -178,7 +178,7 @@ margin-left: 0.1em; } .mq-math-mode .mq-roman var.mq-f { - margin: 0; + margin: 0px; } .mq-math-mode big { font-size: 200%; @@ -323,7 +323,7 @@ padding: 0.1em; } .mq-math-mode .mq-sqrt-prefix { - padding-top: 0; + padding-top: 0px; position: relative; top: 0.1em; vertical-align: top; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 547a2efa8..d5e566226 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -46,7 +46,7 @@ } audiotag { - left: 0; + left: 0px; position: absolute; cursor: pointer; border-radius: 10px; @@ -62,7 +62,7 @@ audiotag:hover { .formattedTextBox { touch-action: none; background: inherit; - padding: 0; + padding: 0px; border-width: 0px; border-color: global.$medium-gray; box-sizing: border-box; @@ -77,14 +77,14 @@ audiotag:hover { width: 100%; position: relative; transform-origin: left top; - top: 0; - left: 0; + top: 0px; + left: 0px; } .formattedTextBox-cont { touch-action: none; background: inherit; - padding: 0; + padding: 0px; border-width: 0px; border-radius: inherit; border-color: global.$medium-gray; @@ -111,7 +111,7 @@ audiotag:hover { .answer-tooltip { font-size: 15px; padding: 2px; - max-width: 150; + max-width: 150px; line-height: 150%; position: relative; } @@ -122,10 +122,10 @@ audiotag:hover { position: absolute; color: white; background: black; - right: 0; - bottom: 0; - width: 15; - height: 22; + right: 0px; + bottom: 0px; + width: 15px; + height: 22px; cursor: default; } @@ -139,8 +139,8 @@ audiotag:hover { .formattedTextBox-sidebar-handle { position: absolute; - top: 0; - right: 0; + top: 0px; + right: 0px; width: 20px; height: 20px; font-size: 11px; @@ -168,7 +168,7 @@ audiotag:hover { height: 100%; display: inline-block; position: absolute; - right: 0; + right: 0px; overflow: hidden; .collectionfreeformview-container { @@ -302,8 +302,8 @@ footnote::before { position: absolute; top: -0.5em; content: ' '; - height: 0; - width: 0; + height: 0px; + width: 0px; } .formattedTextBox-inlineComment { @@ -346,7 +346,7 @@ footnote::before { .prosemirror-linkBtn { background: unset; color: unset; - padding: 0; + padding: 0px; text-transform: unset; letter-spacing: unset; font-size: unset; @@ -357,7 +357,7 @@ footnote::before { background-color: dimgray; margin-top: 1.5em; z-index: 1; - padding: 5; + padding: 5px; border-radius: 2px; } .prosemirror-hrefoptions { @@ -396,7 +396,7 @@ footnote::before { blockquote { padding: 10px 10px; font-size: smaller; - margin: 0; + margin: 0px; font-style: italic; background: lightgray; border-left: solid 2px dimgray; @@ -415,7 +415,7 @@ footnote::before { p { font-family: inherit; } - margin-left: 0; + margin-left: 0px; } .bullet1 { p { @@ -439,7 +439,7 @@ footnote::before { display: inline-block; font-family: inherit; } - margin-left: 0; + margin-left: 0px; background-color: inherit; } .decimal2-ol { @@ -506,7 +506,7 @@ footnote::before { display: inline-block; font-family: inherit; } - margin-left: 0; + margin-left: 0px; padding-left: 1.2em; background-color: inherit; } @@ -661,7 +661,7 @@ footnote::before { .formattedTextBox-cont { touch-action: none; background: inherit; - padding: 0; + padding: 0px; border-width: 0px; border-radius: inherit; border-color: global.$medium-gray; @@ -706,7 +706,7 @@ footnote::before { height: 100%; display: inline-block; position: absolute; - right: 0; + right: 0px; .collectionfreeformview-container { position: relative; @@ -832,8 +832,8 @@ footnote::before { position: absolute; top: -0.5em; content: ' '; - height: 0; - width: 0; + height: px; + width: 0px; } .formattedTextBox-inlineComment { @@ -892,7 +892,7 @@ footnote::before { display: inline; font-family: inherit; } - margin-left: 0; + margin-left: 0px; } .decimal2-ol { counter-reset: deci2; @@ -952,7 +952,7 @@ footnote::before { display: inline; font-family: inherit; } - margin-left: 0; + margin-left: 0px; padding-left: 1.2em; } .multi2-ol { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c8df6e50f..07cb795f1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti import { EditorView, NodeViewConstructor } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; @@ -308,6 +308,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } }; + autoTag = () => { + const rawText = RTFCast(this.Document[this.fieldKey])?.Text ?? StrCast(this.Document[this.fieldKey]); + if (rawText && !this.Document.$tags_chat) { + const callType = rawText.includes('[placeholder]') ? GPTCallType.CLASSIFYTEXTMINIMAL : GPTCallType.CLASSIFYTEXTFULL; + + gptAPICall(rawText, callType).then( + action(desc => { + // Split GPT response into tokens and push individually & clear existing tags + this.Document.$tags_chat = new List<string>(desc.trim().split(/\s+/)); + this.Document._layout_showTags = true; + }) + ); + } + }; + leafText = (node: Node) => { if (node.type === this.EditorView?.state.schema.nodes.dashField) { const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); @@ -369,6 +384,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined; textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText }); + if (textChange) this.dataDoc.$tags_chat = undefined; this.ApplyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false unchanged = false; } @@ -1072,6 +1088,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return anchorDoc ?? this.Document; } + showBorderRounding = returnTrue; getView = (doc: Doc, options: FocusViewOptions) => { if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss index bc0810f22..92f3e3290 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -11,8 +11,8 @@ -webkit-transform: translateX(-50%); transform: translateX(-50%); box-shadow: 3px 3px 1.5px grey; - max-width: 400; - max-height: 235; + max-width: 400px; + max-height: 235px; height: max-content; .formattedTextBox-tooltipText { height: max-content; @@ -22,26 +22,26 @@ .formattedTextBox-tooltip:before { content: ''; - height: 0; - width: 0; + height: 0px; + width: 0px; position: absolute; left: 50%; margin-left: -5px; bottom: -6px; border: 5px solid transparent; - border-bottom-width: 0; + border-bottom-width: 0px; border-top-color: silver; } .formattedTextBox-tooltip:after { content: ''; - height: 0; - width: 0; + height: 0px; + width: 0px; position: absolute; left: 50%; margin-left: -5px; bottom: -4.5px; border: 5px solid transparent; - border-bottom-width: 0; + border-bottom-width: 0px; border-top-color: white; } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss index fcc816447..7c747de1e 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.scss +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -23,7 +23,7 @@ .dropdown { position: absolute; top: 35px; - left: 0; + left: 0px; background-color: #323232; color: global.$light-gray; border: 1px solid #4d4d4d; @@ -47,7 +47,7 @@ } &:last-child { - margin-bottom: 0; + margin-bottom: 0px; } } } diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss index 87320943d..8980a93a2 100644 --- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss +++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss @@ -195,7 +195,7 @@ left: 1px; width: 24px; height: 4px; - margin-top: 0; + margin-top: 0px; } } @@ -221,7 +221,7 @@ display: inline-block; width: 1em; height: 1em; - stroke-width: 0; + stroke-width: 0px; stroke: currentColor; fill: currentColor; margin-right: 15px; @@ -231,7 +231,7 @@ display: inline-block; width: 1em; height: 1em; - stroke-width: 3; + stroke-width: 3px; fill: greenyellow; margin-right: 15px; } @@ -270,7 +270,7 @@ &.ProseMirror-menu-dropdown { width: 10px; height: 25px; - margin: 0; + margin: 0px; padding: 0 2px; background-color: #323232; text-align: center; diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss index c691e6a18..942a7d4c6 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.scss +++ b/src/client/views/nodes/imageEditor/ImageEditor.scss @@ -4,8 +4,8 @@ $scale: 0.5; .imageEditorContainer { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 9999; height: 100vh; width: 100vw; @@ -78,7 +78,7 @@ $scale: 0.5; .sideControlsContainer { width: 160px; position: absolute; - left: 0; + left: 0px; height: 100%; .sideControls { @@ -129,8 +129,8 @@ $scale: 0.5; .originalImageLabel { position: absolute; - bottom: 10; - left: 10; + bottom: 10px; + left: 10px; color: #ffffff; font-size: 0.8rem; letter-spacing: 1px; diff --git a/src/client/views/nodes/scrapbook/AIPresetGenerator.ts b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts new file mode 100644 index 000000000..1f159222b --- /dev/null +++ b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts @@ -0,0 +1,31 @@ +import { ScrapbookItemConfig } from './ScrapbookPreset'; +import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT'; + +// Represents the descriptor for each document +export interface DocumentDescriptor { + type: string; + tags: string[]; +} + +// Main function to request AI-generated presets +export async function requestAiGeneratedPreset(descriptors: DocumentDescriptor[]): Promise<ScrapbookItemConfig[]> { + const prompt = createPrompt(descriptors); + let aiResponse = await gptAPICall(prompt, GPTCallType.GENERATESCRAPBOOK); + // Strip out ```json and ``` if the model wrapped its answer in fences + aiResponse = aiResponse + .trim() + .replace(/^```(?:json)?\s*/, "") // remove leading ``` or ```json + .replace(/\s*```$/, ""); // remove trailing ``` + const parsedPreset = JSON.parse(aiResponse) as ScrapbookItemConfig[]; + return parsedPreset; +} + +// Helper to generate prompt text for AI +function createPrompt(descriptors: DocumentDescriptor[]): string { + let prompt = ""; + descriptors.forEach((desc, index) => { + prompt += `${index + 1}. Type: ${desc.type}, Tags: ${desc.tags.join(', ')}\n`; + }); + + return prompt; +} diff --git a/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx b/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx deleted file mode 100644 index e99bf67c7..000000000 --- a/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx +++ /dev/null @@ -1,52 +0,0 @@ -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -import * as React from "react"; -import { observer } from "mobx-react"; -import { Doc } from "../../../../fields/Doc"; -import { DocumentView } from "../DocumentView"; -import { Transform } from "../../../util/Transform"; - -interface EmbeddedDocViewProps { - doc: Doc; - width?: number; - height?: number; - slotId?: string; -} - -@observer -export class EmbeddedDocView extends React.Component<EmbeddedDocViewProps> { - render() { - const { doc, width = 300, height = 200, slotId } = this.props; - - // Use either an existing embedding or create one - let docToDisplay = doc; - - // If we need an embedding, create or use one - if (!docToDisplay.isEmbedding) { - docToDisplay = Doc.BestEmbedding(doc) || Doc.MakeEmbedding(doc); - // Set the container to the slot's ID so we can track it - if (slotId) { - docToDisplay.embedContainer = `scrapbook-slot-${slotId}`; - } - } - - return ( - <DocumentView - Document={docToDisplay} - renderDepth={0} - // Required sizing functions - NativeWidth={() => width} - NativeHeight={() => height} - PanelWidth={() => width} - PanelHeight={() => height} - // Required state functions - isContentActive={() => true} - childFilters={() => []} - ScreenToLocalTransform={() => new Transform()} - // Display options - hideDeleteButton={true} - hideDecorations={true} - hideResizeHandles={true} - /> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.scss b/src/client/views/nodes/scrapbook/ScrapbookBox.scss new file mode 100644 index 000000000..6ac2220f9 --- /dev/null +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.scss @@ -0,0 +1,66 @@ +.scrapbook-box { + /* Make sure the container fills its parent, and set a base background */ + position: relative; /* so that absolute children (loading overlay, etc.) are positioned relative to this */ + width: 100%; + height: 100%; + background: beige; + overflow: hidden; /* prevent scrollbars if children overflow */ +} + +/* Loading overlay that covers the entire scrapbook while AI-generation is in progress */ +.scrapbook-box-loading-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: rgba(255, 255, 255, 0.8); + z-index: 10; /* sits above the ImageBox and other content */ +} + +/* The <select> dropdown for choosing presets */ +.scrapbook-box-preset-select { + position: relative; + top: 8px; + left: 8px; + z-index: 20; + padding: 4px 8px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 4px; + background: white; +} + +/* Container for the “Regenerate Background” button */ +.scrapbook-box-ui { + position: relative; + top: 8px; + right: 8px; + z-index: 20; + background: white; + width: 40px; + display: flex; + justify-content: center; +} + +/* The button itself */ +.scrapbook-box-ui-button { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + font-size: 14px; + color: black; + background: white; + border: 1px solid #ccc; + border-radius: 4px; + cursor: pointer; + white-space: nowrap; +} + +.scrapbook-box-ui-button:hover { + background: #f5f5f5; +} diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index 6cfe9a62c..d0ae6194f 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -1,130 +1,260 @@ -import { action, makeObservable, observable } from 'mobx'; +import { IconButton, Size } from '@dash/components'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast } from '../../../../fields/Doc'; +import ReactLoading from 'react-loading'; +import { Doc, DocListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; +import { DateCast, DocCast, NumCast, toList } from '../../../../fields/Types'; import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { DragManager } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { undoable } from '../../../util/UndoManager'; import { CollectionView } from '../../collections/CollectionView'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { AspectRatioLimits, FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; +import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import { DragManager } from '../../../util/DragManager'; -import { RTFCast, StrCast, toList } from '../../../../fields/Types'; -import { undoable } from '../../../util/UndoManager'; -// Scrapbook view: a container that lays out its child items in a grid/template -export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - @observable createdDate: string; +import { ImageBox } from '../ImageBox'; +import './ScrapbookBox.scss'; +import { ScrapbookItemConfig } from './ScrapbookPreset'; +import { createPreset, getPresetNames } from './ScrapbookPresetRegistry'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { DocUtils } from '../../../documents/DocUtils'; +import { returnTrue } from '../../../../ClientUtils'; - constructor(props: FieldViewProps) { - super(props); - makeObservable(this); - this.createdDate = this.getFormattedDate(); +function createPlaceholder(cfg: ScrapbookItemConfig, doc: Doc) { + const placeholder = new Doc(); + placeholder.proto = doc; + placeholder.original = doc; + placeholder.x = cfg.x; + placeholder.y = cfg.y; + if (cfg.width !== null) placeholder._width = cfg.width; + if (cfg.height !== null) placeholder._height = cfg.height; + return placeholder; +} - // ensure we always have a List<Doc> in dataDoc['items'] - if (!this.dataDoc[this.fieldKey]) { - this.dataDoc[this.fieldKey] = new List<Doc>(); +function createMessagePlaceholder(cfg: ScrapbookItemConfig) { + return createPlaceholder(cfg, + Docs.Create.TextDocument(cfg.message ?? ('[placeholder] ' + cfg.acceptTags?.[0]), { placeholder: "", placeholder_docType: cfg.type, placeholder_acceptTags: new List<string>(cfg.acceptTags) }) + ); // prettier-ignore +} +export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]) { + return configs.map(cfg => { + if (cfg.children?.length) { + const childDocs = cfg.children.map(createMessagePlaceholder); + const protoW = cfg.containerWidth ?? cfg.width; + const protoH = cfg.containerHeight ?? cfg.height; + // Create a stacking document with the child placeholders + const containerProto = Docs.Create.StackingDocument(childDocs, { + ...(protoW !== null ? { _width: protoW } : {}), + ...(protoH !== null ? { _height: protoH } : {}), + title: cfg.message, + }); + return createPlaceholder(cfg, containerProto); } - this.createdDate = this.getFormattedDate(); - this.setTitle(); + return createMessagePlaceholder(cfg); + }); +} +export async function slotRealDocIntoPlaceholders(realDoc: Doc, placeholders: Doc[]) { + if (!realDoc.$tags_chart) { + await DocumentView.getFirstDocumentView(realDoc)?.ComponentView?.autoTag?.(); } + const realTags = new Set<string>(StrListCast(realDoc.$tags_chat).map(t => t.toLowerCase?.() ?? '')); + // Find placeholder with most matching tags + let bestMatch: Doc | null = null; + let maxMatches = 0; + + // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type + placeholders + .filter(ph => ph.placeholder_docType === realDoc.$type) // Skip this placeholder entirely if types do not match. + .forEach(ph => { + const matches = StrListCast(ph.placeholder_acceptTags) + .map(t => t.toLowerCase?.()) + .filter(tag => realTags.has(tag)); + + if (matches.length > maxMatches) { + maxMatches = matches.length; + bestMatch = ph; + } + }); + + if (bestMatch && maxMatches > 0) { + setTimeout(undoable(() => (bestMatch!.proto = realDoc), 'Scrapbook add')); + return true; + } + + return false; +} + +// Scrapbook view: a container that lays out its child items in a template +@observer +export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScrapbookBox, fieldStr); } + private _disposers: { [name: string]: IReactionDisposer } = {}; + private _imageBoxRef = React.createRef<ImageBox>(); + + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + + @observable _selectedPreset = getPresetNames()[0]; + @observable _loading = false; - getFormattedDate(): string { - return new Date().toLocaleDateString(undefined, { + @computed get createdDate() { + return DateCast(this.dataDoc.$author_date)?.date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }); } + @computed get ScrapbookLayoutDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } // prettier-ignore + @computed get BackgroundDoc() { return DocCast(this.dataDoc[this.fieldKey + '_background']); } // prettier-ignore + set ScrapbookLayoutDocs(doc: Doc[]) { this.dataDoc[this.fieldKey] = new List(doc); } // prettier-ignore + set BackgroundDoc(doc: Opt<Doc>) { this.dataDoc[this.fieldKey + '_background'] = doc; } // prettier-ignore @action - setTitle() { - const title = `Scrapbook - ${this.createdDate}`; - if (this.dataDoc.title !== title) { - this.dataDoc.title = title; - - const image = Docs.Create.TextDocument('image'); - image.accepts_docType = DocumentType.IMG; - const placeholder = new Doc(); - placeholder.proto = image; - placeholder.original = image; - placeholder._width = 250; - placeholder._height = 200; - placeholder.x = 0; - placeholder.y = -100; - //placeholder.overrideFields = new List<string>(['x', 'y']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos - - const summary = Docs.Create.TextDocument('summary'); - summary.accepts_docType = DocumentType.RTF; - summary.accepts_textType = 'one line'; - const placeholder2 = new Doc(); - placeholder2.proto = summary; - placeholder2.original = summary; - placeholder2.x = 0; - placeholder2.y = 200; - placeholder2._width = 250; - //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos - this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2]); - } - } + setDefaultPlaceholder = () => { + this.ScrapbookLayoutDocs = [ + createMessagePlaceholder({ + message: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.', + type: DocumentType.RTF, + width: 250, + height: 200, + x: 0, + y: 0, + }), + ]; + + const placeholder1 = createMessagePlaceholder({ acceptTags: ['PERSON'], type: DocumentType.IMG, width: 250, height: 200, x: 0, y: -100 }); + const placeholder2 = createMessagePlaceholder({ acceptTags: ['lengthy description'], type: DocumentType.RTF, width: 250, height: undefined, x: 0, y: 200 }); + const placeholder3 = createMessagePlaceholder({ acceptTags: ['title'], type: DocumentType.RTF, width: 50, height: 200, x: 280, y: -50 }); + const placeholder4 = createPlaceholder( { width: 100, height: 200, x: -200, y: -100 }, Docs.Create.StackingDocument([ + createMessagePlaceholder({ acceptTags: ['LANDSCAPE'], type: DocumentType.IMG, width: 50, height: 100, x: 0, y: -100 }) + ], { _width: 300, _height: 300, title: 'internal coll' })); // prettier-ignore + console.log('UNUSED', placeholder4, placeholder3, placeholder2, placeholder1); + /* note-to-self + would doing: + const collection = Docs.Create.ScrapbookDocument([placeholder, placeholder2, placeholder3]); + create issues with references to the same object? */ + + /*note-to-self + Should we consider that there are more collections than just COL type collections? + when spreading */ + + /*note-to-self + difference between passing a new List<Doc> versus just the raw array? */ + }; + + selectPreset = action((presetName: string) => (this.ScrapbookLayoutDocs = buildPlaceholdersFromConfigs(createPreset(presetName)))); componentDidMount() { - this.setTitle(); + const title = `Scrapbook - ${this.createdDate}`; + if (!this.ScrapbookLayoutDocs.length) this.setDefaultPlaceholder(); + if (!this.BackgroundDoc) this.generateAiImage(this.regenPrompt); + if (this.dataDoc.title !== title) this.dataDoc.title = title; // ensure title is set + + this._disposers.propagateResize = reaction( + () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }), + (dims, prev) => { + const imageBox = this._imageBoxRef.current; + // prev is undefined on the first run + if (prev && SnappingManager.ShiftKey && this.BackgroundDoc && imageBox) { + this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalWidth'] = prev.w; + this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalHeight'] = prev.h; + imageBox.layoutDoc._width = dims.w; + imageBox.layoutDoc._height = dims.h; + } + } + ); } - childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => { - return true; // disable dropping documents onto any child of the scrapbook. - }; - rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => { - // Test to see if the dropped doc is dropped on an acceptable location (anywerhe? on a specific box). - // const draggedDocs = de.complete.docDragData?.draggedDocuments; - return false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision. + isOutpaintable = returnTrue; + showBorderRounding = returnTrue; + + @action + generateAiImage = (prompt: string) => { + this._loading = true; + + const ratio = NumCast(this.layoutDoc._width, 1) / NumCast(this.layoutDoc._height, 1); // Measure the scrapbook’s current aspect + const choosePresetForDimensions = (() => { // Pick the Firefly preset that best matches the aspect ratio + if (ratio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) return FireflyImageDimensions.Widescreen; + if (ratio > AspectRatioLimits[FireflyImageDimensions.Landscape]) return FireflyImageDimensions.Landscape; + if (ratio < AspectRatioLimits[FireflyImageDimensions.Portrait]) return FireflyImageDimensions.Portrait; + return FireflyImageDimensions.Square; + })(); // prettier-ignore + + SmartDrawHandler.CreateWithFirefly(prompt, choosePresetForDimensions) // Call exactly the same CreateWithFirefly that ImageBox uses + .then(action(doc => { + if (doc instanceof Doc) { + this.BackgroundDoc = doc; // set the background image directly on the scrapbook + } else { + alert('Failed to generate document.'); + } + })) + .catch(e => alert(`Generation error: ${e}`)) + .finally(action(() => (this._loading = false))); // prettier-ignore }; - filterAddDocument = (docIn: Doc | Doc[]) => { - const docs = toList(docIn); - if (docs?.length === 1) { - const placeholder = DocListCast(this.dataDoc[this.fieldKey]).find(d => - (d.accepts_docType === docs[0].$type || // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type - RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type))) - ); // prettier-ignore - - if (placeholder) { - // ugh. we have to tell the underlying view not to add the Doc so that we can add it where we want it. - // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo. - setTimeout( - undoable(() => { - //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field])); // // shouldn't need to do this for layout fields since the placeholder already overrides its protos - placeholder.proto = docs[0]; - }, 'Scrapbook add') - ); - return false; - } - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => true; // disable dropping documents onto any child of the scrapbook. + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision. + + /** + * Filter function to determine if a document can be added to the scrapbook. + * This checks if the document matches any of the placeholder slots in the scrapbook. + * @param docs - The document(s) being added to the scrapbook. + * @returns true if the document can be added, false otherwise. + */ + filterAddDocument = (docs: Doc | Doc[]) => { + toList(docs).forEach(doc => slotRealDocIntoPlaceholders(doc, DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs))); return false; }; + @computed get regenPrompt() { + const allDocs = DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs); // find all non-collections in scrapbook (e.g., placeholder content docs) + const internalTagsSet = new Set<string>(allDocs.flatMap(doc => StrListCast(doc.$tags_chat).filter(tag => !tag.startsWith?.('ASPECT_')))); + const internalTags = Array.from(internalTagsSet).join(', '); + + return internalTags ? `Create a new scrapbook background featuring: ${internalTags}` : 'A serene mountain landscape at sunrise, ultra-wide, pastel sky, abstract, scrapbook background'; + } + render() { return ( - <div style={{ background: 'beige', width: '100%', height: '100%' }}> - <CollectionView - {...this._props} // - setContentViewBox={emptyFunction} - rejectDrop={this.rejectDrop} - childRejectDrop={this.childRejectDrop} - filterAddDocument={this.filterAddDocument} - /> - {/* <div style={{ border: '1px black', borderStyle: 'dotted', position: 'absolute', top: '50%', width: '100%', textAlign: 'center' }}>Drop an image here</div> */} + <div className="scrapbook-box"> + <div style={{ display: this._loading ? undefined : 'none' }} className="scrapbook-box-loading-overlay"> + <ReactLoading type="spin" width={50} height={50} /> + </div> + + {this.BackgroundDoc && <ImageBox ref={this._imageBoxRef} {...this._props} Document={this.BackgroundDoc} fieldKey="data" />} + <div style={{ display: this._props.isContentActive() ? 'flex' : 'none', alignItems: 'center', justifyContent: 'space-between', padding: '0 10px' }}> + <select className="scrapbook-box-preset-select" value={this._selectedPreset} onChange={action(e => this.selectPreset((this._selectedPreset = e.currentTarget.value)))}> + {getPresetNames().map(name => ( + <option key={name} value={name}> + {name} + </option> + ))} + </select> + <div className="scrapbook-box-ui" style={{ opacity: this._loading ? 0.5 : 1 }}> + <IconButton size={Size.SMALL} tooltip="regenerate a new background" label="back-ground" icon={<FontAwesomeIcon icon="redo-alt" size="sm" />} onClick={() => !this._loading && this.generateAiImage(this.regenPrompt)} /> + </div> + </div> + + <CollectionView {...this._props} setContentViewBox={emptyFunction} rejectDrop={this.rejectDrop} childRejectDrop={this.childRejectDrop} filterAddDocument={this.filterAddDocument} /> </div> ); } } -// Register scrapbook Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, { layout: { view: ScrapbookBox, dataField: 'items' }, options: { diff --git a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx deleted file mode 100644 index ad1d308e8..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import { observer } from "mobx-react-lite"; -// Import the Doc type from your actual module. -import { Doc } from "../../../../fields/Doc"; - -export interface ScrapbookContentProps { - doc: Doc; -} - -// A simple view that displays a document's title and content. -// Adjust how you extract the text if your Doc fields are objects. -export const ScrapbookContent: React.FC<ScrapbookContentProps> = observer(({ doc }) => { - // If doc.title or doc.content are not plain strings, convert them. - const titleText = doc.title ? doc.title.toString() : "Untitled"; - const contentText = doc.content ? doc.content.toString() : "No content available."; - - return ( - <div className="scrapbook-content"> - <h3>{titleText}</h3> - <p>{contentText}</p> - </div> - ); -}); diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx new file mode 100644 index 000000000..a3405083b --- /dev/null +++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx @@ -0,0 +1,94 @@ +import { DocumentType } from '../../../documents/DocumentTypes'; + +export enum ScrapbookPresetType { + None = 'None', + Collage = 'Collage', + Spotlight = 'Spotlight', + Gallery = 'Gallery', + Default = 'Default', + Classic = 'Classic', +} + +export interface ScrapbookItemConfig { + x: number; + y: number; + + message?: string; // optional text to display instead of [placeholder] + acceptTags[0] + type?: DocumentType; + /** what this slot actually accepts (defaults to `tag`) */ + acceptTags?: string[]; + /** the frame this placeholder occupies */ + width?: number; + height?: number; + /** if this is a container with children, use these for the proto’s own size */ + containerWidth?: number; + containerHeight?: number; + children?: ScrapbookItemConfig[]; +} + +export class ScrapbookPreset { + static createPreset(presetType: ScrapbookPresetType): ScrapbookItemConfig[] { + switch (presetType) { + case ScrapbookPresetType.None: return ScrapbookPreset.createNonePreset(); + case ScrapbookPresetType.Classic: return ScrapbookPreset.createClassicPreset(); + case ScrapbookPresetType.Collage: return ScrapbookPreset.createCollagePreset(); + case ScrapbookPresetType.Spotlight: return ScrapbookPreset.createSpotlightPreset(); + case ScrapbookPresetType.Default: return ScrapbookPreset.createDefaultPreset(); + case ScrapbookPresetType.Gallery: return ScrapbookPreset.createGalleryPreset(); + default: + throw new Error(`Unknown preset type: ${presetType}`); + } // prettier-ignore + } + + private static createNonePreset(): ScrapbookItemConfig[] { + return [{ message: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.', type: DocumentType.RTF, acceptTags: [], x: 0, y: 0, width: 250, height: 200 }]; + } + + private static createClassicPreset(): ScrapbookItemConfig[] { + return [ + { type: DocumentType.IMG, message: '[placeholder] landscape', acceptTags: ['LANDSCAPE'], x: 0, y: -100, width: 250, height: 200 }, + { type: DocumentType.RTF, message: '[placeholder] lengthy caption', acceptTags: ['paragraphs'], x: 0, y: 138, width: 250, height: 172 }, + { type: DocumentType.RTF, message: '[placeholder] brief description', acceptTags: ['sentence'], x: 280, y: -50, width: 50, height: 200 }, + { type: DocumentType.IMG, message: '[placeholder] person', acceptTags: ['PERSON'], x: -200, y: -100, width: 167, height: 200 }, + ]; + } + + private static createGalleryPreset(): ScrapbookItemConfig[] { + return [ + { type: DocumentType.IMG, message: 'Gallery 1 <drop person images into the gallery!>', acceptTags: ['PERSON'], x: -150, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 2', acceptTags: ['PERSON'], x: 0, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 3', acceptTags: ['PERSON'], x: 150, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 4', acceptTags: ['PERSON'], x: -150, y: 0, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 5', acceptTags: ['PERSON'], x: 0, y: 0, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 6', acceptTags: ['PERSON'], x: 150, y: 0, width: 150, height: 150 }, + ]; + } + + private static createDefaultPreset(): ScrapbookItemConfig[] { + return [ + { type: DocumentType.IMG, message: 'drop a landscape image', acceptTags: ['LANDSCAPE'], x: 44, y: -50, width: 200, height: 120 }, + { type: DocumentType.PDF, message: 'summary pdf', acceptTags: ['word', 'sentence', 'paragraphs'], x: 45, y: 93, width: 184, height: 273 }, + { type: DocumentType.RTF, message: 'sidebar text', acceptTags: ['paragraphs'], x: 250, y: -50, width: 100, height: 200 }, + { containerWidth: 200, containerHeight: 425, x: -171, y: -54, width: 200, height: 425, + children: [{ type: DocumentType.IMG, message: 'drop a person image', acceptTags: ['PERSON'], x: -350, y: 200, width: 162, height: 137 }], }, + ]; // prettier-ignore + } + + private static createCollagePreset(): ScrapbookItemConfig[] { + return [ + { type: DocumentType.IMG, message: 'landscape image', acceptTags: ['LANDSCAPE'], x: -174, y: 100, width: 160, height: 150 }, + { type: DocumentType.IMG, message: 'person image', acceptTags: ['PERSON'], x: 0, y: 100, width: 150, height: 150 }, + { type: DocumentType.RTF, message: 'caption', acceptTags: ['sentence'], x: -174, y: 50, width: 150, height: 40 }, + { type: DocumentType.RTF, message: 'caption', acceptTags: ['sentence'], x: 0, y: 50, width: 150, height: 40 }, + { type: DocumentType.RTF, message: 'lengthy description', acceptTags: ['paragraphs'], x: -180, y: -60, width: 350, height: 100 }, + ]; // prettier-ignore + } + + private static createSpotlightPreset(): ScrapbookItemConfig[] { + return [ + { type: DocumentType.RTF, message: 'title text', acceptTags: ['word'], x: 0, y: -30, width: 300, height: 40 }, + { type: DocumentType.IMG, message: 'drop a landscape image', acceptTags: ['LANDSCAPE'], x: 0, y: 20, width: 300, height: 200 }, + { type: DocumentType.RTF, message: 'caption text', acceptTags: ['sentence'], x: 0, y: 230, width: 300, height: 50 }, + ]; + } +} diff --git a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts new file mode 100644 index 000000000..3a2189d00 --- /dev/null +++ b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts @@ -0,0 +1,36 @@ +import { ScrapbookItemConfig } from './ScrapbookPreset'; +import { ScrapbookPresetType } from './ScrapbookPreset'; + +type PresetGenerator = () => ScrapbookItemConfig[]; + +// Internal map of preset name to generator +const presetRegistry = new Map<string, PresetGenerator>(); + +/** + * Register a new scrapbook preset under the given name. + */ +export function registerPreset(name: string, gen: PresetGenerator) { + presetRegistry.set(name, gen); +} + +/** + * List all registered preset names. + */ +export function getPresetNames(): string[] { + return Array.from(presetRegistry.keys()); +} + +/** + * Create the config array for the named preset. + */ +export function createPreset(name: string): ScrapbookItemConfig[] { + const gen = presetRegistry.get(name); + if (!gen) throw new Error(`Unknown scrapbook preset: ${name}`); + return gen(); +} + +// ------------------------ +// Register built-in presets +import { ScrapbookPreset } from './ScrapbookPreset'; + +Object.keys(ScrapbookPresetType).forEach(key => registerPreset(key, () => ScrapbookPreset.createPreset(key as ScrapbookPresetType))); // pretter-ignore diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss b/src/client/views/nodes/scrapbook/ScrapbookSlot.scss deleted file mode 100644 index ae647ad36..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss +++ /dev/null @@ -1,85 +0,0 @@ -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -.scrapbook-slot { - position: absolute; - background-color: rgba(245, 245, 245, 0.7); - border: 2px dashed #ccc; - border-radius: 5px; - box-sizing: border-box; - transition: all 0.2s ease; - overflow: hidden; - - &.scrapbook-slot-over { - border-color: #4a90e2; - background-color: rgba(74, 144, 226, 0.1); - } - - &.scrapbook-slot-filled { - border-style: solid; - border-color: rgba(0, 0, 0, 0.1); - background-color: transparent; - - &.scrapbook-slot-over { - border-color: #4a90e2; - background-color: rgba(74, 144, 226, 0.1); - } - } - - .scrapbook-slot-empty { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - } - - .scrapbook-slot-placeholder { - text-align: center; - color: #888; - } - - .scrapbook-slot-title { - font-weight: bold; - margin-bottom: 5px; - } - - .scrapbook-slot-instruction { - font-size: 0.9em; - font-style: italic; - } - - .scrapbook-slot-content { - width: 100%; - height: 100%; - position: relative; - } - - .scrapbook-slot-controls { - position: absolute; - top: 5px; - right: 5px; - z-index: 10; - opacity: 0; - transition: opacity 0.2s ease; - - .scrapbook-slot-remove-btn { - background-color: rgba(255, 255, 255, 0.8); - border: 1px solid #ccc; - border-radius: 50%; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - font-size: 10px; - - &:hover { - background-color: rgba(255, 0, 0, 0.1); - } - } - } - - &:hover .scrapbook-slot-controls { - opacity: 1; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx deleted file mode 100644 index 2c8f93778..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx +++ /dev/null @@ -1,28 +0,0 @@ - -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -export interface SlotDefinition { - id: string; - x: number; y: number; - defaultWidth: number; - defaultHeight: number; - } - - export interface SlotContentMap { - slotId: string; - docId?: string; - } - - export interface ScrapbookConfig { - slots: SlotDefinition[]; - contents?: SlotContentMap[]; - } - - export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = { - slots: [ - { id: "slot1", x: 10, y: 10, defaultWidth: 180, defaultHeight: 120 }, - { id: "slot2", x: 200, y: 10, defaultWidth: 180, defaultHeight: 120 }, - // …etc - ], - contents: [] - }; -
\ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts deleted file mode 100644 index 686917d9a..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ScrapbookSlotTypes.ts -export interface SlotDefinition { - id: string; - title: string; - x: number; - y: number; - defaultWidth: number; - defaultHeight: number; - } - - export interface ScrapbookConfig { - slots: SlotDefinition[]; - contents?: { slotId: string; docId: string }[]; - } - - // give it three slots by default: - export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = { - slots: [ - { id: "main", title: "Main Content", x: 20, y: 20, defaultWidth: 360, defaultHeight: 200 }, - { id: "notes", title: "Notes", x: 20, y: 240, defaultWidth: 360, defaultHeight: 160 }, - { id: "resources", title: "Resources", x: 400, y: 20, defaultWidth: 320, defaultHeight: 380 }, - ], - contents: [], - }; -
\ No newline at end of file diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss index e24b47bd1..8875f7012 100644 --- a/src/client/views/nodes/trails/PresBox.scss +++ b/src/client/views/nodes/trails/PresBox.scss @@ -229,7 +229,7 @@ .toolbar-transition { display: flex; font-size: 10; - width: 100; + width: 100px; background-color: rgba(0, 0, 0, 0); min-width: max-content; @@ -358,7 +358,7 @@ font-weight: 200; padding: 8px; border-radius: 4px; - // height: 20; + // height: 20px; // display: flex; // margin-left: 5px; // margin-top: 5px; @@ -371,8 +371,8 @@ } .ribbon-propertyUpDown { - height: 20; - width: 20; + height: 20px; + width: 20px; margin-top: 5px; display: grid; grid-template-rows: 10px 10px; @@ -486,7 +486,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - margin: 0; + margin: 0px; margin-right: 3px; border-radius: 100%; height: 15px; @@ -658,9 +658,9 @@ height: 25px; color: white; width: 100%; - max-width: 120; - padding-left: 10; - padding-right: 10; + max-width: 120px; + padding-left: 10px; + padding-right: 10px; border-radius: 10px; background-color: global.$medium-gray; } @@ -683,9 +683,9 @@ height: 25px; color: global.$light-gray; width: 100%; - max-width: 120; - padding-left: 10; - padding-right: 10; + max-width: 120px; + padding-left: 10px; + padding-right: 10px; border-radius: 10px; background-color: global.$black; } @@ -745,9 +745,9 @@ .selectedList { display: block; - min-width: 50; - max-width: 120; - height: 70; + min-width: 50px; + max-width: 120px; + height: 70px; overflow-y: scroll; .selectedList-items { @@ -760,7 +760,7 @@ cursor: pointer; font-size: 10.5; font-weight: 300; - height: 20; + height: 20px; background-color: global.$medium-gray; color: white; display: flex; @@ -788,7 +788,7 @@ cursor: pointer; font-size: 10.5; font-weight: 200; - height: 20; + height: 20px; background-color: global.$white; display: inline-flex; color: global.$black; @@ -813,7 +813,7 @@ } svg.svg-inline--fa.fa-thumbtack.fa-w-12.toolbar-thumbtack { - right: 40; + right: 40px; position: absolute; transform: rotate(45deg); } @@ -850,7 +850,7 @@ background-color: global.$light-gray; border-radius: 5px; font-size: 10; - height: 25; + height: 25px; color: global.$black; padding-left: 5px; align-items: center; @@ -868,8 +868,8 @@ display: block; padding-left: 10px; padding-right: 5px; - padding-top: 3; - padding-bottom: 3; + padding-top: 3px; + padding-bottom: 3px; opacity: 0.8; } @@ -959,8 +959,8 @@ border-radius: 4px; padding-left: 7px; padding-right: 7px; - border-bottom-right-radius: 0; - border-top-right-radius: 0; + border-bottom-right-radius: 0px; + border-top-right-radius: 0px; } .presBox-button-right { @@ -976,8 +976,8 @@ border-radius: 4px; padding-left: 7px; padding-right: 7px; - border-bottom-left-radius: 0; - border-top-left-radius: 0; + border-bottom-left-radius: 0px; + border-top-left-radius: 0px; } .presBox-button-right.active { @@ -1043,8 +1043,8 @@ cursor: pointer; align-self: center; justify-self: center; - margin-top: 5; - margin-bottom: 5; + margin-top: 5px; + margin-bottom: 5px; position: relative; height: 55px; min-width: 90px; @@ -1063,7 +1063,7 @@ padding-left: 3px; margin-left: 3px; margin-right: 3px; - height: 13; + height: 13px; font-size: 12; display: flex; background-color: global.$white; @@ -1076,7 +1076,7 @@ margin-left: 3px; margin-right: 3px; font-weight: 400; - height: 13; + height: 13px; font-size: 9; display: flex; background-color: global.$white; @@ -1089,11 +1089,11 @@ padding-left: 3px; margin-left: 3px; margin-right: 3px; - height: 13; + height: 13px; font-size: 10; display: flex; background-color: global.$white; - height: 33; + height: 33px; text-align: left; font-size: 8px; } @@ -1112,7 +1112,7 @@ .presBox-viewPicker { cursor: pointer; - height: 25; + height: 25px; position: relative; display: inline-block; grid-column: 1; @@ -1184,9 +1184,9 @@ } .collectionViewBaseChrome-viewPicker { - min-width: 50; + min-width: 50px; width: 5%; - height: 25; + height: 25px; position: relative; display: inline-block; left: 8px; @@ -1210,11 +1210,11 @@ } .presBox-backward { - left: 5; + left: 5px; } .presBox-forward { - right: 5; + right: 5px; } // CSS adjusted for mobile devices @@ -1233,8 +1233,8 @@ .presBox-button { margin-top: 5%; - height: 250; - width: 300; + height: 250px; + width: 300px; font-size: 100; display: flex; align-items: center; @@ -1256,7 +1256,7 @@ } .presBox-cont .presBox-listCont { - top: 50; + top: 50px; height: calc(100% - 80px); } @@ -1269,8 +1269,8 @@ .miniPres { cursor: grab; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; opacity: 0.5; transition: all 0.4s; color: global.$white; @@ -1298,7 +1298,7 @@ .presPanel-button-text { cursor: pointer; display: flex; - height: 20; + height: 20px; width: max-content; font-family: Roboto; font-weight: 400; @@ -1327,8 +1327,8 @@ grid-template-columns: auto auto auto; justify-content: space-around; font-size: 11; - margin-left: 7; - width: 30; + margin-left: 7px; + width: 30px; height: 85%; background-color: rgba(91, 157, 221, 0.4); border-radius: 5px; @@ -1337,8 +1337,8 @@ .presPanel-button { cursor: pointer; display: flex; - height: 20; - min-width: 20; + height: 20px; + min-width: 20px; margin-left: 3px; margin-right: 3px; border-radius: 100%; @@ -1361,8 +1361,8 @@ // cursor: grab; // position: absolute; // overflow: hidden; -// right: 10; -// top: 10; +// right: 10px; +// top: 10px; // opacity: 0.1; // transition: all 0.4s; // /* border: solid 1px; */ @@ -1380,7 +1380,7 @@ // .miniPres-button-text { // cursor: pointer; // display: flex; -// height: 20; +// height: 20px; // font-weight: 400; // min-width: 100%; // border-radius: 5px; @@ -1397,8 +1397,8 @@ // grid-template-columns: auto auto auto; // justify-content: space-around; // font-size: 11; -// margin-left: 7; -// width: 30; +// margin-left: 7px; +// width: 30px; // height: 85%; // background-color: rgba(91, 157, 221, 0.4); // border-radius: 5px; @@ -1413,8 +1413,8 @@ // .miniPres-button { // cursor: pointer; // display: flex; -// height: 20; -// min-width: 20; +// height: 20px; +// min-width: 20px; // border-radius: 100%; // align-items: center; // justify-content: center; diff --git a/src/client/views/nodes/trails/PresSlideBox.scss b/src/client/views/nodes/trails/PresSlideBox.scss index 9ac2b5a94..740bcae5f 100644 --- a/src/client/views/nodes/trails/PresSlideBox.scss +++ b/src/client/views/nodes/trails/PresSlideBox.scss @@ -117,8 +117,8 @@ $slide-active: #5b9fdd; height: 100%; position: absolute; border-radius: 3px; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 1; overflow: hidden; } @@ -209,7 +209,7 @@ $slide-active: #5b9fdd; position: absolute; /* grid-row: 3; */ /* grid-column: 1/8; */ - top: 28; + top: 28px; display: block; background: #92adb9; width: 100%; @@ -276,7 +276,7 @@ $slide-active: #5b9fdd; cursor: pointer; position: absolute; border-radius: 100px; - bottom: 0; + bottom: 0px; left: -18; z-index: 300; width: 15px; @@ -302,7 +302,7 @@ $slide-active: #5b9fdd; color: #d5dce2; position: absolute; left: -15px; - top: 1; + top: 1px; font-weight: 600; font-size: 12; } diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss index 6990bdcf1..98eeaf80e 100644 --- a/src/client/views/pdf/AnchorMenu.scss +++ b/src/client/views/pdf/AnchorMenu.scss @@ -6,19 +6,19 @@ } .anchorMenu-highlighter { padding-right: 5px; - .antimodeMenu-button { - padding: 0; - padding: 0; + .antimodeMenu-button { + padding: 0px; + padding: 0px; padding-right: 0px; padding-left: 0px; width: 5px; } } -.anchor-color-preview-button { - width: 25px !important; +.anchor-color-preview-button { + width: 25px !important; .anchor-color-preview { display: flex; - flex-direction: column; + flex-direction: column; padding-right: 3px; width: unset !important; .color-preview { @@ -51,4 +51,4 @@ border: 2px solid white; } } -}
\ No newline at end of file +} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index f6fa45221..85c1f6759 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -20,8 +20,8 @@ $headingHeight: 32px; left: 75px; width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; border-top: solid gray 20px; border-radius: 16px; z-index: 999; @@ -58,7 +58,7 @@ $headingHeight: 32px; font-size: 12px; font-weight: 400; letter-spacing: 1px; - margin: 0; + margin: 0px; padding-right: 5px; } @@ -214,8 +214,8 @@ $headingHeight: 32px; .img-container::after { content: ''; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index a54505443..dafa908a2 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -3,8 +3,8 @@ width: 100%; position: absolute; display: inline-block; - top: 0; - left: 0; + top: 0px; + left: 0px; } :root { @@ -16,8 +16,8 @@ position: absolute; width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; overflow-y: auto; overflow-x: hidden; @@ -99,7 +99,7 @@ .pdfViewerDash-annotationLayer { position: absolute; transform-origin: left top; - top: 0; + top: 0px; width: 100%; pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 3ad5bc844..84404d65a 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -9,6 +9,7 @@ import { ImageField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; +import { DocumentView } from '../nodes/DocumentView'; /** * A singleton class that handles face recognition and manages face Doc collections for each face found. @@ -33,7 +34,7 @@ export class FaceRecognitionHandler { // eslint-disable-next-line no-use-before-define static _instance: FaceRecognitionHandler; private _apiModelReady = false; - private _pendingAPIModelReadyDocs: Doc[] = []; + private _pendingAPIModelReadyDocs: DocumentView[] = []; public static get Instance() { return FaceRecognitionHandler._instance ?? new FaceRecognitionHandler(); @@ -126,7 +127,7 @@ export class FaceRecognitionHandler { constructor() { FaceRecognitionHandler._instance = this; this.loadAPIModels().then(() => this._pendingAPIModelReadyDocs.forEach(this.classifyFacesInImage)); - DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.classifyFacesInImage(dv.Document)); + DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.classifyFacesInImage(dv)); } /** @@ -199,16 +200,18 @@ export class FaceRecognitionHandler { * match them to existing unique faces, otherwise new unique face(s) are created. * @param imgDoc The document being analyzed. */ - private classifyFacesInImage = async (imgDoc: Doc) => { + private classifyFacesInImage = async (imgDocView: DocumentView) => { + const imgDoc = imgDocView.Document; if (!Doc.UserDoc().recognizeFaceImages) return; const activeDashboard = Doc.ActiveDashboard; if (!this._apiModelReady || !activeDashboard) { - this._pendingAPIModelReadyDocs.push(imgDoc); + this._pendingAPIModelReadyDocs.push(imgDocView); } else if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) { - setTimeout(() => this.classifyFacesInImage(imgDoc), 1000); + setTimeout(() => this.classifyFacesInImage(imgDocView), 1000); } else { const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]); if (imgUrl && !DocListCast(Doc.MyFaceCollection?.examinedFaceDocs).includes(imgDoc[DocData])) { + imgDocView.ComponentView?.autoTag?.(); // only examine Docs that have an image and that haven't already been examined. Doc.MyFaceCollection && Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]); FaceRecognitionHandler.loadImage(imgUrl).then( diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index 35a3da312..ca177c746 100644 --- a/src/client/views/topbar/TopBar.scss +++ b/src/client/views/topbar/TopBar.scss @@ -125,7 +125,7 @@ .topbar-lozenge-user, .topbar-lozenge { - height: 23; + height: 23px; font-size: 12; color: white; font-family: 'Roboto'; diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index f3295a6c6..354ed11d6 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -1,8 +1,5 @@ /* eslint-disable react/no-unescaped-entities */ -/* eslint-disable react/button-has-type */ /* eslint-disable no-use-before-define */ -/* eslint-disable react/no-array-index-key */ -/* eslint-disable no-redeclare */ import { action, configure, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/fields/FieldLoader.scss b/src/fields/FieldLoader.scss index 9a23c3e4f..62d85a763 100644 --- a/src/fields/FieldLoader.scss +++ b/src/fields/FieldLoader.scss @@ -1,7 +1,7 @@ .fieldLoader { z-index: 10000; width: 200px; - height: 30; + height: 30px; background: lightblue; position: absolute; left: calc(50% - 99px); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 7c55e4a42..1e68a4e30 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -18,7 +18,7 @@ export default class UploadManager extends ApiManager { protected initialize(register: Registration): void { register({ method: Method.POST, - subscription: '/ping', + subscription: '/DashPing', secureHandler: async ({ /* req, */ res }) => { _success(res, { message: DashVersion, date: new Date() }); }, diff --git a/src/server/PdfTypes.ts b/src/server/PdfTypes.ts index fb435a677..458df7b26 100644 --- a/src/server/PdfTypes.ts +++ b/src/server/PdfTypes.ts @@ -2,7 +2,7 @@ export interface PDFInfo { PDFFormatVersion: string; IsAcroFormPresent: boolean; IsXFAPresent: boolean; - [key: string]: any; + [key: string]: unknown; } export interface PDFMetadata { parse(): void; diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 514e2ce1e..641a88312 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -125,10 +125,13 @@ function registerCorsProxy(server: express.Express) { //res.status(400).json({ error: 'Invalid URL format' }); return; } + + const isBinary = /\.(gif|png|jpe?g|bmp|webp|ico|pdf|zip|mp3|mp4|wav|ogg)$/i.test(targetUrl as string); + const responseType = isBinary ? 'arraybuffer' : 'text'; const response = await axios.get(targetUrl as string, { headers: { 'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0' }, - responseType: 'text', + responseType: responseType }); const baseUrl = new URL(targetUrl as string); |