feat: implement state and components for text config
This commit is contained in:
@@ -6,27 +6,28 @@
|
|||||||
import ColorPicker from "$components/ui/ColorPicker.svelte";
|
import ColorPicker from "$components/ui/ColorPicker.svelte";
|
||||||
import RangeSlider from "$components/ui/RangeSlider.svelte";
|
import RangeSlider from "$components/ui/RangeSlider.svelte";
|
||||||
import SelectFont from "$components/ui/SelectFont.svelte";
|
import SelectFont from "$components/ui/SelectFont.svelte";
|
||||||
|
import { textConfigState } from "$states/textConfig.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card title="Настройки текста">
|
<Card title="Настройки текста">
|
||||||
<SettingsGrid>
|
<SettingsGrid>
|
||||||
<SettingsRow label="Размер">
|
<SettingsRow label="Размер">
|
||||||
<RangeSlider />
|
<RangeSlider bind:value={textConfigState.fontSize} min={10} max={100} step={1} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<SettingsRow label="Шрифт">
|
<SettingsRow label="Шрифт">
|
||||||
<SelectFont />
|
<SelectFont bind:value={textConfigState.fontFamily} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<SettingsRow label="Цвет">
|
<SettingsRow label="Цвет">
|
||||||
<ColorPicker />
|
<ColorPicker bind:value={textConfigState.color} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<SettingsRow label="Выравнивание">
|
<SettingsRow label="Выравнивание">
|
||||||
<Alignment />
|
<Alignment bind:align={textConfigState.align} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<SettingsRow label="Отступы">
|
<SettingsRow label="Отступы">
|
||||||
<RangeSlider />
|
<RangeSlider bind:value={textConfigState.paddingX} min={0} max={100} step={1} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<SettingsRow label="Смещение">
|
<SettingsRow label="Смещение">
|
||||||
<RangeSlider />
|
<RangeSlider bind:value={textConfigState.offsetY} min={-100} max={100} step={1} />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</SettingsGrid>
|
</SettingsGrid>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { TextAlign } from "$lib/types/text";
|
||||||
import IconAlignCenter from "./Icons/IconAlignCenter.svelte";
|
import IconAlignCenter from "./Icons/IconAlignCenter.svelte";
|
||||||
import IconAlignLeft from "./Icons/IconAlignLeft.svelte";
|
import IconAlignLeft from "./Icons/IconAlignLeft.svelte";
|
||||||
import IconAlignRight from "./Icons/IconAlignRight.svelte";
|
import IconAlignRight from "./Icons/IconAlignRight.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
align: TextAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { align = $bindable("left") }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="align-btn active">
|
<button class="align-btn" class:active={align === "left"} onclick={() => (align = "left")}>
|
||||||
<IconAlignLeft />
|
<IconAlignLeft />
|
||||||
</button>
|
</button>
|
||||||
<button class="align-btn">
|
<button class="align-btn" class:active={align === "center"} onclick={() => (align = "center")}>
|
||||||
<IconAlignCenter />
|
<IconAlignCenter />
|
||||||
</button>
|
</button>
|
||||||
<button class="align-btn">
|
<button class="align-btn" class:active={align === "right"} onclick={() => (align = "right")}>
|
||||||
<IconAlignRight />
|
<IconAlignRight />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<input type="color" value="#ffffff" class="color-input" />
|
<script lang="ts">
|
||||||
<span class="color-value">#ffffff</span>
|
import type { HexColor } from "$lib/types/text";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: HexColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable() }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="color" bind:value class="color-input" />
|
||||||
|
<span class="color-value">{value}</span>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.color-input {
|
.color-input {
|
||||||
@@ -10,6 +20,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
|
padding: 0px 8px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-input:hover {
|
.color-input:hover {
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
<input type="range" id="fontSize" min="12" max="48" value="18" class="slider" />
|
<script lang="ts">
|
||||||
<span class="value-display" id="fontSizeValue">18</span>
|
import type { ChangeEventHandler } from "svelte/elements";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
onchange?: ChangeEventHandler<HTMLInputElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable(), min = 0, max = 100, step = 1, onchange = () => {} }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="range" {min} {max} {step} bind:value class="slider" {onchange} />
|
||||||
|
<span class="value-display">{value}</span>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.slider {
|
.slider {
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
<select class="select-input">
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable() }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select class="select-input" bind:value>
|
||||||
<option value="Arial">Arial</option>
|
<option value="Arial">Arial</option>
|
||||||
<option value="Verdana">Verdana</option>
|
<option value="Verdana">Verdana</option>
|
||||||
<option value="Georgia">Georgia</option>
|
<option value="Georgia">Georgia</option>
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
export type TextAlign = "left" | "center" | "right";
|
|
||||||
|
|
||||||
export interface TextItem {
|
|
||||||
id: string;
|
|
||||||
text: string;
|
|
||||||
fontSize: number;
|
|
||||||
fontFamily: string;
|
|
||||||
color: string;
|
|
||||||
textAlign: TextAlign;
|
|
||||||
paddingX: number;
|
|
||||||
verticalOffset: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Panel {
|
|
||||||
id: string;
|
|
||||||
backgroundImage: string;
|
|
||||||
text: TextItem;
|
|
||||||
height: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageUploadResult {
|
|
||||||
success: boolean;
|
|
||||||
image?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CropOptions {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageCropResult {
|
|
||||||
success: boolean;
|
|
||||||
croppedImage?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UIState {
|
|
||||||
isLoading: boolean;
|
|
||||||
error: string | undefined;
|
|
||||||
currentStep: "upload" | "crop" | "text" | "preview";
|
|
||||||
showCropModal: boolean;
|
|
||||||
showTextManager: boolean;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type TextAlign = "left" | "center" | "right";
|
||||||
|
|
||||||
|
export type HexColor = `#${string}`;
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-primary: #ffffff;
|
--bg-primary: #ffffff;
|
||||||
--bg-secondary: #fafafa;
|
--bg-secondary: #dddddd;
|
||||||
--bg-card: #ffffff;
|
--bg-card: #ffffff;
|
||||||
--bg-hover: #f5f5f5;
|
--bg-hover: #f5f5f5;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
:global([data-theme="dark"]) {
|
:global([data-theme="dark"]) {
|
||||||
--bg-primary: #0a0a0a;
|
--bg-primary: #0a0a0a;
|
||||||
--bg-secondary: #1a1a1a;
|
--bg-secondary: #333333;
|
||||||
--bg-card: #1a1a1a;
|
--bg-card: #1a1a1a;
|
||||||
--bg-hover: #2a2a2a;
|
--bg-hover: #2a2a2a;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import type { HexColor, TextAlign } from "$lib/types/text";
|
||||||
|
|
||||||
|
export type TextConfig = {
|
||||||
|
fontSize: number;
|
||||||
|
fontFamily: string;
|
||||||
|
color: HexColor;
|
||||||
|
align: TextAlign;
|
||||||
|
paddingX: number;
|
||||||
|
offsetY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createState() {
|
||||||
|
const defaults: TextConfig = {
|
||||||
|
fontSize: 24,
|
||||||
|
fontFamily: "Arial",
|
||||||
|
color: "#ffffff",
|
||||||
|
align: "center",
|
||||||
|
paddingX: 10,
|
||||||
|
offsetY: 0,
|
||||||
|
};
|
||||||
|
let state: TextConfig = $state({ ...defaults });
|
||||||
|
|
||||||
|
return {
|
||||||
|
get fontSize() {
|
||||||
|
return state.fontSize;
|
||||||
|
},
|
||||||
|
set fontSize(value: number) {
|
||||||
|
state.fontSize = value;
|
||||||
|
},
|
||||||
|
get fontFamily() {
|
||||||
|
return state.fontFamily;
|
||||||
|
},
|
||||||
|
set fontFamily(fontFamily: string) {
|
||||||
|
state.fontFamily = this.fontFamily;
|
||||||
|
},
|
||||||
|
get color() {
|
||||||
|
return state.color;
|
||||||
|
},
|
||||||
|
set color(color: HexColor) {
|
||||||
|
state.color = color;
|
||||||
|
},
|
||||||
|
get align() {
|
||||||
|
return state.align;
|
||||||
|
},
|
||||||
|
set align(align: TextAlign) {
|
||||||
|
state.align = align;
|
||||||
|
},
|
||||||
|
get paddingX() {
|
||||||
|
return state.paddingX;
|
||||||
|
},
|
||||||
|
set paddingX(paddingX: number) {
|
||||||
|
state.paddingX = paddingX;
|
||||||
|
},
|
||||||
|
get offsetY() {
|
||||||
|
return state.offsetY;
|
||||||
|
},
|
||||||
|
set offsetY(offsetY: number) {
|
||||||
|
state.offsetY = offsetY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const textConfigState = createState();
|
||||||
@@ -5,7 +5,7 @@ export interface TextItem {
|
|||||||
|
|
||||||
const defaultTexts: Array<TextItem> = ["About me", "Links", "Projects"].map((text, idx) => ({ text, id: idx }));
|
const defaultTexts: Array<TextItem> = ["About me", "Links", "Projects"].map((text, idx) => ({ text, id: idx }));
|
||||||
|
|
||||||
function createTextsState() {
|
function createState() {
|
||||||
let texts: Array<TextItem> = $state(defaultTexts);
|
let texts: Array<TextItem> = $state(defaultTexts);
|
||||||
let nextId = $state(defaultTexts.length);
|
let nextId = $state(defaultTexts.length);
|
||||||
|
|
||||||
@@ -23,4 +23,4 @@ function createTextsState() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const textsState = createTextsState();
|
export const textsState = createState();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type Theme = "dark" | "light";
|
export type Theme = "dark" | "light";
|
||||||
|
|
||||||
function createThemeState() {
|
function createState() {
|
||||||
let current: Theme = $state("dark");
|
let current: Theme = $state("dark");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -13,4 +13,4 @@ function createThemeState() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeState = createThemeState();
|
export const themeState = createState();
|
||||||
|
|||||||
Reference in New Issue
Block a user