fix: resolve text preview error, add default panels
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Panel } from "$lib/types/panel";
|
||||
import type { Panel, TextItem } from "$lib/types/panel";
|
||||
import { Image, Layer, Stage, Text } from "svelte-konva";
|
||||
import { setCurrentStep } from "../../stores/uiStore";
|
||||
import Button from "../ui/Button.svelte";
|
||||
@@ -18,16 +18,28 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
console.log(
|
||||
"[PanelPreview] Effect triggered. Panel background:",
|
||||
panel?.backgroundImage,
|
||||
"Current image src:",
|
||||
backgroundImage?.src,
|
||||
);
|
||||
if (panel?.backgroundImage && panel.backgroundImage !== backgroundImage?.src) {
|
||||
console.log("[PanelPreview] Loading new background image:", panel.backgroundImage);
|
||||
loadImage(panel.backgroundImage);
|
||||
}
|
||||
});
|
||||
|
||||
function loadImage(src: string) {
|
||||
console.log("[PanelPreview] Starting to load image:", src);
|
||||
const img = document.createElement("img");
|
||||
img.onload = () => {
|
||||
console.log("[PanelPreview] Image loaded successfully:", src);
|
||||
backgroundImage = img;
|
||||
};
|
||||
img.onerror = (error) => {
|
||||
console.error("[PanelPreview] Failed to load image:", src, error);
|
||||
};
|
||||
img.src = src;
|
||||
}
|
||||
|
||||
@@ -39,25 +51,34 @@
|
||||
|
||||
function getTextPosition(textItem: TextItem) {
|
||||
const panelWidth = 320;
|
||||
const paddingX = textItem.paddingX || 0;
|
||||
const paddingX = textItem.paddingX || 20; // Default padding
|
||||
const verticalOffset = textItem.verticalOffset || 0;
|
||||
const centerY = panel.height / 2 + verticalOffset;
|
||||
const textWidth = 300; // Width of the text area
|
||||
|
||||
let position;
|
||||
switch (textItem.textAlign) {
|
||||
case "left":
|
||||
return { x: paddingX, y: centerY };
|
||||
position = { x: paddingX, y: centerY };
|
||||
break;
|
||||
case "right":
|
||||
return { x: panelWidth - paddingX, y: centerY };
|
||||
// For right alignment, position the right edge of text at panel edge minus padding
|
||||
position = { x: panelWidth - textWidth - paddingX, y: centerY };
|
||||
break;
|
||||
case "center":
|
||||
default:
|
||||
return { x: panelWidth / 2, y: centerY };
|
||||
// For center alignment, center the text area within the panel
|
||||
position = { x: (panelWidth - textWidth) / 2, y: centerY };
|
||||
break;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="panel-preview">
|
||||
<div class="preview-header">
|
||||
<div class="panel-title">{panel.texts[0]?.text || "Без названия"}</div>
|
||||
<div class="panel-title">{panel.text?.text || "Без названия"}</div>
|
||||
<Button variant="primary" size="sm" onclick={handleDownload}>Скачать</Button>
|
||||
</div>
|
||||
<div class="preview-sections">
|
||||
@@ -65,23 +86,29 @@
|
||||
<div class="preview-section">
|
||||
<div class="canvas-container">
|
||||
<Stage width={320} height={panel.height}>
|
||||
<!-- Background layer -->
|
||||
<Layer>
|
||||
{#if backgroundImage}
|
||||
<Image image={backgroundImage} width={320} height={panel.height} />
|
||||
{/if}
|
||||
{#each panel.texts || [] as textItem (textItem.id)}
|
||||
{@const textPosition = getTextPosition(textItem)}
|
||||
</Layer>
|
||||
|
||||
<!-- Text layer (renders above background) -->
|
||||
<Layer>
|
||||
{#if panel.text}
|
||||
{@const textPosition = getTextPosition(panel.text)}
|
||||
|
||||
<Text
|
||||
text={textItem.text}
|
||||
fontSize={textItem.fontSize}
|
||||
fill={textItem.color}
|
||||
fontFamily={textItem.fontFamily}
|
||||
text={panel.text.text}
|
||||
fontSize={panel.text.fontSize || 24}
|
||||
fill={panel.text.color || "#ffffff"}
|
||||
fontFamily={panel.text.fontFamily || "Arial"}
|
||||
x={textPosition.x}
|
||||
y={textPosition.y}
|
||||
align={textItem.textAlign}
|
||||
width={320 - textItem.paddingX * 2}
|
||||
align={panel.text.textAlign || "center"}
|
||||
width={300}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</Layer>
|
||||
</Stage>
|
||||
</div>
|
||||
|
||||
@@ -38,18 +38,17 @@ export class ExportService {
|
||||
ctx.drawImage(bgImage, 0, 0, 320, panel.height);
|
||||
|
||||
// Рисуем текст
|
||||
panel.texts.forEach((textItem) => {
|
||||
ctx.font = `${textItem.fontSize}px ${textItem.fontFamily}`;
|
||||
ctx.fillStyle = textItem.color;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const textItem = panel.text;
|
||||
ctx.font = `${textItem.fontSize}px ${textItem.fontFamily}`;
|
||||
ctx.fillStyle = textItem.color;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
// Calculate position based on existing system
|
||||
const x = 160; // Center of 320px panel
|
||||
const y = panel.height / 2 + textItem.verticalOffset;
|
||||
// Calculate position based on existing system
|
||||
const x = 160; // Center of 320px panel
|
||||
const y = panel.height / 2 + textItem.verticalOffset;
|
||||
|
||||
ctx.fillText(textItem.text, x, y);
|
||||
});
|
||||
ctx.fillText(textItem.text, x, y);
|
||||
|
||||
// Конвертируем в blob и сохраняем
|
||||
canvas.toBlob((blob) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface TextItem {
|
||||
export interface Panel {
|
||||
id: string;
|
||||
backgroundImage: string;
|
||||
texts: TextItem[];
|
||||
text: TextItem;
|
||||
height: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
+21
-1
@@ -13,11 +13,31 @@
|
||||
let backgroundImage = $state<string | undefined>(undefined);
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize with default texts for common Twitch panels
|
||||
console.log("[INIT] Creating default texts...");
|
||||
const defaultTexts = [
|
||||
{ id: crypto.randomUUID(), text: "About" },
|
||||
{ id: crypto.randomUUID(), text: "Links" },
|
||||
];
|
||||
texts = defaultTexts;
|
||||
console.log("[INIT] Default texts created:", texts);
|
||||
|
||||
// Load background image
|
||||
try {
|
||||
backgroundImage = await imageService.loadDefaultBackground();
|
||||
console.log("[INIT] Loading default background...");
|
||||
const loadedBackground = await imageService.loadDefaultBackground();
|
||||
console.log("[INIT] Background loaded:", loadedBackground);
|
||||
backgroundImage = loadedBackground;
|
||||
} catch (error) {
|
||||
console.error("[INIT] Error loading background:", error);
|
||||
exportService.setErrorMessage("Не удалось загрузить фоновое изображение по умолчанию");
|
||||
}
|
||||
|
||||
// Create panels from texts (with or without background)
|
||||
console.log("[INIT] Creating panels from texts...");
|
||||
const initialPanels = panelService.updatePanelsFromTexts(texts, [], backgroundImage || "");
|
||||
console.log("[INIT] Panels created:", initialPanels);
|
||||
panels = initialPanels;
|
||||
});
|
||||
|
||||
function handleImageUpload(image: string) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class PanelService {
|
||||
}
|
||||
|
||||
isDuplicateText(texts: Array<{ id: string; text: string }>, text: string, excludeId?: string): boolean {
|
||||
return texts.some(t => t.id !== excludeId && t.text === text);
|
||||
return texts.some((t) => t.id !== excludeId && t.text === text);
|
||||
}
|
||||
|
||||
addText(texts: Array<{ id: string; text: string }>, text: string): Array<{ id: string; text: string }> {
|
||||
@@ -28,20 +28,24 @@ export class PanelService {
|
||||
return [...texts, { id: crypto.randomUUID(), text }];
|
||||
}
|
||||
|
||||
updateText(texts: Array<{ id: string; text: string }>, id: string, newText: string): Array<{ id: string; text: string }> {
|
||||
updateText(
|
||||
texts: Array<{ id: string; text: string }>,
|
||||
id: string,
|
||||
newText: string,
|
||||
): Array<{ id: string; text: string }> {
|
||||
if (!this.validateText(newText)) return texts;
|
||||
if (this.isDuplicateText(texts, newText, id)) return texts;
|
||||
|
||||
return texts.map(t => t.id === id ? { ...t, text: newText } : t);
|
||||
return texts.map((t) => (t.id === id ? { ...t, text: newText } : t));
|
||||
}
|
||||
|
||||
deleteText(texts: Array<{ id: string; text: string }>, id: string): Array<{ id: string; text: string }> {
|
||||
return texts.filter(t => t.id !== id);
|
||||
return texts.filter((t) => t.id !== id);
|
||||
}
|
||||
|
||||
updatePanelsFromTexts(texts: Array<{ id: string; text: string }>, panels: Panel[], backgroundImage: string): Panel[] {
|
||||
return texts.map(textItem => {
|
||||
const existingPanel = panels.find(p => p.texts[0]?.text === textItem.text);
|
||||
return texts.map((textItem) => {
|
||||
const existingPanel = panels.find((p) => p.text.text === textItem.text);
|
||||
if (existingPanel) {
|
||||
return updatePanelText(existingPanel, textItem.text);
|
||||
}
|
||||
@@ -50,7 +54,7 @@ export class PanelService {
|
||||
}
|
||||
|
||||
updatePanelsBackground(panels: Panel[], newBackground: string): Panel[] {
|
||||
return panels.map(panel => ({
|
||||
return panels.map((panel) => ({
|
||||
...panel,
|
||||
backgroundImage: newBackground,
|
||||
updatedAt: new Date(),
|
||||
|
||||
+21
-29
@@ -4,11 +4,24 @@ import { type Panel, type TextItem } from "../lib/types/panel";
|
||||
|
||||
export const panelStore: Writable<Panel | undefined> = writable(undefined);
|
||||
|
||||
// Panel creation should go through panelService.updatePanelsFromTexts()
|
||||
// These functions are kept for backward compatibility but should be avoided
|
||||
export const createEmptyPanel = (height: number = 100): Panel => {
|
||||
const defaultText: TextItem = {
|
||||
id: uuidv4(),
|
||||
text: "",
|
||||
fontSize: 18,
|
||||
fontFamily: "Arial",
|
||||
color: "#ffffff",
|
||||
textAlign: "center",
|
||||
paddingX: 10,
|
||||
verticalOffset: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
id: uuidv4(),
|
||||
backgroundImage: "",
|
||||
texts: [],
|
||||
text: defaultText,
|
||||
height,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -23,32 +36,16 @@ export const updatePanel = (panel: Panel, updates: Partial<Panel>): Panel => {
|
||||
};
|
||||
};
|
||||
|
||||
export const addTextToPanel = (panel: Panel, text: string): Panel => {
|
||||
const newText: TextItem = {
|
||||
id: uuidv4(),
|
||||
text,
|
||||
fontSize: 18,
|
||||
fontFamily: "Arial",
|
||||
color: "#ffffff",
|
||||
textAlign: "center",
|
||||
paddingX: 10,
|
||||
verticalOffset: 0,
|
||||
};
|
||||
|
||||
export const updatePanelText = (panel: Panel, text: string): Panel => {
|
||||
return updatePanel(panel, {
|
||||
texts: [...panel.texts, newText],
|
||||
text: { ...panel.text, text },
|
||||
});
|
||||
};
|
||||
|
||||
export const updateTextInPanel = (panel: Panel, textId: string, updates: Partial<Panel["texts"][0]>): Panel => {
|
||||
const updatedTexts = panel.texts.map((text) => (text.id === textId ? { ...text, ...updates } : text));
|
||||
|
||||
return updatePanel(panel, { texts: updatedTexts });
|
||||
};
|
||||
|
||||
export const removeTextFromPanel = (panel: Panel, textId: string): Panel => {
|
||||
const updatedTexts = panel.texts.filter((text) => text.id !== textId);
|
||||
return updatePanel(panel, { texts: updatedTexts });
|
||||
export const updateTextProperties = (panel: Panel, updates: Partial<TextItem>): Panel => {
|
||||
return updatePanel(panel, {
|
||||
text: { ...panel.text, ...updates },
|
||||
});
|
||||
};
|
||||
|
||||
export const createPanelFromText = (backgroundImage: string, text: string, height: number = 100): Panel => {
|
||||
@@ -66,14 +63,9 @@ export const createPanelFromText = (backgroundImage: string, text: string, heigh
|
||||
return {
|
||||
id: uuidv4(),
|
||||
backgroundImage,
|
||||
texts: [newText],
|
||||
text: newText,
|
||||
height,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
export const updatePanelText = (panel: Panel, text: string): Panel => {
|
||||
const updatedTexts = panel.texts.map((t) => ({ ...t, text }));
|
||||
return updatePanel(panel, { texts: updatedTexts });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user