refactor: consistent component props
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let {
|
||||
onUploadNewImage
|
||||
}: {
|
||||
interface Props {
|
||||
onUploadNewImage?: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { onUploadNewImage }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="app-header">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let {
|
||||
backgroundImage,
|
||||
onUploadNewImage
|
||||
}: {
|
||||
interface Props {
|
||||
backgroundImage?: string | null;
|
||||
onUploadNewImage: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { backgroundImage, onUploadNewImage }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if backgroundImage}
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
import { uiStore, setLoading } from "../stores/uiStore";
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let {
|
||||
imageSrc,
|
||||
onCropComplete,
|
||||
onCancel,
|
||||
}: {
|
||||
interface Props {
|
||||
imageSrc: string;
|
||||
onCropComplete: (croppedImage: string) => void;
|
||||
onCancel: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { imageSrc, onCropComplete, onCancel }: Props = $props();
|
||||
|
||||
let imageElement: HTMLImageElement;
|
||||
let cropper: CropperJS | null = null;
|
||||
@@ -109,9 +107,7 @@
|
||||
|
||||
<div class="cropper-actions">
|
||||
<Button variant="secondary" onclick={handleCancel} disabled={isProcessing}>Отмена</Button>
|
||||
<Button variant="primary" onclick={handleCrop} disabled={isProcessing} loading={isProcessing}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="primary" onclick={handleCrop} disabled={isProcessing} loading={isProcessing}>Применить</Button>
|
||||
</div>
|
||||
|
||||
<div class="cropper-info">
|
||||
@@ -202,11 +198,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.cropper-wrap-box,
|
||||
.cropper-canvas,
|
||||
.cropper-drag-box,
|
||||
.cropper-crop-box,
|
||||
.cropper-modal) {
|
||||
:global(.cropper-wrap-box, .cropper-canvas, .cropper-drag-box, .cropper-crop-box, .cropper-modal) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
@@ -214,8 +206,7 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
:global(.cropper-wrap-box,
|
||||
.cropper-canvas) {
|
||||
:global(.cropper-wrap-box, .cropper-canvas) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
import ImageUpload from "./ImageUpload.svelte";
|
||||
import ImageCropper from "./ImageCropper.svelte";
|
||||
|
||||
let {
|
||||
currentStep,
|
||||
uploadedImage,
|
||||
onImageUpload,
|
||||
onCropComplete,
|
||||
onCropCancel
|
||||
}: {
|
||||
interface Props {
|
||||
currentStep: string;
|
||||
uploadedImage: string | null;
|
||||
onImageUpload: (image: string) => void;
|
||||
onCropComplete: (croppedImage: string) => void;
|
||||
onCropCancel: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { currentStep, uploadedImage, onImageUpload, onCropComplete, onCropCancel }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if currentStep === "upload"}
|
||||
<ImageUpload onImageSelect={onImageUpload} />
|
||||
{:else if currentStep === "crop" && uploadedImage}
|
||||
<ImageCropper imageSrc={uploadedImage} onCropComplete={onCropComplete} onCancel={onCropCancel} />
|
||||
<ImageCropper imageSrc={uploadedImage} {onCropComplete} onCancel={onCropCancel} />
|
||||
{/if}
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
let imageService = new ImageService();
|
||||
|
||||
let { onImageSelect }: { onImageSelect: (image: string) => void } = $props();
|
||||
interface Props {
|
||||
onImageSelect: (image: string) => void;
|
||||
}
|
||||
|
||||
let { onImageSelect }: Props = $props();
|
||||
|
||||
let dropZone = $state<HTMLElement | null>(null);
|
||||
let fileInput = $state<HTMLInputElement | null>(null);
|
||||
@@ -167,7 +171,7 @@
|
||||
tabindex="0"
|
||||
onclick={triggerFileInput}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
triggerFileInput();
|
||||
}
|
||||
@@ -184,7 +188,13 @@
|
||||
<p>Перетащите файл сюда, нажмите для выбора, или вставьте через Ctrl+V</p>
|
||||
|
||||
<div class="upload-methods">
|
||||
<Button variant="primary" onclick={(e) => { e.stopPropagation(); triggerFileInput(); }}>Выбрать файл</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
triggerFileInput();
|
||||
}}>Выбрать файл</Button
|
||||
>
|
||||
|
||||
<Button variant="secondary" onclick={() => (showUrlInput = !showUrlInput)}>
|
||||
{showUrlInput ? "Скрыть" : "По URL"}
|
||||
@@ -200,7 +210,9 @@
|
||||
required
|
||||
class="url-input"
|
||||
/>
|
||||
<Button type="submit" variant="primary" disabled={$uiStore.isLoading} loading={$uiStore.isLoading}>Загрузить</Button>
|
||||
<Button type="submit" variant="primary" disabled={$uiStore.isLoading} loading={$uiStore.isLoading}
|
||||
>Загрузить</Button
|
||||
>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { panelStore } from "../stores/panelStore";
|
||||
@@ -6,10 +5,11 @@
|
||||
import type { Panel } from "../lib/types/panel";
|
||||
import { IconButton } from "../lib/components/ui";
|
||||
|
||||
let { onPanelSelect, onPanelDelete }: {
|
||||
interface Props {
|
||||
onPanelSelect: (panel: Panel) => void;
|
||||
onPanelDelete: (panelId: string) => void;
|
||||
} = $props();
|
||||
}
|
||||
let { onPanelSelect, onPanelDelete }: Props = $props();
|
||||
|
||||
let panels = $state<Panel[]>([]);
|
||||
let selectedPanelId = $state<string | null>(null);
|
||||
@@ -76,9 +76,7 @@
|
||||
<div class="panel-list">
|
||||
<div class="panel-list-header">
|
||||
<h2>Сохраненные панели</h2>
|
||||
<IconButton variant="secondary" onclick={loadPanels} ariaLabel="Обновить список">
|
||||
↻
|
||||
</IconButton>
|
||||
<IconButton variant="secondary" onclick={loadPanels} ariaLabel="Обновить список">↻</IconButton>
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
@@ -102,11 +100,7 @@
|
||||
>
|
||||
<div class="panel-preview">
|
||||
{#if panel.backgroundImage}
|
||||
<img
|
||||
src={panel.backgroundImage}
|
||||
alt="Panel preview"
|
||||
class="panel-image"
|
||||
/>
|
||||
<img src={panel.backgroundImage} alt="Panel preview" class="panel-image" />
|
||||
{:else}
|
||||
<div class="no-image">Нет изображения</div>
|
||||
{/if}
|
||||
@@ -121,7 +115,10 @@
|
||||
<div class="panel-actions">
|
||||
<IconButton
|
||||
variant="danger"
|
||||
onclick={(e) => { e.stopPropagation(); handlePanelDelete(panel.id); }}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePanelDelete(panel.id);
|
||||
}}
|
||||
ariaLabel="Удалить панель"
|
||||
>
|
||||
🗑️
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
import { Stage, Layer, Image, Text } from "svelte-konva";
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let {
|
||||
panel,
|
||||
onDownload,
|
||||
}: {
|
||||
interface Props {
|
||||
panel: Panel;
|
||||
onDownload?: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { panel, onDownload }: Props = $props();
|
||||
|
||||
let backgroundImage: HTMLImageElement | undefined = $state(undefined);
|
||||
|
||||
@@ -63,33 +62,33 @@
|
||||
<div class="panel-title">{panel.texts[0]?.text || "Без названия"}</div>
|
||||
<Button variant="primary" size="sm" onclick={onDownload}>Скачать</Button>
|
||||
</div>
|
||||
<div class="preview-sections">
|
||||
<!-- Превью с текстом -->
|
||||
<div class="preview-section">
|
||||
<div class="canvas-container">
|
||||
<Stage width={320} height={panel.height}>
|
||||
<Layer>
|
||||
{#if backgroundImage}
|
||||
<Image image={backgroundImage} width={320} height={panel.height} />
|
||||
{/if}
|
||||
{#each panel.texts || [] as textItem (textItem.id)}
|
||||
{@const textPosition = getTextPosition(textItem)}
|
||||
<Text
|
||||
text={textItem.text}
|
||||
fontSize={textItem.fontSize}
|
||||
fill={textItem.color}
|
||||
fontFamily={textItem.fontFamily}
|
||||
x={textPosition.x}
|
||||
y={textPosition.y}
|
||||
align={textItem.textAlign}
|
||||
width={320 - textItem.paddingX * 2}
|
||||
/>
|
||||
{/each}
|
||||
</Layer>
|
||||
</Stage>
|
||||
</div>
|
||||
<div class="preview-sections">
|
||||
<!-- Превью с текстом -->
|
||||
<div class="preview-section">
|
||||
<div class="canvas-container">
|
||||
<Stage width={320} height={panel.height}>
|
||||
<Layer>
|
||||
{#if backgroundImage}
|
||||
<Image image={backgroundImage} width={320} height={panel.height} />
|
||||
{/if}
|
||||
{#each panel.texts || [] as textItem (textItem.id)}
|
||||
{@const textPosition = getTextPosition(textItem)}
|
||||
<Text
|
||||
text={textItem.text}
|
||||
fontSize={textItem.fontSize}
|
||||
fill={textItem.color}
|
||||
fontFamily={textItem.fontFamily}
|
||||
x={textPosition.x}
|
||||
y={textPosition.y}
|
||||
align={textItem.textAlign}
|
||||
width={320 - textItem.paddingX * 2}
|
||||
/>
|
||||
{/each}
|
||||
</Layer>
|
||||
</Stage>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
import PanelPreview from "./PanelPreview.svelte";
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let {
|
||||
panels,
|
||||
onDownload,
|
||||
onDownloadAll
|
||||
}: {
|
||||
interface Props {
|
||||
panels: Panel[];
|
||||
onDownload: (panel: Panel) => void;
|
||||
onDownloadAll: () => void;
|
||||
} = $props();
|
||||
}
|
||||
let { panels, onDownload, onDownloadAll }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if panels.length > 0}
|
||||
@@ -23,10 +20,7 @@
|
||||
<div class="panels-list">
|
||||
{#each panels as panel (panel.id)}
|
||||
<div class="panel-item">
|
||||
<PanelPreview
|
||||
panel={panel}
|
||||
onDownload={() => onDownload(panel)}
|
||||
/>
|
||||
<PanelPreview {panel} onDownload={() => onDownload(panel)} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Button } from "../lib/components/ui";
|
||||
|
||||
let { onTextAdd, onTextUpdate, onTextDelete, texts }: {
|
||||
interface Props {
|
||||
onTextAdd: (text: string) => void;
|
||||
onTextUpdate: (id: string, text: string) => void;
|
||||
onTextDelete: (id: string) => void;
|
||||
texts: Array<{ id: string; text: string }>;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { onTextAdd, onTextUpdate, onTextDelete, texts }: Props = $props();
|
||||
|
||||
let newText = $state("");
|
||||
let errorMessage = $state<string | null>(null);
|
||||
@@ -107,12 +108,7 @@
|
||||
class="text-edit-input"
|
||||
maxlength="100"
|
||||
/>
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
onclick={() => handleDeleteText(textItem.id)}
|
||||
aria-label="Удалить текст"
|
||||
>
|
||||
<Button variant="danger" size="sm" onclick={() => handleDeleteText(textItem.id)} aria-label="Удалить текст">
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
@@ -125,7 +121,7 @@
|
||||
<div class="common-settings-section">
|
||||
<h3>Общие настройки текста</h3>
|
||||
<p class="settings-note">Настройки применятся ко всем создаваемым панелям</p>
|
||||
|
||||
|
||||
<div class="settings-grid">
|
||||
<div class="control-group">
|
||||
<label>
|
||||
@@ -180,7 +176,7 @@
|
||||
<button
|
||||
class="align-btn {commonTextSettings.textAlign === 'left' ? 'active' : ''}"
|
||||
onclick={() => {
|
||||
commonTextSettings.textAlign = 'left';
|
||||
commonTextSettings.textAlign = "left";
|
||||
}}
|
||||
aria-label="Выровнять по левому краю"
|
||||
>
|
||||
@@ -189,7 +185,7 @@
|
||||
<button
|
||||
class="align-btn {commonTextSettings.textAlign === 'center' ? 'active' : ''}"
|
||||
onclick={() => {
|
||||
commonTextSettings.textAlign = 'center';
|
||||
commonTextSettings.textAlign = "center";
|
||||
}}
|
||||
aria-label="Выровнять по центру"
|
||||
>
|
||||
@@ -198,7 +194,7 @@
|
||||
<button
|
||||
class="align-btn {commonTextSettings.textAlign === 'right' ? 'active' : ''}"
|
||||
onclick={() => {
|
||||
commonTextSettings.textAlign = 'right';
|
||||
commonTextSettings.textAlign = "right";
|
||||
}}
|
||||
aria-label="Выровнять по правому краю"
|
||||
>
|
||||
@@ -238,12 +234,13 @@
|
||||
commonTextSettings.verticalOffset = parseInt(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<span class="value-display">{commonTextSettings.verticalOffset > 0 ? '+' : ''}{commonTextSettings.verticalOffset}px</span>
|
||||
<span class="value-display"
|
||||
>{commonTextSettings.verticalOffset > 0 ? "+" : ""}{commonTextSettings.verticalOffset}px</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
import { panelStore } from "../stores/panelStore";
|
||||
import { Stage, Layer, Image, Text } from "svelte-konva";
|
||||
|
||||
let {
|
||||
textItem,
|
||||
height,
|
||||
}: {
|
||||
interface Props {
|
||||
textItem: TextItem;
|
||||
height: number;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { textItem, height }: Props = $props();
|
||||
|
||||
let bgImage: HTMLImageElement | undefined = $state(undefined);
|
||||
let currentPanel = $state($panelStore);
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
<script lang="ts">
|
||||
import TextManager from "./TextManager.svelte";
|
||||
|
||||
let {
|
||||
texts,
|
||||
onTextAdd,
|
||||
onTextUpdate,
|
||||
onTextDelete
|
||||
}: {
|
||||
interface Props {
|
||||
texts: Array<{ id: string; text: string }>;
|
||||
onTextAdd: (text: string) => void;
|
||||
onTextUpdate: (id: string, text: string) => void;
|
||||
onTextDelete: (id: string) => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { texts, onTextAdd, onTextUpdate, onTextDelete }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="text-section">
|
||||
<h2>Добавьте тексты для панелей</h2>
|
||||
<TextManager
|
||||
onTextAdd={onTextAdd}
|
||||
onTextUpdate={onTextUpdate}
|
||||
onTextDelete={onTextDelete}
|
||||
texts={texts}
|
||||
/>
|
||||
<TextManager {onTextAdd} {onTextUpdate} {onTextDelete} {texts} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
type ButtonSize = "sm" | "md" | "lg";
|
||||
type ButtonType = "button" | "submit" | "reset";
|
||||
|
||||
interface Props {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
disabled?: boolean;
|
||||
type?: ButtonType;
|
||||
fullWidth?: boolean;
|
||||
loading?: boolean;
|
||||
class?: string;
|
||||
children?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
let {
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
@@ -13,17 +25,7 @@
|
||||
class: className = "",
|
||||
children,
|
||||
...restProps
|
||||
}: {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
disabled?: boolean;
|
||||
type?: ButtonType;
|
||||
fullWidth?: boolean;
|
||||
loading?: boolean;
|
||||
class?: string;
|
||||
children?: any;
|
||||
[key: string]: any;
|
||||
} = $props();
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
type IconButtonVariant = "primary" | "secondary" | "danger";
|
||||
type IconButtonSize = "sm" | "md" | "lg";
|
||||
|
||||
interface Props {
|
||||
variant?: IconButtonVariant;
|
||||
size?: IconButtonSize;
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
class?: string;
|
||||
children?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
let {
|
||||
variant = "secondary",
|
||||
size = "md",
|
||||
@@ -10,15 +20,7 @@
|
||||
class: className = "",
|
||||
children,
|
||||
...restProps
|
||||
}: {
|
||||
variant?: IconButtonVariant;
|
||||
size?: IconButtonSize;
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
class?: string;
|
||||
children?: any;
|
||||
[key: string]: any;
|
||||
} = $props();
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user