diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 70 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 44 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 51 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 52 | ||||
| -rw-r--r-- | src/client/views/nodes/scrapbook/AIPresetGenerator.ts | 31 | ||||
| -rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 264 | ||||
| -rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookPreset.tsx | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts | 3 |
9 files changed, 314 insertions, 211 deletions
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f5e699d3e..0b91d628b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,6 +28,8 @@ 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 './MarqueeView.scss'; interface MarqueeViewProps { @@ -519,35 +521,71 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); + + + generateScrapbook = action(async () => { + + const selectedDocs = this.marqueeSelect(false); + if (!selectedDocs.length) return; - @undoBatch - generateScrapbook = action(() => { - let docs = new Array<Doc>(); + const descriptors: DocumentDescriptor[] = selectedDocs.map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: (() => { + const internalTagsSet = new Set<string>(); + StrListCast(doc.$tags_chat ?? new List<string>()).forEach(tag => { + internalTagsSet.add(tag); + }); + return Array.from(internalTagsSet); + })() + })); + + + const aiPreset = await requestAiGeneratedPreset(descriptors); + if (!aiPreset.length) { + alert("Failed to generate preset"); + return; + } + + const scrapbookPlaceholders: Doc[] = aiPreset.map(cfg => { + const placeholderDoc = Docs.Create.TextDocument(cfg.tag); + placeholderDoc.accepts_docType = cfg.type as DocumentType; + placeholderDoc.accepts_tagType = cfg.acceptTag ?? 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' + }); const selected = this.marqueeSelect(false).map(d => { this._props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; d.y = NumCast(d.y) - this.Bounds.top; - docs.push(d); return d; }); - const scrapbook = Docs.Create.ScrapbookDocument(docs, { - backgroundColor: '#e2ad32', - x: this.Bounds.left, - y: this.Bounds.top, - followLinkToggle: true, - _width: 200, - _height: 200, - _layout_showSidebar: true, - title: 'overview', - }); + + this._props.addDocument?.(scrapbook); + selectedDocs.forEach(doc => this._props.removeDocument?.(doc)); const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); DocUtils.MakeLink(scrapbook, portal, { link_relationship: 'summary of:summarized by' }); portal.hidden = true; this._props.addDocument?.(portal); - //this._props.addLiveTextDocument(summary); - this._props.addDocument?.(scrapbook); MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bd612d04f..9067f7e0c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -132,7 +132,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const base64 = await imageUrlToBase64(url); if (!base64) throw new Error('Failed to load image data'); - // 3) ask GPT for exactly one label: PERSON or LANDSCAPE + // 3) ask GPT for labels one label: PERSON or LANDSCAPE const raw = await gptImageLabel( base64, `Classify this image as PERSON or LANDSCAPE. You may only respond with one of these two options. Then diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 55e6d5596..282b06215 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -34,6 +34,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>() { @@ -76,6 +79,47 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } } + autoTag = async () => { + try { + if (!this._pdf) { + throw new Error('PDF not loaded'); + } + + // 1) Extract text from the first few pages (e.g., first 2 pages) + const maxPages = Math.min(2, this._pdf.numPages); + let textContent = ''; + for (let pageNum = 1; pageNum <= maxPages; pageNum++) { + const page = await this._pdf.getPage(pageNum); + const text = await page.getTextContent(); + const pageText = text.items.map(item => ('str' in item ? item.str : '')).join(' '); + textContent += ` ${pageText}`; + } + + if (!textContent.trim()) { + throw new Error('No text found in PDF'); + } + + // 2) Ask GPT to classify and provide descriptive tags + const raw = await gptAPICall( + `"${textContent.trim().slice(0, 2000)}"`, + GPTCallType.CLASSIFYTEXTFULL + ); + + // 3) Normalize and store the labels + const label = raw.trim().toUpperCase(); + + const tokens = label.split(/\s+/); + this.Document.$tags_chat = new List<string>(); + tokens.forEach(tok => (this.Document.$tags_chat as List<string>).push(tok)); + + // 4) Show tags in layout + this.Document._layout_showTags = true; + + } catch (err) { + console.error('PDF autoTag failed:', err); + } +}; + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { if (oldDiv.childNodes) { for (let i = 0; i < oldDiv.childNodes.length; i++) { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index fa099178c..0e7afbab1 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,56 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return this._videoRef; } + + autoTag = async () => { + 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, reject) => { + 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 raw = 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.` + ); + + // 4) Normalize and store labels in the Document's tags + const label = raw.trim().toUpperCase(); + const tokens = label.split(/\s+/); + this.Document.$tags_chat = new List<string>(); + tokens.forEach(tok => (this.Document.$tags_chat as List<string>).push(tok)); + + // 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/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 10becc00b..04a14a15f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -8,6 +8,7 @@ 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'; @@ -305,12 +306,53 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } }; - autoTag = async () => { + autoTag = async () => { + + 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; + }); + }); + /*this.Document.$tags_chat = new List<string>(); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => (this.Document.$tags_chat as List<string>).push(desc)); + this.Document._layout_showTags = true;*/ + + + // 2) grab whatever’s actually in the field (either RTF or plain string) +/* + const rawText = RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]) + + // 3) pick minimal vs. full classification based on "[placeholder]" substring + if (rawText.includes("[placeholder]")) { this.Document.$tags_chat = new List<string>(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXT).then(desc => (this.Document.$tags_chat as List<string>).push(desc)); - this.Document._layout_showTags = true; - //or... then(desc => this.Document.$tags_chat = desc); - }; + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => { + (this.Document.$tags_chat as List<string>).push(desc); + }); + } else { + this.Document.$tags_chat = new List<string>(); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTFULL).then(desc => { + (this.Document.$tags_chat as List<string>).push(desc); + })}; + // 4) make sure the UI will show tags + this.Document._layout_showTags = true;*/ + +}; leafText = (node: Node) => { if (node.type === this.EditorView?.state.schema.nodes.dashField) { 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/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index 391dcb83d..5dd02295c 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -24,20 +24,72 @@ import { ImageCast } from '../../../../fields/Types'; import { SnappingManager } from '../../../util/SnappingManager'; import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; -import { ImageField } from '../../../../fields/URLField'; import { runInAction } from 'mobx'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; import { getPresetNames, createPreset } from './ScrapbookPresetRegistry'; -enum ScrapbookPresetType { - Classic = 'Classic', - Default = 'Default', - Collage = 'Collage', - Spotlight = 'Spotlight', -} +export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Doc[] { + const placeholders: Doc[] = []; + + for (const cfg of configs) { + if (cfg.children && cfg.children.length) { + // --- nested container --- + const childDocs = cfg.children.map(child => { + const doc = Docs.Create.TextDocument("[placeholder] " + child.tag); + doc.accepts_docType = child.type; + + 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; + }); + + // wrap those children in a stacking container + const protoW = cfg.containerWidth ?? cfg.width; + const protoH = cfg.containerHeight ?? cfg.height; + 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 { + // --- flat placeholder --- + const doc = Docs.Create.TextDocument("[placeholder] " + cfg.tag); + doc.accepts_docType = cfg.type; + doc.accepts_tagType = cfg.acceptTag ?? 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); + } + } + return placeholders; +} // Scrapbook view: a container that lays out its child items in a grid/template @observer export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -55,14 +107,22 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() constructor(props: FieldViewProps) { super(props); makeObservable(this); - // whenever the preset changes, rebuild the layout - reaction( + 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.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(); + } //this.configs = //ScrapbookPreset.createPreset(presetType); @@ -70,9 +130,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (!this.dataDoc[this.fieldKey]) { this.dataDoc[this.fieldKey] = new List<Doc>(); } - this.createdDate = this.getFormattedDate(); //this.initScrapbook(ScrapbookPresetType.Default); - this.setTitle(); //this.setLayout(ScrapbookPreset.Spotlight); } @@ -99,175 +157,11 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } // 2) build placeholders from the preset + const placeholders = buildPlaceholdersFromConfigs(configs); - const placeholders: Doc[] = []; - - for (const cfg of configs) { - if (cfg.children) { - // --- nested container --- - const childDocs = cfg.children.map(child => { - const doc = Docs.Create.TextDocument(child.tag); - doc.accepts_docType = child.type; - doc.accepts_tagType = child.acceptTag ?? 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; - 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 { - // --- flat placeholder --- - const doc = Docs.Create.TextDocument(cfg.tag); - doc.accepts_docType = cfg.type; - doc.accepts_tagType = cfg.acceptTag ?? 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); - } - } - // 3) commit them into the field this.dataDoc[this.fieldKey] = new List<Doc>(placeholders); } - @action - //INACTIVE VER ignore!! not in use rn, implementation ver 1 - setLayout(preset: ScrapbookPreset) { - // helper to wrap a TextDocument proto in a Doc with positioning - function makePlaceholder( - proto: Doc, x: number, y: number, - width: number, height: number - ): Doc { - const d = new Doc(); - d.proto = proto; - d.original = proto; - d.x = x; - d.y = y; - d._width = width; - d._height = height; - return d; - } - - let placeholders: Doc[]; - - switch (preset) { - case ScrapbookPresetType.Classic: - // One large landscape image on top, caption below, sidebar at right - const imgClassic = Docs.Create.TextDocument('image'); - imgClassic.accepts_docType = DocumentType.IMG; - imgClassic.accepts_tagType = 'LANDSCAPE'; - const phImageClassic = makePlaceholder(imgClassic, 0, -120, 300, 180); - - const captionClassic = Docs.Create.TextDocument('caption'); - captionClassic.accepts_docType = DocumentType.RTF; - captionClassic.accepts_tagType = 'caption'; - const phCaptionClassic = makePlaceholder(captionClassic, 0, 80, 300, 60); - - const sidebarClassic = Docs.Create.TextDocument('sidebar'); - sidebarClassic.accepts_docType = DocumentType.RTF; - sidebarClassic.accepts_tagType = 'lengthy description'; - const phSidebarClassic = makePlaceholder(sidebarClassic, 320, -50, 80, 200); - - placeholders = [phImageClassic, phCaptionClassic, phSidebarClassic]; - break; - - case ScrapbookPresetType.Collage: - // Grid of four person images, small captions under each - const personDocs: Doc[] = []; - for (let i = 0; i < 4; i++) { - const img = Docs.Create.TextDocument(`person ${i+1}`); - img.accepts_docType = DocumentType.IMG; - img.accepts_tagType = 'PERSON'; - // position in 2x2 grid - const x = (i % 2) * 160 - 80; - const y = Math.floor(i / 2) * 160 - 80; - personDocs.push(makePlaceholder(img, x, y, 150, 120)); - - const cap = Docs.Create.TextDocument(`caption ${i+1}`); - cap.accepts_docType = DocumentType.RTF; - cap.accepts_tagType = 'caption'; - personDocs.push(makePlaceholder(cap, x, y + 70, 150, 30)); - } - placeholders = personDocs; - break; - - case ScrapbookPresetType.Spotlight: - // Full-width title, then a stacking of an internal person image + landscape, then description - const titleSpot = Docs.Create.TextDocument('title'); - titleSpot.accepts_docType = DocumentType.RTF; - titleSpot.accepts_tagType = 'title'; - const phTitleSpot = makePlaceholder(titleSpot, 0, -180, 400, 60); - - const internalImg = Docs.Create.TextDocument('<person>'); - internalImg.accepts_docType = DocumentType.IMG; - internalImg.accepts_tagType = 'PERSON'; - const phInternal = makePlaceholder(internalImg, -100, -120, 120, 160); - - const landscapeImg = Docs.Create.TextDocument('<landscape>'); - landscapeImg.accepts_docType = DocumentType.IMG; - landscapeImg.accepts_tagType = 'LANDSCAPE'; - const phLandscape = makePlaceholder(landscapeImg, 50, 0, 200, 160); - - const stack = Docs.Create.StackingDocument( - [phInternal, phLandscape], - { _width: 360, _height: 180, title: 'spotlight stack' } - ); - const phStack = (() => { - const d = new Doc(); - d.proto = stack; - d.original = stack; - d.x = 8; - d.y = -84; - d._width = 360; - d._height = 180; - return d; - })(); - - const descSpot = Docs.Create.TextDocument('description'); - descSpot.accepts_docType = DocumentType.RTF; - descSpot.accepts_tagType = 'lengthy description'; - const phDescSpot = makePlaceholder(descSpot, 0, 140, 400, 100); - - placeholders = [phTitleSpot, phStack, phDescSpot]; - break; - - default: - placeholders = []; - } - - // finally assign into the dataDoc - this.dataDoc[this.fieldKey] = new List<Doc>(placeholders); - } @@ -276,8 +170,8 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() const title = `Scrapbook - ${this.createdDate}`; if (this.dataDoc.title !== title) { this.dataDoc.title = title; - - const image = Docs.Create.TextDocument('person image'); + if (!this.dataDoc[this.fieldKey]){ + const image = Docs.Create.TextDocument('[placeholder] person image'); image.accepts_docType = DocumentType.IMG; image.accepts_tagType = 'PERSON' //should i be writing fields on this doc? clarify diff between this and proto, original const placeholder = new Doc(); @@ -289,7 +183,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() 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('long summary'); + const summary = Docs.Create.TextDocument('[placeholder] long summary'); summary.accepts_docType = DocumentType.RTF; summary.accepts_tagType = 'lengthy description'; //summary.$tags_chat = new List<string>(['lengthy description']); //we need to go back and set this @@ -301,7 +195,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() 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 - const sidebar = Docs.Create.TextDocument('brief sidebar'); + const sidebar = Docs.Create.TextDocument('[placeholder] brief sidebar'); sidebar.accepts_docType = DocumentType.RTF; sidebar.accepts_tagType = 'title'; //accepts_textType = 'lengthy description' const placeholder3 = new Doc(); @@ -314,7 +208,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() - const internalImg = Docs.Create.TextDocument('landscape internal'); + const internalImg = Docs.Create.TextDocument('[placeholder] landscape internal'); internalImg.accepts_docType = DocumentType.IMG; internalImg.accepts_tagType = 'LANDSCAPE' //should i be writing fields on this doc? clarify diff between this and proto, original const placeholder5 = new Doc(); @@ -350,7 +244,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() difference between passing a new List<Doc> versus just the raw array? */ this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]); - + } //this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey] ?? new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]); diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx index fc69552c0..87821c7bf 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx @@ -47,22 +47,22 @@ export class ScrapbookPreset { private static createClassicPreset(): ScrapbookItemConfig[] { return [ { type: DocumentType.IMG, - tag: 'LANDSCAPE', + tag: '[placeholder] LANDSCAPE', acceptTag: 'LANDSCAPE', x: 0, y: -100, width: 250, height: 200 }, { type: DocumentType.RTF, - tag: 'caption', + tag: '[placeholder] caption', acceptTag: 'caption', x: 0, y: 200, width: 250, height: 50 }, { type: DocumentType.RTF, - tag: 'lengthy description', + tag: '[placeholder] lengthy description', acceptTag: 'lengthy description', x: 280, y: -50, width: 50, height: 200 }, { type: DocumentType.IMG, - tag: 'PERSON', + tag: '[placeholder] PERSON', acceptTag: 'PERSON', x: -200, y: -100, width: 100, height: 200 }, diff --git a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts index f7ddd70ab..d6fd3620c 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts +++ b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts @@ -6,6 +6,9 @@ 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. */ |
