aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx70
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx44
-rw-r--r--src/client/views/nodes/VideoBox.tsx51
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx52
-rw-r--r--src/client/views/nodes/scrapbook/AIPresetGenerator.ts31
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx264
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPreset.tsx8
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts3
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.
*/