diff options
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 112 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 44 | ||||
-rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookBox.scss | 87 | ||||
-rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 645 | ||||
-rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookPreset.tsx | 169 | ||||
-rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts | 20 |
7 files changed, 353 insertions, 727 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4ad9c9bd8..aea636040 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/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b2b904509..12515a72c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -81,14 +81,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 - - - + // ─── 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(); @@ -532,42 +529,39 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.fadeOut(true); }); - getAiPresetsDescriptors(): DocumentDescriptor[] { - const selected = this.marqueeSelect(false); - return selected.map((doc) => ({ - type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', - tags: (() => { - const s = new Set<string>(); - StrListCast(doc.$tags_chat ?? new List<string>()).forEach((t) => s.add(t)); - return Array.from(s); - })(), - })); - } - + const selected = this.marqueeSelect(false); + return selected.map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: (() => { + const s = new Set<string>(); + StrListCast(doc.$tags_chat ?? new List<string>()).forEach(t => s.add(t)); + return Array.from(s); + })(), + })); + } 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 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"); + 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.accepts_docType = cfg.type as DocumentType; - placeholderDoc.accepts_tagType = new List<string>(cfg.acceptTags ?? [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; @@ -586,25 +580,17 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps y: this.Bounds.top, _width: 500, _height: 500, - title: 'AI-generated Scrapbook' + title: 'AI-generated Scrapbook', }); - - // 3) Now grab that new scrapbook’s flat placeholders const flatPl = DocListCast(scrapbook[Doc.LayoutDataKey(scrapbook)]) as Doc[]; - const unwrap = (items: Doc[]): Doc[] => - items.flatMap(d => - d.$type === DocumentType.COL - ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)])) - : [d] - ); + const unwrap = (items: Doc[]): Doc[] => items.flatMap(d => (d.$type === DocumentType.COL ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)])) : [d])); const allPlaceholders = unwrap(flatPl); // 4) Slot each selectedDocs[i] into the first matching placeholder selectedDocs.forEach(realDoc => { - slotRealDocIntoPlaceholders(realDoc, allPlaceholders - ); + slotRealDocIntoPlaceholders(realDoc, allPlaceholders); }); const selected = this.marqueeSelect(false).map(d => { @@ -625,9 +611,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps this.hideMarquee(); }); - - - @action marqueeCommand = (e: KeyboardEvent) => { const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean }; @@ -649,7 +632,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 === 'g' || e.key === 'G') this.generateScrapbook(); // ← scrapbook shortcut if (e.key === 'p') this.pileup(); this.cleanupInteractions(false); } @@ -795,27 +778,26 @@ 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/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0c3179173..1768eb08d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -8,7 +8,6 @@ import { baseKeymap, selectAll, splitBlock } from 'prosemirror-commands'; import { history } from 'prosemirror-history'; import { inputRules } from 'prosemirror-inputrules'; import { keymap } from 'prosemirror-keymap'; -import { runInAction } from 'mobx'; import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView, NodeViewConstructor } from 'prosemirror-view'; @@ -65,7 +64,6 @@ import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; -import { tickStep } from 'd3'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -310,30 +308,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } }; - autoTag = async () => { + autoTag = () => { + const rawText = RTFCast(this.Document[this.fieldKey])?.Text ?? StrCast(this.Document[this.fieldKey]); + const callType = rawText.includes('[placeholder]') ? GPTCallType.CLASSIFYTEXTMINIMAL : GPTCallType.CLASSIFYTEXTFULL; - const layoutKey = Doc.LayoutDataKey(this.Document); - const rawText = RTFCast(this.Document[layoutKey])?.Text ?? StrCast(this.Document[layoutKey]); - - const callType = rawText.includes("[placeholder]") - ? GPTCallType.CLASSIFYTEXTMINIMAL - : GPTCallType.CLASSIFYTEXTFULL; - - gptAPICall(rawText, callType).then(desc => { - runInAction(() => { - // Clear existing tags - this.Document.$tags_chat = new List<string>(); - - // Split GPT response into tokens and push individually - const tokens = desc.trim().split(/\s+/); - tokens.forEach(tok => { - (this.Document.$tags_chat as List<string>).push(tok); - }); - - this.Document._layout_showTags = true; - }); - }); -}; + 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) { @@ -1298,13 +1284,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB { fireImmediately: true } ); - this._disposers.tagger = reaction( - () => ({ title: this.Document.title, sel: this._props.isSelected() }), - action(() => { - this.autoTag(); - }), - { fireImmediately: true } - ); + this._disposers.tagger = reaction(() => ({ title: this.Document.title, sel: this._props.isSelected() }), this.autoTag, { fireImmediately: true }); if (!this._props.dontRegisterView) { this._disposers.record = reaction( diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.scss b/src/client/views/nodes/scrapbook/ScrapbookBox.scss index 8dc93df60..6ac2220f9 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.scss +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.scss @@ -1,63 +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 */ + /* 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 */ + 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: absolute; - top: 8px; - left: 8px; - z-index: 20; - padding: 4px 8px; - font-size: 14px; - border: 1px solid #ccc; - border-radius: 4px; - background: white; + 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: absolute; - top: 8px; - right: 8px; - z-index: 20; + 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; + 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; + background: #f5f5f5; } diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index 52e3c26dc..fcb82a6ba 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -1,510 +1,259 @@ -import { action, makeObservable, observable, reaction, computed } 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, StrListCast } 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 } from '../../smartdraw/FireflyConstants'; +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 { toList } from '../../../../fields/Types'; -import { undoable } from '../../../util/UndoManager'; -import ReactLoading from 'react-loading'; -import { NumCast } from '../../../../fields/Types'; -import { ScrapbookItemConfig } from './ScrapbookPreset'; import { ImageBox } from '../ImageBox'; -import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; -import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; -import { ImageCast } from '../../../../fields/Types'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { IReactionDisposer } from 'mobx'; -import { observer } from 'mobx-react'; -import { runInAction } from 'mobx'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; -import { getPresetNames, createPreset } from './ScrapbookPresetRegistry'; import './ScrapbookBox.scss'; -import { isDestArraysEqual } from 'pdfjs-dist/types/web/pdf_history'; - - -export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Doc[] { - const placeholders: Doc[] = []; - - for (const cfg of configs) { - if (cfg.children && cfg.children.length) { - const childDocs = cfg.children.map(child => { - const doc = Docs.Create.TextDocument("[placeholder] " + child.tag); - doc.accepts_docType = child.type; - doc.accepts_tagType = new List<string>(child.acceptTags ?? [child.tag]); - - const ph = new Doc(); - ph.proto = doc; - ph.original = doc; - ph.x = child.x; - ph.y = child.y; - if (child.width != null) ph._width = child.width; - if (child.height != null) ph._height = child.height; - return ph; - }); - - 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.tag, - }); - - const ph = new Doc(); - ph.proto = containerProto; - ph.original = containerProto; - ph.x = cfg.x; - ph.y = cfg.y; - if (cfg.width != null) ph._width = cfg.width; - if (cfg.height != null) ph._height = cfg.height; - placeholders.push(ph); - } - - else { - const doc = Docs.Create.TextDocument("[placeholder] " + cfg.tag); - doc.accepts_docType = cfg.type; - doc.accepts_tagType = new List<string>(cfg.acceptTags ?? [cfg.tag]); - - const ph = new Doc(); - ph.proto = doc; - ph.original = doc; - ph.x = cfg.x; - ph.y = cfg.y; - if (cfg.width != null) ph._width = cfg.width; - if (cfg.height != null) ph._height = cfg.height; - placeholders.push(ph); - } - } +import { ScrapbookItemConfig } from './ScrapbookPreset'; +import { createPreset, getPresetNames } from './ScrapbookPresetRegistry'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - return placeholders; +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; } -export function slotRealDocIntoPlaceholders( - realDoc: Doc, - placeholders: Doc[] -): boolean { - const realTags = new Set<string>( - StrListCast(realDoc.$tags_chat ?? new List<string>()) - .map(t => t.toLowerCase()) -); - // Find placeholder with most matching tags - let bestMatch: Doc | null = null; - let maxMatches = 0; -/* - (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)))*/ - - placeholders.forEach(ph => { - if (ph.accepts_docType !== realDoc.$type) { - // Skip this placeholder entirely if types do not match. - return; - }; - const phTagTypes = StrListCast(ph.accepts_tagType ?? new List<string>()) - .map(t => t.toLowerCase()); - console.log({ realTags, phTagTypes }); - const matches = phTagTypes.filter(tag => realTags.has(tag)); +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); + } + return createMessagePlaceholder(cfg); + }); +} +export function slotRealDocIntoPlaceholders(realDoc: Doc, placeholders: Doc[]): boolean { + 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 (matches.length > maxMatches) { - maxMatches = matches.length; - bestMatch = ph; + if (bestMatch && maxMatches > 0) { + setTimeout(undoable(() => (bestMatch!.proto = realDoc), 'Scrapbook add')); + return true; } - }); - - if (bestMatch && maxMatches > 0) { - setTimeout( - undoable(() => { - bestMatch!.proto = realDoc; - }, 'Scrapbook add'), - 0 - ); - return true; - } - - return false; + return false; } // Scrapbook view: a container that lays out its child items in a template @observer export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - @observable selectedPreset = getPresetNames()[0]; - - @observable createdDate: string; - @observable loading = false; - @observable src = ''; - @observable imgDoc: Doc | undefined; + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(ScrapbookBox, fieldStr); + } private _disposers: { [name: string]: IReactionDisposer } = {}; - private imageBoxRef = React.createRef<ImageBox>(); - + private _imageBoxRef = React.createRef<ImageBox>(); constructor(props: FieldViewProps) { super(props); makeObservable(this); - const existingItems = DocListCast(this.dataDoc[this.fieldKey] as List<Doc>); - if (!existingItems || existingItems.length === 0) { - // Only wire up reaction/setTitle if it's truly a brand-new, empty Scrapbook - reaction( - () => this.selectedPreset, - presetName => this.initScrapbook(presetName), - { fireImmediately: true } - ); - - this.createdDate = this.getFormattedDate(); - this.setTitle(); - } else { - // If items are already present, just preserve whatever was injected. - // We still want `createdDate` set so that the UI title bar can show it if needed. - this.createdDate = this.getFormattedDate(); } - - // ensure we always have a List<Doc> in dataDoc['items'] - if (!this.dataDoc[this.fieldKey]) { - this.dataDoc[this.fieldKey] = new List<Doc>(); - } - - } - - public static LayoutString(fieldStr: string) { - return FieldView.LayoutString(ScrapbookBox, fieldStr); - } + @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', }); } - - - @action - initScrapbook(name: string) { - const configs = createPreset(name); - // 1) ensure title is set - const title = `Scrapbook - ${this.createdDate}`; - if (this.dataDoc.title !== title) { - this.dataDoc.title = title; - } - - // 2) build placeholders from the preset - const placeholders = buildPlaceholdersFromConfigs(configs); - - // 3) commit them into the field - this.dataDoc[this.fieldKey] = new List<Doc>(placeholders); - } - - + @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; - if (!this.dataDoc[this.fieldKey]){ - const image = Docs.Create.TextDocument('[placeholder] person image'); - image.accepts_docType = DocumentType.IMG; - image.accepts_tagType = 'PERSON' - const placeholder = new Doc(); - placeholder.proto = image; - placeholder.original = image; - placeholder._width = 250; - placeholder._height = 200; - placeholder.x = 0; - placeholder.y = -100; - - - const summary = Docs.Create.TextDocument('[placeholder] long summary'); - summary.accepts_docType = DocumentType.RTF; - summary.accepts_tagType = 'lengthy description'; - const placeholder2 = new Doc(); - placeholder2.proto = summary; - placeholder2.original = summary; - placeholder2.x = 0; - placeholder2.y = 200; - placeholder2._width = 250; - - - const sidebar = Docs.Create.TextDocument('[placeholder] brief sidebar'); - sidebar.accepts_docType = DocumentType.RTF; - sidebar.accepts_tagType = 'title'; - const placeholder3 = new Doc(); - placeholder3.proto = sidebar; - placeholder3.original = sidebar; - placeholder3.x = 280; - placeholder3.y = -50; - placeholder3._width = 50; - placeholder3._height = 200; - - - - const internalImg = Docs.Create.TextDocument('[placeholder] landscape internal'); - internalImg.accepts_docType = DocumentType.IMG; - internalImg.accepts_tagType = 'LANDSCAPE' - const placeholder5 = new Doc(); - placeholder5.proto = internalImg; - placeholder5.original = internalImg; - placeholder5._width = 50; - placeholder5._height = 100; - placeholder5.x = 0; - placeholder5.y = -100; - - const collection = Docs.Create.StackingDocument([placeholder5], { _width: 300, _height: 300, title: "internal coll" }); - //collection.accepts_docType = DocumentType.COL; don't mark this field - const placeholder4 = new Doc(); - placeholder4.proto = collection; - placeholder4.original = collection; - placeholder4.x = -200; - placeholder4.y = -100; - placeholder4._width = 100; - placeholder4._height = 200; - - const starter = Docs.Create.TextDocument('To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.'); - starter.accepts_docType = DocumentType.RTF; - starter.accepts_tagType = 'n/a' - const starterplaceholder = new Doc(); - starterplaceholder.proto = summary; - starterplaceholder.original = summary; - starterplaceholder.x = 0; - starterplaceholder.y = 0; - starterplaceholder._width = 250; - - - - - - /*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? - */ - this.dataDoc[this.fieldKey] = new List<Doc>([starterplaceholder]); - } + 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(); - this.generateAiImage(); + 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) => { - // prev is undefined on the first run, so bail early - if (!prev || !SnappingManager.ShiftKey || !this.imgDoc) return; - - // either guard the ref… - const imageBox = this.imageBoxRef.current; - if (!imageBox) return; - - // …or just hard-code the fieldKey if you know it’s always `"data"` - const key = imageBox.props.fieldKey; - - runInAction(() => { - if(!this.imgDoc){ - return + 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; } - // use prev.w/h (the *old* size) as your orig dims - this.imgDoc[key + '_outpaintOriginalWidth'] = prev.w; - this.imgDoc[key + '_outpaintOriginalHeight'] = prev.h; - ;(this.imageBoxRef.current as any).layoutDoc._width = dims.w - ;(this.imageBoxRef.current as any).layoutDoc._height = dims.h - - }); } ); } - @action - async generateAiImage(prompt?: string) { - this.loading = true; - try { - // 1) Default to regenPrompt if none provided - if (!prompt) prompt = this.regenPrompt; - - // 2) Measure the scrapbook’s current size - const w = NumCast(this.layoutDoc._width, 1); - const h = NumCast(this.layoutDoc._height, 1); - const ratio = w / h; - - // 3) Pick the Firefly preset that best matches the aspect ratio - let preset = FireflyImageDimensions.Square; - if (ratio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) { - preset = FireflyImageDimensions.Widescreen; - } else if (ratio > AspectRatioLimits[FireflyImageDimensions.Landscape]) { - preset = FireflyImageDimensions.Landscape; - } else if (ratio < AspectRatioLimits[FireflyImageDimensions.Portrait]) { - preset = FireflyImageDimensions.Portrait; - } - - // 4) Call exactly the same CreateWithFirefly that ImageBox uses - const doc = await SmartDrawHandler.CreateWithFirefly(prompt, preset); - - if (doc instanceof Doc) { - // 5) Hook it into your state - this.imgDoc = doc; - const imgField = ImageCast(doc.data); - this.src = imgField?.url.href ?? ''; - } else { - alert('Failed to generate document.'); - this.src = ''; - } - } catch (e) { - alert(`Generation error: ${e}`); - } finally { - runInAction(() => { - this.loading = false; - }); - } - } - - 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. + 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); //The docs being added to the scrapbook - - // 1) Grab all template slots: - const slots = DocListCast(this.dataDoc[this.fieldKey]); - - // 2) recursive unwrap: - const unwrap = (items: Doc[]): Doc[] => - items.flatMap(d => - d.$type === DocumentType.COL - ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)])) - : [d] - ); - - // 3) produce a flat list of every doc, unwrapping any number of nested COLs - const allDocs: Doc[] = unwrap(slots); - if (docs?.length === 1) { - return slotRealDocIntoPlaceholders( - docs[0], - allDocs, - ) - ? false - : false; - } - - 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. + + /** + * 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 + */ + unwrapPlaceholders = (items: Doc[]): Doc[] => items.flatMap(d => (d.$type === DocumentType.COL ? this.unwrapPlaceholders(DocListCast(d[Doc.LayoutDataKey(d)])) : [d])); + + /** + * 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, this.unwrapPlaceholders(this.ScrapbookLayoutDocs))); + return false; + }; @computed get regenPrompt() { - const slots = DocListCast(this.dataDoc[this.fieldKey]); - - const unwrap = (items: Doc[]): Doc[] => - items.flatMap(d => - d.$type === DocumentType.COL - ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)])) - : [d] - ); - - const allDocs: Doc[] = unwrap(slots); - const internalTagsSet = new Set<string>(); - - allDocs.forEach(doc => { - const tags = StrListCast(doc.$tags_chat ?? new List<string>()); - tags.forEach(tag => - {if (!tag.startsWith("ASPECT_")) { - internalTagsSet.add(tag); - } - }); - }); - + const allDocs = this.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 className="scrapbook-box"> - {this.loading && ( - <div className="scrapbook-box-loading-overlay"> - <ReactLoading type="spin" width={50} height={50} /> - </div> - )} - - {this.src && this.imgDoc && ( - <ImageBox - ref={this.imageBoxRef} - {...this._props} - Document={this.imgDoc} - fieldKey="data" - /> - )} - - <select - className="scrapbook-box-preset-select" - value={this.selectedPreset} - onChange={e => (this.selectedPreset = e.currentTarget.value)} - > - {getPresetNames().map(name => ( - <option key={name} value={name}> - {name} - </option> - ))} - </select> - {this._props.isContentActive() && ( - <div className="scrapbook-box-ui"> - <button - type="button" - title="Regenerate Background" - onClick={() => this.generateAiImage(this.regenPrompt)} - className="scrapbook-box-ui-button" - > - <FontAwesomeIcon icon={faRedoAlt} /> - <span>Regenerate Background</span> - </button> - </div> - )} + return internalTags ? `Create a new scrapbook background featuring: ${internalTags}` : 'A serene mountain landscape at sunrise, ultra-wide, pastel sky, abstract, scrapbook background'; + } - <CollectionView - {...this._props} - setContentViewBox={emptyFunction} - rejectDrop={this.rejectDrop} - childRejectDrop={this.childRejectDrop} - filterAddDocument={this.filterAddDocument} - /> - </div> - ); - } + render() { + return ( + <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> + ); + } } - - Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, { layout: { view: ScrapbookBox, dataField: 'items' }, options: { diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx index 706b9dafd..fe33741af 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx @@ -1,23 +1,22 @@ import { DocumentType } from '../../../documents/DocumentTypes'; export enum ScrapbookPresetType { - Default = 'Default', - Classic = 'Classic', + Default = 'Default', + Classic = 'Classic', None = 'Select Template', - Collage = 'Collage', + Collage = 'Collage', Spotlight = 'Spotlight', - Gallery = 'Gallery' + Gallery = 'Gallery', } export interface ScrapbookItemConfig { - type: DocumentType; - /** text shown in the placeholder bubble */ - tag: string; - /** what this slot actually accepts (defaults to `tag`) */ - acceptTags?: string[]; - 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; @@ -30,147 +29,65 @@ export interface 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(); + 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 [ - - { type: DocumentType.RTF, - tag: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.', - acceptTags: ['n/a'], - x: 0, y: 0, width: 250, height: 200 - }, - - ]; + 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, - tag: '[placeholder] LANDSCAPE', - acceptTags: ['LANDSCAPE'], - x: 0, y: -100, width: 250, height: 200 - }, - { type: DocumentType.RTF, - tag: '[placeholder] caption', - acceptTags: ['sentence'], - x: 0, y: 200, width: 250, height: 50 - }, - { type: DocumentType.RTF, - tag: '[placeholder] lengthy description', - acceptTags: ['paragraphs'], - x: 280, y: -50, width: 50, height: 200 - }, - { type: DocumentType.IMG, - tag: '[placeholder] PERSON', - acceptTags: ['PERSON'], - x: -200, y: -100, width: 100, height: 200 - }, + { type: DocumentType.IMG, message: '[placeholder] LANDSCAPE', acceptTags: ['LANDSCAPE'], x: 0, y: -100, width: 250, height: 200 }, + { type: DocumentType.RTF, message: '[placeholder] caption', acceptTags: ['sentence'], x: 0, y: 200, width: 250, height: 50 }, + { type: DocumentType.RTF, message: '[placeholder] lengthy description', acceptTags: ['paragraphs'], x: 280, y: -50, width: 50, height: 200 }, + { type: DocumentType.IMG, message: '[placeholder] PERSON', acceptTags: ['PERSON'], x: -200, y: -100, width: 100, height: 200 }, ]; } private static createGalleryPreset(): ScrapbookItemConfig[] { return [ - { type: DocumentType.IMG, tag: 'Gallery 1', acceptTags: ['LANDSCAPE'], x: -150, y: -150, width: 150, height: 150 }, - { type: DocumentType.IMG, tag: 'Gallery 2', acceptTags: ['LANDSCAPE'], x: 0, y: -150, width: 150, height: 150 }, - { type: DocumentType.IMG, tag: 'Gallery 3', acceptTags: ['LANDSCAPE'], x: 150, y: -150, width: 150, height: 150 }, - { type: DocumentType.IMG, tag: 'Gallery 4', acceptTags: ['LANDSCAPE'], x: -150, y: 0, width: 150, height: 150 }, - { type: DocumentType.IMG, tag: 'Gallery 5', acceptTags: ['LANDSCAPE'], x: 0, y: 0, width: 150, height: 150 }, - { type: DocumentType.IMG, tag: 'Gallery 6', acceptTags: ['LANDSCAPE'], x: 150, y: 0, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 1', acceptTags: ['LANDSCAPE'], x: -150, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 2', acceptTags: ['LANDSCAPE'], x: 0, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 3', acceptTags: ['LANDSCAPE'], x: 150, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 4', acceptTags: ['LANDSCAPE'], x: -150, y: 0, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 5', acceptTags: ['LANDSCAPE'], x: 0, y: 0, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'Gallery 6', acceptTags: ['LANDSCAPE'], x: 150, y: 0, width: 150, height: 150 }, ]; - } - + } private static createDefaultPreset(): ScrapbookItemConfig[] { return [ - { type: DocumentType.IMG, - tag: 'image', - acceptTags: ['LANDSCAPE'], - x: 0, y: -100, width: 250, height: 200 - }, - { type: DocumentType.RTF, - tag: 'summary', - acceptTags: ['sentence'], - x: 0, y: 200, width: 250 - }, - { type: DocumentType.RTF, - tag: 'sidebar', - acceptTags: ['paragraphs'], - x: 280, y: -50, width: 50, height: 200 - }, - { - type: DocumentType.COL, - tag: 'internal coll', - x: -200, y: -100, width: 100, height: 200, - containerWidth: 300, containerHeight: 300, - children: [ - { type: DocumentType.IMG, - tag: 'image internal', - acceptTags: ['PERSON'], - x: 0, y: 0, width: 50, height: 100 - } - ] - } - ]; + { type: DocumentType.IMG, message: 'image', acceptTags: ['LANDSCAPE'], x: 0, y: -100, width: 250, height: 200 }, + { type: DocumentType.RTF, message: 'summary', acceptTags: ['sentence'], x: 0, y: 200, width: 250 }, + { type: DocumentType.RTF, message: 'sidebar', acceptTags: ['paragraphs'], x: 280, y: -50, width: 50, height: 200 }, + { containerWidth: 300, containerHeight: 300, x: -200, y: -100, width: 100, height: 200, + children: [{ type: DocumentType.IMG, message: 'image internal', acceptTags: ['PERSON'], x: 0, y: 0, width: 50, height: 100 }], }, + ]; // prettier-ignore } private static createCollagePreset(): ScrapbookItemConfig[] { return [ - { type: DocumentType.IMG, - tag: 'LANDSCAPE', - acceptTags: ['LANDSCAPE'], - x: -150, y: -150, width: 150, height: 150 - }, - { type: DocumentType.IMG, - tag: 'PERSON', - acceptTags: ['PERSON'], - x: 0, y: -150, width: 150, height: 150 - }, - { type: DocumentType.RTF, - tag: 'caption', - acceptTags: ['sentence'], - x: -150, y: 0, width: 300, height: 100 - }, - { type: DocumentType.RTF, - tag: 'lengthy description', - acceptTags: ['paragraphs'], - x: 0, y: 100, width: 300, height: 100 - } - ]; + { type: DocumentType.IMG, message: 'LANDSCAPE', acceptTags: ['LANDSCAPE'], x: -150, y: -150, width: 150, height: 150 }, + { type: DocumentType.IMG, message: 'PERSON', acceptTags: ['PERSON'], x: 0, y: -150, width: 150, height: 150 }, + { type: DocumentType.RTF, message: 'caption', acceptTags: ['sentence'], x: -150, y: 0, width: 300, height: 100 }, + { type: DocumentType.RTF, message: 'lengthy description', acceptTags: ['paragraphs'], x: 0, y: 100, width: 300, height: 100 }, + ]; // prettier-ignore } private static createSpotlightPreset(): ScrapbookItemConfig[] { return [ - { type: DocumentType.RTF, - tag: 'title', - acceptTags: ['word'], - x: 0, y: -180, width: 300, height: 40 - }, - { type: DocumentType.IMG, - tag: 'LANDSCAPE', - acceptTags: ['LANDSCAPE'], - x: 0, y: 0, width: 300, height: 200 - }, - { type: DocumentType.RTF, - tag: 'caption', - acceptTags: ['sentence'], - x: 0, y: 230, width: 300, height: 50 - } + { type: DocumentType.RTF, message: 'title', acceptTags: ['word'], x: 0, y: -180, width: 300, height: 40 }, + { type: DocumentType.IMG, message: 'LANDSCAPE', acceptTags: ['LANDSCAPE'], x: 0, y: 0, width: 300, height: 200 }, + { type: DocumentType.RTF, message: 'caption', 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 index c6d67ab73..3a2189d00 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts +++ b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts @@ -6,39 +6,31 @@ 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); + presetRegistry.set(name, gen); } /** * List all registered preset names. */ export function getPresetNames(): string[] { - return Array.from(presetRegistry.keys()); + 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(); + const gen = presetRegistry.get(name); + if (!gen) throw new Error(`Unknown scrapbook preset: ${name}`); + return gen(); } // ------------------------ // Register built-in presets import { ScrapbookPreset } from './ScrapbookPreset'; -registerPreset(ScrapbookPresetType.None, () => ScrapbookPreset.createPreset(ScrapbookPresetType.None)); -registerPreset(ScrapbookPresetType.Classic, () => ScrapbookPreset.createPreset(ScrapbookPresetType.Classic)); -registerPreset(ScrapbookPresetType.Collage, () => ScrapbookPreset.createPreset(ScrapbookPresetType.Collage)); -registerPreset(ScrapbookPresetType.Spotlight, () => ScrapbookPreset.createPreset(ScrapbookPresetType.Spotlight)); -registerPreset(ScrapbookPresetType.Default, () => ScrapbookPreset.createPreset(ScrapbookPresetType.Default)); -registerPreset(ScrapbookPresetType.Gallery, () => ScrapbookPreset.createPreset(ScrapbookPresetType.Gallery)); +Object.keys(ScrapbookPresetType).forEach(key => registerPreset(key, () => ScrapbookPreset.createPreset(key as ScrapbookPresetType))); // pretter-ignore |