Merge branch 'dev'
CI/CD / deploy (push) Has been cancelled
Details
CI/CD / deploy (push) Has been cancelled
Details
This commit is contained in:
commit
344729d5c1
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* extractArtifactToCanvas — Pull a generated artifact out of a generator shape
|
||||||
|
* and place it as a standalone canvas object (folk-image, folk-embed, or folk-bookmark).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { FolkShape } from "./folk-shape";
|
||||||
|
|
||||||
|
export type ArtifactMediaType = "image" | "video" | "pdf" | "download";
|
||||||
|
|
||||||
|
interface ExtractOptions {
|
||||||
|
url: string;
|
||||||
|
mediaType: ArtifactMediaType;
|
||||||
|
title?: string;
|
||||||
|
sourceShape: FolkShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CanvasApi {
|
||||||
|
newShape: (tagName: string, props?: Record<string, any>, atPosition?: { x: number; y: number }) => any;
|
||||||
|
findFreePosition: (w: number, h: number, px?: number, py?: number, exclude?: any) => { x: number; y: number };
|
||||||
|
SHAPE_DEFAULTS: Record<string, { width: number; height: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_MAP: Record<ArtifactMediaType, string> = {
|
||||||
|
image: "folk-image",
|
||||||
|
video: "folk-embed",
|
||||||
|
pdf: "folk-embed",
|
||||||
|
download: "folk-bookmark",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function extractArtifactToCanvas({ url, mediaType, title, sourceShape }: ExtractOptions): boolean {
|
||||||
|
const api = (window as any).__canvasApi as CanvasApi | undefined;
|
||||||
|
if (!api) {
|
||||||
|
console.warn("[extract-artifact] Canvas API not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagName = TAG_MAP[mediaType];
|
||||||
|
const defaults = api.SHAPE_DEFAULTS[tagName] || { width: 400, height: 300 };
|
||||||
|
|
||||||
|
// Position to the right of the source shape
|
||||||
|
const preferX = sourceShape.x + sourceShape.width + 40 + defaults.width / 2;
|
||||||
|
const preferY = sourceShape.y + sourceShape.height / 2;
|
||||||
|
const pos = api.findFreePosition(defaults.width, defaults.height, preferX, preferY, sourceShape);
|
||||||
|
|
||||||
|
const props: Record<string, any> = {};
|
||||||
|
if (mediaType === "image") {
|
||||||
|
props.src = url;
|
||||||
|
if (title) props.alt = title;
|
||||||
|
} else if (mediaType === "video" || mediaType === "pdf") {
|
||||||
|
props.url = url;
|
||||||
|
} else {
|
||||||
|
props.url = url;
|
||||||
|
if (title) props.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.newShape(tagName, props, { x: pos.x + defaults.width / 2, y: pos.y + defaults.height / 2 });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CSS for the extract button — inject into each component's stylesheet */
|
||||||
|
export const extractBtnCss = `
|
||||||
|
.extract-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
z-index: 1;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.image-item:hover .extract-btn,
|
||||||
|
.video-item:hover .extract-btn,
|
||||||
|
.section:hover .extract-btn,
|
||||||
|
.render-preview:hover .extract-btn,
|
||||||
|
.preview-area:hover > .extract-btn { opacity: 1; }
|
||||||
|
.extract-btn:hover { background: rgba(0, 0, 0, 0.8); }
|
||||||
|
`;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -202,6 +203,8 @@ const styles = css`
|
||||||
.render-preview:hover .zoom-reset { opacity: 1; }
|
.render-preview:hover .zoom-reset { opacity: 1; }
|
||||||
.render-preview .zoom-reset:hover { background: var(--rs-surface-hover, #f1f5f9); }
|
.render-preview .zoom-reset:hover { background: var(--rs-surface-hover, #f1f5f9); }
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.code-area {
|
.code-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
@ -638,7 +641,7 @@ export class FolkBlender extends FolkShape {
|
||||||
this.#viewCleanup = null;
|
this.#viewCleanup = null;
|
||||||
|
|
||||||
if (this.#renderUrl) {
|
if (this.#renderUrl) {
|
||||||
this.#previewArea.innerHTML = `<img src="${this.#escapeHtml(this.#renderUrl)}" alt="3D Render" /><button class="zoom-reset" title="Reset zoom">Reset</button><span class="zoom-hint">Scroll to zoom · drag to pan</span>`;
|
this.#previewArea.innerHTML = `<button class="extract-btn" data-url="${this.#escapeHtml(this.#renderUrl)}" data-title="${this.#escapeHtml(this.#prompt || "3D Render")}" title="Extract to canvas">↗</button><img src="${this.#escapeHtml(this.#renderUrl)}" alt="3D Render" /><button class="zoom-reset" title="Reset zoom">Reset</button><span class="zoom-hint">Scroll to zoom · drag to pan</span>`;
|
||||||
const img = this.#previewArea.querySelector("img") as HTMLImageElement;
|
const img = this.#previewArea.querySelector("img") as HTMLImageElement;
|
||||||
const resetBtn = this.#previewArea.querySelector(".zoom-reset") as HTMLButtonElement;
|
const resetBtn = this.#previewArea.querySelector(".zoom-reset") as HTMLButtonElement;
|
||||||
|
|
||||||
|
|
@ -668,6 +671,13 @@ export class FolkBlender extends FolkShape {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
fitImage();
|
fitImage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extract button
|
||||||
|
const extractBtn = this.#previewArea.querySelector(".extract-btn") as HTMLElement;
|
||||||
|
extractBtn?.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
extractArtifactToCanvas({ url: extractBtn.dataset.url!, mediaType: "image", title: extractBtn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">✅</span><span>Script generated (see Script tab)</span></div>';
|
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">✅</span><span>Script generated (see Script tab)</span></div>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -160,6 +161,8 @@ const styles = css`
|
||||||
background: #0e7490;
|
background: #0e7490;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -311,6 +314,14 @@ export class FolkFreeCAD extends FolkShape {
|
||||||
this.#renderResult();
|
this.#renderResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract button — delegated click
|
||||||
|
this.#previewArea?.addEventListener("click", (e) => {
|
||||||
|
const btn = (e.target as HTMLElement).closest(".extract-btn") as HTMLElement;
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
extractArtifactToCanvas({ url: btn.dataset.url!, mediaType: "image", title: btn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
|
|
||||||
// Health check — only disable if endpoint unreachable (sidecar starts on demand)
|
// Health check — only disable if endpoint unreachable (sidecar starts on demand)
|
||||||
fetch("/api/freecad/health").then(r => r.json()).then((h: any) => {
|
fetch("/api/freecad/health").then(r => r.json()).then((h: any) => {
|
||||||
if (this.#generateBtn && h.available === false && h.status && !h.status.includes("on demand")) {
|
if (this.#generateBtn && h.available === false && h.status && !h.status.includes("on demand")) {
|
||||||
|
|
@ -366,7 +377,7 @@ export class FolkFreeCAD extends FolkShape {
|
||||||
#renderResult() {
|
#renderResult() {
|
||||||
if (this.#previewArea) {
|
if (this.#previewArea) {
|
||||||
if (this.#previewUrl) {
|
if (this.#previewUrl) {
|
||||||
this.#previewArea.innerHTML = `<img src="${this.#escapeHtml(this.#previewUrl)}" alt="CAD Preview" />`;
|
this.#previewArea.innerHTML = `<button class="extract-btn" data-url="${this.#escapeHtml(this.#previewUrl)}" data-title="${this.#escapeHtml(this.#prompt || "CAD Preview")}" title="Extract to canvas">↗</button><img src="${this.#escapeHtml(this.#previewUrl)}" alt="CAD Preview" />`;
|
||||||
} else {
|
} else {
|
||||||
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">✅</span><span>Model generated! Download files below.</span></div>';
|
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">✅</span><span>Model generated! Download files below.</span></div>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -141,6 +142,8 @@ const styles = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.image-prompt {
|
.image-prompt {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
|
|
@ -316,6 +319,14 @@ export class FolkImageGen extends FolkShape {
|
||||||
|
|
||||||
// Prevent canvas drag when interacting with content
|
// Prevent canvas drag when interacting with content
|
||||||
this.#imageArea?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
this.#imageArea?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||||
|
|
||||||
|
// Extract button — delegated click
|
||||||
|
this.#imageArea?.addEventListener("click", (e) => {
|
||||||
|
const btn = (e.target as HTMLElement).closest(".extract-btn") as HTMLElement;
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
extractArtifactToCanvas({ url: btn.dataset.url!, mediaType: "image", title: btn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
this.#promptInput?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
this.#promptInput?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||||
|
|
||||||
// Close button
|
// Close button
|
||||||
|
|
@ -422,6 +433,7 @@ export class FolkImageGen extends FolkShape {
|
||||||
.map(
|
.map(
|
||||||
(img) => `
|
(img) => `
|
||||||
<div class="image-item">
|
<div class="image-item">
|
||||||
|
<button class="extract-btn" data-url="${this.#escapeHtml(img.url)}" data-title="${this.#escapeHtml(img.prompt)}" title="Extract to canvas">↗</button>
|
||||||
<img class="generated-image" src="${this.#escapeHtml(img.url)}" alt="${this.#escapeHtml(img.prompt)}" loading="lazy" />
|
<img class="generated-image" src="${this.#escapeHtml(img.url)}" alt="${this.#escapeHtml(img.prompt)}" loading="lazy" />
|
||||||
<div class="image-prompt">${this.#escapeHtml(img.prompt)}</div>
|
<div class="image-prompt">${this.#escapeHtml(img.prompt)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -218,6 +219,8 @@ const styles = css`
|
||||||
background: #047857;
|
background: #047857;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -397,6 +400,15 @@ export class FolkKiCAD extends FolkShape {
|
||||||
this.#showExports();
|
this.#showExports();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract button — delegated click
|
||||||
|
this.#previewArea?.addEventListener("click", (e) => {
|
||||||
|
const btn = (e.target as HTMLElement).closest(".extract-btn") as HTMLElement;
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
const mediaType = (btn.dataset.media || "image") as "image" | "pdf";
|
||||||
|
extractArtifactToCanvas({ url: btn.dataset.url!, mediaType, title: btn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
|
|
||||||
// Health check — only disable if endpoint unreachable (sidecar starts on demand)
|
// Health check — only disable if endpoint unreachable (sidecar starts on demand)
|
||||||
fetch("/api/kicad/health").then(r => r.json()).then((h: any) => {
|
fetch("/api/kicad/health").then(r => r.json()).then((h: any) => {
|
||||||
if (this.#generateBtn && h.available === false && h.status && !h.status.includes("on demand")) {
|
if (this.#generateBtn && h.available === false && h.status && !h.status.includes("on demand")) {
|
||||||
|
|
@ -465,14 +477,14 @@ export class FolkKiCAD extends FolkShape {
|
||||||
switch (this.#activeTab) {
|
switch (this.#activeTab) {
|
||||||
case "schematic":
|
case "schematic":
|
||||||
if (this.#schematicSvg) {
|
if (this.#schematicSvg) {
|
||||||
this.#previewArea.innerHTML = `<img src="${this.#escapeHtml(this.#schematicSvg)}" alt="Schematic" />`;
|
this.#previewArea.innerHTML = `<button class="extract-btn" data-url="${this.#escapeHtml(this.#schematicSvg)}" data-title="Schematic" data-media="image" title="Extract to canvas">↗</button><img src="${this.#escapeHtml(this.#schematicSvg)}" alt="Schematic" />`;
|
||||||
} else {
|
} else {
|
||||||
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">📋</span><span>Schematic will appear here</span></div>';
|
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">📋</span><span>Schematic will appear here</span></div>';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "board":
|
case "board":
|
||||||
if (this.#boardSvg) {
|
if (this.#boardSvg) {
|
||||||
this.#previewArea.innerHTML = `<img src="${this.#escapeHtml(this.#boardSvg)}" alt="Board Layout" />`;
|
this.#previewArea.innerHTML = `<button class="extract-btn" data-url="${this.#escapeHtml(this.#boardSvg)}" data-title="Board Layout" data-media="image" title="Extract to canvas">↗</button><img src="${this.#escapeHtml(this.#boardSvg)}" alt="Board Layout" />`;
|
||||||
} else {
|
} else {
|
||||||
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">📟</span><span>Board layout will appear here</span></div>';
|
this.#previewArea.innerHTML = '<div class="placeholder"><span class="placeholder-icon">📟</span><span>Board layout will appear here</span></div>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -234,6 +235,8 @@ const styles = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.video-prompt {
|
.video-prompt {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
|
|
@ -469,6 +472,14 @@ export class FolkVideoGen extends FolkShape {
|
||||||
// Prevent drag on inputs
|
// Prevent drag on inputs
|
||||||
this.#promptInput?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
this.#promptInput?.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||||
|
|
||||||
|
// Extract button — delegated click
|
||||||
|
this.#videoArea?.addEventListener("click", (e) => {
|
||||||
|
const btn = (e.target as HTMLElement).closest(".extract-btn") as HTMLElement;
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
extractArtifactToCanvas({ url: btn.dataset.url!, mediaType: "video", title: btn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
|
|
||||||
// Model selector
|
// Model selector
|
||||||
this.#modelSelect?.addEventListener("change", (e) => {
|
this.#modelSelect?.addEventListener("change", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -690,6 +701,7 @@ export class FolkVideoGen extends FolkShape {
|
||||||
.map(
|
.map(
|
||||||
(vid) => `
|
(vid) => `
|
||||||
<div class="video-item">
|
<div class="video-item">
|
||||||
|
<button class="extract-btn" data-url="${this.#escapeHtml(vid.url)}" data-title="${this.#escapeHtml(vid.prompt)}" title="Extract to canvas">↗</button>
|
||||||
<video class="generated-video" src="${this.#escapeHtml(vid.url)}" controls autoplay loop muted playsinline></video>
|
<video class="generated-video" src="${this.#escapeHtml(vid.url)}" controls autoplay loop muted playsinline></video>
|
||||||
<div class="video-prompt">${this.#escapeHtml(vid.prompt)}</div>
|
<div class="video-prompt">${this.#escapeHtml(vid.prompt)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FolkShape } from "./folk-shape";
|
import { FolkShape } from "./folk-shape";
|
||||||
import { css, html } from "./tags";
|
import { css, html } from "./tags";
|
||||||
|
import { extractArtifactToCanvas, extractBtnCss } from "./extract-artifact";
|
||||||
|
|
||||||
const styles = css`
|
const styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|
@ -193,6 +194,8 @@ const styles = css`
|
||||||
border-color: #f59e0b;
|
border-color: #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${extractBtnCss}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -694,8 +697,9 @@ export class FolkZineGen extends FolkShape {
|
||||||
<textarea class="section-text ${textClass}" data-section-id="${section.id}">${this.#escapeHtml(section.content || "")}</textarea>
|
<textarea class="section-text ${textClass}" data-section-id="${section.id}">${this.#escapeHtml(section.content || "")}</textarea>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else if (section.type === "image") {
|
} else if (section.type === "image") {
|
||||||
sectionsHtml += `<div class="section-body">`;
|
sectionsHtml += `<div class="section-body" style="position:relative">`;
|
||||||
if (section.imageUrl) {
|
if (section.imageUrl) {
|
||||||
|
sectionsHtml += `<button class="extract-btn" data-url="${this.#escapeHtml(section.imageUrl)}" data-title="${this.#escapeHtml(section.imagePrompt || "")}" title="Extract to canvas">↗</button>`;
|
||||||
sectionsHtml += `<img class="section-image" src="${this.#escapeHtml(section.imageUrl)}" alt="${this.#escapeHtml(section.imagePrompt || "")}" loading="lazy" />`;
|
sectionsHtml += `<img class="section-image" src="${this.#escapeHtml(section.imageUrl)}" alt="${this.#escapeHtml(section.imagePrompt || "")}" loading="lazy" />`;
|
||||||
} else {
|
} else {
|
||||||
sectionsHtml += `<div class="section-image-placeholder">${isRegenerating ? '<div class="spinner" style="width:24px;height:24px;"></div>' : "No image yet — click ↻ to generate"}</div>`;
|
sectionsHtml += `<div class="section-image-placeholder">${isRegenerating ? '<div class="spinner" style="width:24px;height:24px;"></div>' : "No image yet — click ↻ to generate"}</div>`;
|
||||||
|
|
@ -821,6 +825,14 @@ export class FolkZineGen extends FolkShape {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.#downloadZine();
|
this.#downloadZine();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extract button — delegated click on image sections
|
||||||
|
this.#contentEl.querySelector(".page-content")?.addEventListener("click", (e) => {
|
||||||
|
const btn = (e.target as HTMLElement).closest(".extract-btn") as HTMLElement;
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
extractArtifactToCanvas({ url: btn.dataset.url!, mediaType: "image", title: btn.dataset.title, sourceShape: this });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #regenerateSection(sectionId: string, feedback: string) {
|
async #regenerateSection(sectionId: string, feedback: string) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue