aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/ImageBox.tsx6
-rw-r--r--src/client/views/nodes/VideoBox.tsx3
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx165
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPicker.scss40
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPicker.tsx84
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPreset.tsx44
-rw-r--r--src/client/views/nodes/scrapbook/scrapbookleftover.ts46
7 files changed, 288 insertions, 100 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9067f7e0c..2473f1c0a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -141,6 +141,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
that describes the image.`
);
+ const { nativeWidth, nativeHeight } = this.nativeSize;
+ const aspectRatio = nativeWidth && nativeHeight
+ ? (nativeWidth / nativeHeight).toFixed(2)
+ : '1.00';
+
// 4) normalize and prefix
const label = raw
.trim()
@@ -152,6 +157,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.Document.$tags_chat = new List<string>();
tokens.forEach(tok => {
(this.Document.$tags_chat as List<string>).push(tok)});
+ (this.Document.$tags_chat as List<string>).push(`ASPECT_${aspectRatio}`);
//!!! changed may 11 (this.Document.$tags_chat as List<string>).push(label);
// 6) flip on “show tags” in the layout
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0e7afbab1..404be6a1b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -151,7 +151,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const tokens = label.split(/\s+/);
this.Document.$tags_chat = new List<string>();
tokens.forEach(tok => (this.Document.$tags_chat as List<string>).push(tok));
-
+ const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1);
+ (this.Document.$tags_chat as List<string>).push(`ASPECT_${aspect}`);
// 5) Turn on tag display in layout
this.Document._layout_showTags = true;
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index 5dd02295c..eb997024b 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -3,7 +3,6 @@ import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { emptyFunction } from '../../../../Utils';
-import axios from 'axios';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CollectionView } from '../../collections/CollectionView';
@@ -35,54 +34,51 @@ export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Do
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;
+ doc.accepts_tagType = new List<string>(child.acceptTags ?? [child.tag]);
const ph = new Doc();
- ph.proto = doc;
+ ph.proto = doc;
ph.original = doc;
- ph.x = child.x;
- ph.y = child.y;
- if (child.width != null) ph._width = child.width;
+ 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
- }
- );
+ // 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.proto = containerProto;
ph.original = containerProto;
- ph.x = cfg.x;
- ph.y = cfg.y;
- if (cfg.width != null) ph._width = cfg.width;
+ 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 ---
+ }
+
+ else {
const doc = Docs.Create.TextDocument("[placeholder] " + cfg.tag);
doc.accepts_docType = cfg.type;
- doc.accepts_tagType = cfg.acceptTag ?? cfg.tag;
+ doc.accepts_tagType = new List<string>(cfg.acceptTags ?? [cfg.tag]);
const ph = new Doc();
- ph.proto = doc;
+ ph.proto = doc;
ph.original = doc;
- ph.x = cfg.x;
- ph.y = cfg.y;
- if (cfg.width != null) ph._width = cfg.width;
+ 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);
}
@@ -90,7 +86,54 @@ export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Do
return placeholders;
}
-// Scrapbook view: a container that lays out its child items in a grid/template
+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 => {
+ // 1) Enforce that placeholder.accepts_docType === realDoc.$type
+ 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));
+
+ if (matches.length > maxMatches) {
+ maxMatches = matches.length;
+ bestMatch = ph;
+ }
+
+ });
+
+ if (bestMatch && maxMatches > 0) {
+ setTimeout(
+ undoable(() => {
+ bestMatch!.proto = realDoc;
+ }, 'Scrapbook add'),
+ 0
+ );
+ return true;
+ }
+
+ 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];
@@ -406,56 +449,19 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// 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;
+ }
- if (docs?.length === 1) {
- const placeholder = allDocs.filter(d =>
+ return false;
+};
+
- (d.accepts_docType === docs[0].$type || // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
- RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type)))
- ); // prettier-ignore
-
- //DocListCast(this.Document.items).map(doc => DocListCast(doc[Doc.LayoutDataKey(doc)])
-
- if (placeholder) {
- /**Look at the autotags and see what matches*RTFCast(d[Doc.LayoutDataKey(d)])?.Text*/
- // ugh. we have to tell the underlying view not to add the Doc so that we can add it where we want it.
- // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo.
- setTimeout(
- undoable(() => {
-
- const slotTagsList: Set<string>[] = placeholder.map(doc =>
- new Set<string>(StrListCast(doc.$tags_chat))
- );
- // turn docs[0].$tags_chat into a Set
- const targetTags = new Set(StrListCast(docs[0].$tags_chat));
-
- //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field])); // // shouldn't need to do this for layout fields since the placeholder already overrides its protos
-
- // find the first placeholder that shares *any* tag
- const match = placeholder.find(ph =>
- ph.accepts_tagType != null && // make sure it actually has one
- targetTags.has(StrCast(ph.accepts_tagType)) // test membership in the Set
- //StrListCast(ph.$tags_chat).some(tag => targetTags.has(tag))
- );
- if (match) {
- match.proto = docs[0];
- }
-
- /*const chosenPlaceholder = placeholder.find(d =>
- pl = new Set<string>(StrListCast(d.$tags_chat)
-
- d.$tags_chat && d.$tags_chat[0].equals(docs[0].$tags_chat)); //why [0]
- if (chosenPlaceholder){
- chosenPlaceholder.proto = docs[0];}*/
- //excess if statement??
- }, 'Scrapbook add')
- );
- return false;
- }
- }
- return false;
- };
@@ -474,10 +480,15 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
allDocs.forEach(doc => {
const tags = StrListCast(doc.$tags_chat ?? new List<string>());
- tags.forEach(tag => internalTagsSet.add(tag));
+ tags.forEach(tag =>
+ {if (!tag.startsWith("ASPECT_")) {
+ internalTagsSet.add(tag);
+ }
+ });
});
const internalTags = Array.from(internalTagsSet).join(', ');
+
return internalTags
? `Create a new scrapbook background featuring: ${internalTags}`
diff --git a/src/client/views/nodes/scrapbook/ScrapbookPicker.scss b/src/client/views/nodes/scrapbook/ScrapbookPicker.scss
new file mode 100644
index 000000000..237274433
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookPicker.scss
@@ -0,0 +1,40 @@
+/* ScrapbookPicker.scss */
+
+.scrapbook-picker-popup {
+ background: rgba(255, 0, 0, 0.5); /* semi-transparent red */
+ position: absolute; /* ← make it float */
+ z-index: 10000; /* so it sits above the overlay‐window’s background */
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ padding: 8px;
+ min-width: 200px; /* at least give it some size */
+}
+
+.scrapbook-picker-close {
+ position: absolute;
+ top: 4px;
+ right: 8px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.scrapbook-picker-thumbnails {
+ margin-top: 24px; /* room under the close button */
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.scrapbook-picker-thumb {
+ cursor: pointer;
+ border: 1px solid #ddd;
+ border-radius: 2px;
+ padding: 4px;
+ background: #f9f9f9;
+}
+
+.scrapbook-picker-thumb-inner {
+ font-size: 12px;
+ text-align: center;
+}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookPicker.tsx b/src/client/views/nodes/scrapbook/ScrapbookPicker.tsx
new file mode 100644
index 000000000..6054cb98d
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookPicker.tsx
@@ -0,0 +1,84 @@
+// src/client/views/nodes/scrapbook/ScrapbookPicker.tsx
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { StrCast } from '../../../../fields/Types';
+import './ScrapbookPicker.scss';
+
+export interface ScrapbookPickerProps {
+ choices: Doc[];
+ x: number;
+ y: number;
+ onSelect: (index: number) => void;
+ onCancel: () => void;
+}
+
+/**
+ * A floating popup that shows N “temporary” Scrapbook documents.
+ * When the user clicks one thumbnail, we call onSelect(i).
+ * When the user clicks × or outside, we call onCancel().
+ *
+ * This component itself does not control its own visibility; MarqueeView / OverlayView will mount/unmount it.
+ */
+@observer
+export class ScrapbookPicker extends React.Component<ScrapbookPickerProps> {
+ containerRef = React.createRef<HTMLDivElement>();
+
+ // Close when user clicks outside the popup
+ handleClickOutside = (e: MouseEvent) => {
+ if (
+ this.containerRef.current &&
+ !this.containerRef.current.contains(e.target as Node)
+ ) {
+ this.props.onCancel();
+ }
+ };
+
+ componentDidMount() {
+ document.addEventListener('mousedown', this.handleClickOutside);
+ }
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handleClickOutside);
+ }
+
+ render() {
+ const { choices, x, y, onSelect, onCancel } = this.props;
+ return (
+ <div
+ className="scrapbook-picker-popup"
+ ref={this.containerRef}
+ style={{
+ top: `${y}px`,
+ left: `${x}px`,
+ }}
+ >
+ {/* close icon */}
+ <div className="scrapbook-picker-close" onClick={onCancel}>
+ ×
+ </div>
+ <div className="scrapbook-picker-thumbnails">
+ {choices.map((doc, i) => {
+ // We simply show a small thumbnail representation of each temp scrapbook
+ // You could replace this with DocumentThumbnail or a custom mini‐preview.
+ return (
+ <div
+ key={`${doc[Id]}_${i}`}
+ className="scrapbook-picker-thumb"
+ onClick={() => onSelect(i)}
+ >
+ {/*
+ For a minimal example, use the document’s title or ID as a placeholder.
+ In a real version, you might render a proper thumbnail/view of doc.
+ */}
+ <div className="scrapbook-picker-thumb-inner">
+ {StrCast(doc.title)}
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx
index 87821c7bf..96a8e9b5f 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx
@@ -13,7 +13,7 @@ export interface ScrapbookItemConfig {
/** text shown in the placeholder bubble */
tag: string;
/** what this slot actually accepts (defaults to `tag`) */
- acceptTag?: string;
+ acceptTags?: string[];
x: number;
y: number;
@@ -48,22 +48,22 @@ export class ScrapbookPreset {
return [
{ type: DocumentType.IMG,
tag: '[placeholder] LANDSCAPE',
- acceptTag: 'LANDSCAPE',
+ acceptTags: ['LANDSCAPE'],
x: 0, y: -100, width: 250, height: 200
},
{ type: DocumentType.RTF,
tag: '[placeholder] caption',
- acceptTag: 'caption',
+ acceptTags: ['caption'],
x: 0, y: 200, width: 250, height: 50
},
{ type: DocumentType.RTF,
tag: '[placeholder] lengthy description',
- acceptTag: 'lengthy description',
+ acceptTags: ['lengthy description'],
x: 280, y: -50, width: 50, height: 200
},
{ type: DocumentType.IMG,
tag: '[placeholder] PERSON',
- acceptTag: 'PERSON',
+ acceptTags: ['PERSON'],
x: -200, y: -100, width: 100, height: 200
},
];
@@ -71,12 +71,12 @@ export class ScrapbookPreset {
private static createGalleryPreset(): ScrapbookItemConfig[] {
return [
- { type: DocumentType.IMG, tag: 'Gallery 1', acceptTag: 'LANDSCAPE', x: -150, y: -150, width: 150, height: 150 },
- { type: DocumentType.IMG, tag: 'Gallery 2', acceptTag: 'LANDSCAPE', x: 0, y: -150, width: 150, height: 150 },
- { type: DocumentType.IMG, tag: 'Gallery 3', acceptTag: 'LANDSCAPE', x: 150, y: -150, width: 150, height: 150 },
- { type: DocumentType.IMG, tag: 'Gallery 4', acceptTag: 'LANDSCAPE', x: -150, y: 0, width: 150, height: 150 },
- { type: DocumentType.IMG, tag: 'Gallery 5', acceptTag: 'LANDSCAPE', x: 0, y: 0, width: 150, height: 150 },
- { type: DocumentType.IMG, tag: 'Gallery 6', acceptTag: 'LANDSCAPE', x: 150, y: 0, width: 150, height: 150 },
+ { 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 },
];
}
@@ -85,17 +85,17 @@ export class ScrapbookPreset {
return [
{ type: DocumentType.IMG,
tag: 'image',
- acceptTag: 'LANDSCAPE',
+ acceptTags: ['LANDSCAPE'],
x: 0, y: -100, width: 250, height: 200
},
{ type: DocumentType.RTF,
tag: 'summary',
- acceptTag: 'caption',
+ acceptTags: ['caption'],
x: 0, y: 200, width: 250
},
{ type: DocumentType.RTF,
tag: 'sidebar',
- acceptTag: 'lengthy description',
+ acceptTags: ['lengthy description'],
x: 280, y: -50, width: 50, height: 200
},
{
@@ -106,7 +106,7 @@ export class ScrapbookPreset {
children: [
{ type: DocumentType.IMG,
tag: 'image internal',
- acceptTag: 'PERSON',
+ acceptTags: ['PERSON'],
x: 0, y: 0, width: 50, height: 100
}
]
@@ -118,22 +118,22 @@ export class ScrapbookPreset {
return [
{ type: DocumentType.IMG,
tag: 'LANDSCAPE',
- acceptTag: 'LANDSCAPE',
+ acceptTags: ['LANDSCAPE'],
x: -150, y: -150, width: 150, height: 150
},
{ type: DocumentType.IMG,
tag: 'PERSON',
- acceptTag: 'PERSON',
+ acceptTags: ['PERSON'],
x: 0, y: -150, width: 150, height: 150
},
{ type: DocumentType.RTF,
tag: 'caption',
- acceptTag: 'caption',
+ acceptTags: ['caption'],
x: -150, y: 0, width: 300, height: 100
},
{ type: DocumentType.RTF,
tag: 'lengthy description',
- acceptTag: 'lengthy description',
+ acceptTags: ['lengthy description'],
x: 0, y: 100, width: 300, height: 100
}
];
@@ -143,17 +143,17 @@ export class ScrapbookPreset {
return [
{ type: DocumentType.RTF,
tag: 'title',
- acceptTag: 'title',
+ acceptTags: ['title'],
x: 0, y: -180, width: 300, height: 40
},
{ type: DocumentType.IMG,
tag: 'LANDSCAPE',
- acceptTag: 'LANDSCAPE',
+ acceptTags: ['LANDSCAPE'],
x: 0, y: 0, width: 300, height: 200
},
{ type: DocumentType.RTF,
tag: 'caption',
- acceptTag: 'caption',
+ acceptTags: ['caption'],
x: 0, y: 230, width: 300, height: 50
}
];
diff --git a/src/client/views/nodes/scrapbook/scrapbookleftover.ts b/src/client/views/nodes/scrapbook/scrapbookleftover.ts
new file mode 100644
index 000000000..2f381ab95
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/scrapbookleftover.ts
@@ -0,0 +1,46 @@
+
+
+
+
+ //DocListCast(this.Document.items).map(doc => DocListCast(doc[Doc.LayoutDataKey(doc)])
+
+ if (placeholder) {
+ /**Look at the autotags and see what matches*RTFCast(d[Doc.LayoutDataKey(d)])?.Text*/
+ // ugh. we have to tell the underlying view not to add the Doc so that we can add it where we want it.
+ // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo.
+ setTimeout(
+ undoable(() => {
+
+ const slotTagsList: Set<string>[] = placeholder.map(doc =>
+ new Set<string>(StrListCast(doc.$tags_chat))
+ );
+ // turn docs[0].$tags_chat into a Set
+ const targetTags = new Set(StrListCast(docs[0].$tags_chat));
+
+ //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field]));
+ // // // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+
+ // find the first placeholder that shares *any* tag
+ const match = placeholder.find(ph =>
+ ph.accepts_tagType != null && // make sure it actually has one
+ targetTags.has(StrCast(ph.accepts_tagType)) // test membership in the Set
+ //StrListCast(ph.$tags_chat).some(tag => targetTags.has(tag))
+ );
+ if (match) {
+ match.proto = docs[0];
+ }
+
+ /*const chosenPlaceholder = placeholder.find(d =>
+ pl = new Set<string>(StrListCast(d.$tags_chat)
+
+ d.$tags_chat && d.$tags_chat[0].equals(docs[0].$tags_chat)); //why [0]
+ if (chosenPlaceholder){
+ chosenPlaceholder.proto = docs[0];}*/
+ //excess if statement??
+ }, 'Scrapbook add')
+ );
+ return false;
+ }
+ }
+ return false;
+ }; \ No newline at end of file