feat: WIP: update to new components structure

This commit is contained in:
2026-02-05 14:25:00 +05:00
parent 21551ef7d7
commit b2e3cf528c
10 changed files with 582 additions and 433 deletions
+10 -19
View File
@@ -1,4 +1,7 @@
<script lang="ts">
import IconMoon from "$components/ui/Icons/IconMoon.svelte";
import IconSun from "$components/ui/Icons/IconSun.svelte";
interface Props {}
let {}: Props = $props();
@@ -16,20 +19,8 @@
<div class="header-content">
<h1>Twitch Panels</h1>
<button class="theme-toggle" aria-label="Toggle theme" onclick={toggleTheme}>
<svg class="sun-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="moon-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
<IconSun />
<IconMoon />
</button>
</div>
</header>
@@ -73,29 +64,29 @@
background: rgba(255, 255, 255, 0.3);
}
.theme-toggle svg {
:global(.theme-toggle svg) {
width: 18px;
height: 18px;
position: absolute;
transition: var(--transition);
}
.sun-icon {
:global(.sun-icon) {
opacity: 1;
transform: rotate(0deg);
}
.moon-icon {
:global(.moon-icon) {
opacity: 0;
transform: rotate(90deg);
}
:global([data-theme="dark"]) .sun-icon {
:global([data-theme="dark"] .sun-icon) {
opacity: 0;
transform: rotate(90deg);
}
:global([data-theme="dark"]) .moon-icon {
:global([data-theme="dark"] .moon-icon) {
opacity: 1;
transform: rotate(0deg);
}
+60
View File
@@ -0,0 +1,60 @@
<script lang="ts">
import type { Snippet } from "svelte";
interface Props {
title: string | Snippet;
children: Snippet;
titleSnippet?: Snippet;
}
let { title, children, titleSnippet = emptySnippet }: Props = $props();
</script>
<!-- svelte-ignore block_empty -->
{#snippet emptySnippet()}{/snippet}
<section class="card">
<div class="card-header-row">
<h2 class="card-title">
{#if typeof title === "string"}
{title}
{:else}
{@render title()}
{/if}
</h2>
<div class="card-snippet">
{@render titleSnippet()}
</div>
</div>
<div class="card-body">
{@render children()}
</div>
</section>
<style>
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 16px;
margin-bottom: 16px;
transition: var(--transition);
}
.card-title {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.card-header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
</style>
+28
View File
@@ -0,0 +1,28 @@
<script lang="ts">
import IconArrowLeft from "$components/ui/Icons/IconArrowLeft.svelte";
import IconArrowRight from "$components/ui/Icons/IconArrowRight.svelte";
import Card from "./Card.svelte";
</script>
<div class="panel-bar">
<Card title="Фоновое изображение">фоновое изображение</Card>
{#snippet panelTitle()}
Панели <span class="badge" id="panelCount">0</span>
{/snippet}
{#snippet panelControls()}
<button id="prevPanel" class="nav-btn" disabled>
<IconArrowLeft />
</button>
<span id="panelIndicator" class="panel-indicator">0 / 0</span>
<button id="nextPanel" class="nav-btn" disabled>
<IconArrowRight />
</button>
{/snippet}
<Card title={panelTitle} titleSnippet={panelControls}>Панели</Card>
</div>
<style>
</style>
+13
View File
@@ -0,0 +1,13 @@
<script lang="ts">
import TextManager from "$components/text/TextManager.svelte";
import Card from "./Card.svelte";
</script>
<div class="text-bar">
<Card title="Тексты панелей"><TextManager /></Card>
<Card title="Настройки текста">Настройки текста</Card>
</div>
<style>
</style>
+5 -409
View File
@@ -1,414 +1,10 @@
<script lang="ts">
import Button from "$components/ui/Button.svelte";
import { TYPOGRAPHY } from "$lib/constants";
import type { TextAlign, TextItem } from "$lib/types/panel";
import { textSettingsStore, updateAllTextSettings } from "$stores/panelStore";
interface Props {
onTextAdd: (text: string, settings?: Partial<TextItem>) => void;
onTextUpdate: (id: string, text: string) => void;
onTextDelete: (id: string) => void;
texts: Array<{ id: string; text: string }>;
}
let { onTextAdd, onTextUpdate, onTextDelete, texts }: Props = $props();
let newText = $state("");
let errorMessage = $state<string | undefined>(undefined);
let commonTextSettings = $derived($textSettingsStore);
const availableFonts = TYPOGRAPHY.FONT_FAMILIES;
function handleAddText() {
if (!newText.trim()) {
errorMessage = "Введите текст";
return;
}
if (newText.length > TYPOGRAPHY.MAX_TEXT_LENGTH) {
errorMessage = `Текст не должен превышать ${TYPOGRAPHY.MAX_TEXT_LENGTH} символов`;
return;
}
try {
errorMessage = undefined;
onTextAdd(newText.trim(), commonTextSettings);
newText = "";
} catch (error) {
errorMessage = error instanceof Error ? error.message : "Ошибка добавления текста";
}
}
function handleKeyPress(e: KeyboardEvent) {
if (e.key === "Enter") {
handleAddText();
}
}
function handleUpdateText(id: string, newText: string) {
if (onTextUpdate && newText.trim()) {
onTextUpdate(id, newText.trim());
}
}
function handleDeleteText(id: string) {
if (onTextDelete) {
onTextDelete(id);
}
}
import IconPlus from "$components/ui/Icons/IconPlus.svelte";
</script>
<div class="text-manager">
<div class="add-text-section">
<div class="input-group">
<input
type="text"
bind:value={newText}
placeholder="Введите текст для панели (например: links, about me, projects...)"
class="text-input"
maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}
onkeypress={handleKeyPress}
/>
<Button variant="primary" onclick={handleAddText}>Добавить</Button>
</div>
{#if errorMessage}
<div class="error-message">
{errorMessage}
</div>
{/if}
</div>
{#if texts.length > 0}
<div class="texts-list">
<h3>Созданные тексты ({texts.length})</h3>
<div class="texts-container">
{#each texts as textItem (textItem.id)}
<div class="text-item">
<input
type="text"
value={textItem.text}
oninput={(e) => handleUpdateText(textItem.id, e.currentTarget.value)}
class="text-edit-input"
maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}
/>
<Button variant="danger" size="sm" onclick={() => handleDeleteText(textItem.id)} aria-label="Удалить текст">
×
</Button>
</div>
{/each}
</div>
</div>
{/if}
<div class="common-settings-section">
<h3>Общие настройки текста</h3>
<p class="settings-note">Настройки применятся ко всем создаваемым панелям</p>
<div class="settings-grid">
<div class="control-group">
<label>
Размер шрифта:
<input
type="range"
min={TYPOGRAPHY.FONT_SIZE_MIN}
max={TYPOGRAPHY.FONT_SIZE_MAX}
step="1"
value={commonTextSettings.fontSize}
oninput={(e: Event) => {
updateAllTextSettings({ fontSize: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display">{commonTextSettings.fontSize}px</span>
</label>
</div>
<div class="control-group">
<label>
Шрифт:
<select
value={commonTextSettings.fontFamily}
onchange={(e) => {
updateAllTextSettings({ fontFamily: (e.target as HTMLSelectElement).value });
}}
>
{#each availableFonts as font}
<option value={font}>{font}</option>
{/each}
</select>
</label>
</div>
<div class="control-group">
<label>
Цвет:
<input
type="color"
value={commonTextSettings.color}
oninput={(e) => {
updateAllTextSettings({ color: (e.target as HTMLInputElement).value });
}}
/>
</label>
</div>
<div class="control-group">
<label>
Выравнивание:
<div class="alignment-buttons">
<button
class="align-btn {commonTextSettings.textAlign === 'left' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "left" as TextAlign });
}}
aria-label="Выровнять по левому краю"
>
<div class="input-group">
<input type="text" id="panelTextInput" placeholder="Введите текст..." class="text-input" />
<button id="addTextBtn" class="btn btn-primary">
<IconPlus />
</button>
<button
class="align-btn {commonTextSettings.textAlign === 'center' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "center" as TextAlign });
}}
aria-label="Выровнять по центру"
>
</button>
<button
class="align-btn {commonTextSettings.textAlign === 'right' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "right" as TextAlign });
}}
aria-label="Выровнять по правому краю"
>
</button>
</div>
</label>
</div>
<div class="control-group">
<label>
Боковые отступы:
<input
type="range"
min="0"
max={TYPOGRAPHY.PADDING_X_MAX}
step="1"
value={commonTextSettings.paddingX}
oninput={(e) => {
updateAllTextSettings({ paddingX: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display">{commonTextSettings.paddingX}px</span>
</label>
</div>
<div class="control-group">
<label>
Смещение от центра:
<input
type="range"
min={TYPOGRAPHY.VERTICAL_OFFSET_MIN}
max={TYPOGRAPHY.VERTICAL_OFFSET_MAX}
step="1"
value={commonTextSettings.verticalOffset}
oninput={(e) => {
updateAllTextSettings({ verticalOffset: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display"
>{(commonTextSettings.verticalOffset ?? 0) > 0 ? "+" : ""}{commonTextSettings.verticalOffset ?? 0}px</span
>
</label>
</div>
</div>
</div>
</div>
<style>
.text-manager {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.common-settings-section {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.common-settings-section h3 {
margin: 0 0 0.75rem 0;
color: #333;
font-size: 1rem;
font-weight: 500;
}
.settings-note {
margin: 0 0 1rem 0;
color: #666;
font-size: 0.875rem;
font-style: italic;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.alignment-buttons {
display: flex;
gap: 0.25rem;
}
.align-btn {
padding: 0.5rem 0.75rem;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 1rem;
}
.align-btn:hover {
background: #f0f0f0;
border-color: #007bff;
}
.align-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.add-text-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.texts-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.texts-list h3 {
margin: 0;
color: #333;
font-size: 1rem;
font-weight: 600;
}
.texts-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.text-item {
display: flex;
gap: 0.5rem;
align-items: center;
}
.text-edit-input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
}
.text-edit-input:focus {
outline: none;
border-color: #007bff;
}
.input-group {
display: flex;
gap: 0.5rem;
}
.text-input {
flex: 1;
padding: 0.6rem 0.75rem;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 0.95rem;
}
.text-input:focus {
outline: none;
border-color: #007bff;
}
.error-message {
background: #ffebee;
color: #c62828;
padding: 0.6rem 0.75rem;
border-radius: 6px;
border: 1px solid #ffcdd2;
font-size: 0.9rem;
}
.text-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
border: 2px solid #e9ecef;
border-radius: 6px;
background: white;
transition: all 0.2s ease;
}
.text-item:hover {
border-color: #007bff;
background: #f0f8ff;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.control-group label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: #666;
}
.control-group input[type="range"] {
flex: 1;
cursor: pointer;
}
.control-group input[type="color"] {
width: 40px;
height: 40px;
border: none;
border-radius: 4px;
cursor: pointer;
padding: 0;
}
.value-display {
min-width: 40px;
text-align: right;
font-weight: 500;
color: #333;
}
</style>
+414
View File
@@ -0,0 +1,414 @@
<script lang="ts">
import Button from "$components/ui/Button.svelte";
import { TYPOGRAPHY } from "$lib/constants";
import type { TextAlign, TextItem } from "$lib/types/panel";
import { textSettingsStore, updateAllTextSettings } from "$stores/panelStore";
interface Props {
onTextAdd: (text: string, settings?: Partial<TextItem>) => void;
onTextUpdate: (id: string, text: string) => void;
onTextDelete: (id: string) => void;
texts: Array<{ id: string; text: string }>;
}
let { onTextAdd, onTextUpdate, onTextDelete, texts }: Props = $props();
let newText = $state("");
let errorMessage = $state<string | undefined>(undefined);
let commonTextSettings = $derived($textSettingsStore);
const availableFonts = TYPOGRAPHY.FONT_FAMILIES;
function handleAddText() {
if (!newText.trim()) {
errorMessage = "Введите текст";
return;
}
if (newText.length > TYPOGRAPHY.MAX_TEXT_LENGTH) {
errorMessage = `Текст не должен превышать ${TYPOGRAPHY.MAX_TEXT_LENGTH} символов`;
return;
}
try {
errorMessage = undefined;
onTextAdd(newText.trim(), commonTextSettings);
newText = "";
} catch (error) {
errorMessage = error instanceof Error ? error.message : "Ошибка добавления текста";
}
}
function handleKeyPress(e: KeyboardEvent) {
if (e.key === "Enter") {
handleAddText();
}
}
function handleUpdateText(id: string, newText: string) {
if (onTextUpdate && newText.trim()) {
onTextUpdate(id, newText.trim());
}
}
function handleDeleteText(id: string) {
if (onTextDelete) {
onTextDelete(id);
}
}
</script>
<div class="text-manager">
<div class="add-text-section">
<div class="input-group">
<input
type="text"
bind:value={newText}
placeholder="Введите текст для панели (например: links, about me, projects...)"
class="text-input"
maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}
onkeypress={handleKeyPress}
/>
<Button variant="primary" onclick={handleAddText}>Добавить</Button>
</div>
{#if errorMessage}
<div class="error-message">
{errorMessage}
</div>
{/if}
</div>
{#if texts.length > 0}
<div class="texts-list">
<h3>Созданные тексты ({texts.length})</h3>
<div class="texts-container">
{#each texts as textItem (textItem.id)}
<div class="text-item">
<input
type="text"
value={textItem.text}
oninput={(e) => handleUpdateText(textItem.id, e.currentTarget.value)}
class="text-edit-input"
maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}
/>
<Button variant="danger" size="sm" onclick={() => handleDeleteText(textItem.id)} aria-label="Удалить текст">
×
</Button>
</div>
{/each}
</div>
</div>
{/if}
<div class="common-settings-section">
<h3>Общие настройки текста</h3>
<p class="settings-note">Настройки применятся ко всем создаваемым панелям</p>
<div class="settings-grid">
<div class="control-group">
<label>
Размер шрифта:
<input
type="range"
min={TYPOGRAPHY.FONT_SIZE_MIN}
max={TYPOGRAPHY.FONT_SIZE_MAX}
step="1"
value={commonTextSettings.fontSize}
oninput={(e: Event) => {
updateAllTextSettings({ fontSize: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display">{commonTextSettings.fontSize}px</span>
</label>
</div>
<div class="control-group">
<label>
Шрифт:
<select
value={commonTextSettings.fontFamily}
onchange={(e) => {
updateAllTextSettings({ fontFamily: (e.target as HTMLSelectElement).value });
}}
>
{#each availableFonts as font}
<option value={font}>{font}</option>
{/each}
</select>
</label>
</div>
<div class="control-group">
<label>
Цвет:
<input
type="color"
value={commonTextSettings.color}
oninput={(e) => {
updateAllTextSettings({ color: (e.target as HTMLInputElement).value });
}}
/>
</label>
</div>
<div class="control-group">
<label>
Выравнивание:
<div class="alignment-buttons">
<button
class="align-btn {commonTextSettings.textAlign === 'left' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "left" as TextAlign });
}}
aria-label="Выровнять по левому краю"
>
</button>
<button
class="align-btn {commonTextSettings.textAlign === 'center' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "center" as TextAlign });
}}
aria-label="Выровнять по центру"
>
</button>
<button
class="align-btn {commonTextSettings.textAlign === 'right' ? 'active' : ''}"
onclick={() => {
updateAllTextSettings({ textAlign: "right" as TextAlign });
}}
aria-label="Выровнять по правому краю"
>
</button>
</div>
</label>
</div>
<div class="control-group">
<label>
Боковые отступы:
<input
type="range"
min="0"
max={TYPOGRAPHY.PADDING_X_MAX}
step="1"
value={commonTextSettings.paddingX}
oninput={(e) => {
updateAllTextSettings({ paddingX: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display">{commonTextSettings.paddingX}px</span>
</label>
</div>
<div class="control-group">
<label>
Смещение от центра:
<input
type="range"
min={TYPOGRAPHY.VERTICAL_OFFSET_MIN}
max={TYPOGRAPHY.VERTICAL_OFFSET_MAX}
step="1"
value={commonTextSettings.verticalOffset}
oninput={(e) => {
updateAllTextSettings({ verticalOffset: parseInt((e.target as HTMLInputElement).value) });
}}
/>
<span class="value-display"
>{(commonTextSettings.verticalOffset ?? 0) > 0 ? "+" : ""}{commonTextSettings.verticalOffset ?? 0}px</span
>
</label>
</div>
</div>
</div>
</div>
<style>
.text-manager {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.common-settings-section {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.common-settings-section h3 {
margin: 0 0 0.75rem 0;
color: #333;
font-size: 1rem;
font-weight: 500;
}
.settings-note {
margin: 0 0 1rem 0;
color: #666;
font-size: 0.875rem;
font-style: italic;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.alignment-buttons {
display: flex;
gap: 0.25rem;
}
.align-btn {
padding: 0.5rem 0.75rem;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 1rem;
}
.align-btn:hover {
background: #f0f0f0;
border-color: #007bff;
}
.align-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.add-text-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.texts-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.texts-list h3 {
margin: 0;
color: #333;
font-size: 1rem;
font-weight: 600;
}
.texts-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.text-item {
display: flex;
gap: 0.5rem;
align-items: center;
}
.text-edit-input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
}
.text-edit-input:focus {
outline: none;
border-color: #007bff;
}
.input-group {
display: flex;
gap: 0.5rem;
}
.text-input {
flex: 1;
padding: 0.6rem 0.75rem;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 0.95rem;
}
.text-input:focus {
outline: none;
border-color: #007bff;
}
.error-message {
background: #ffebee;
color: #c62828;
padding: 0.6rem 0.75rem;
border-radius: 6px;
border: 1px solid #ffcdd2;
font-size: 0.9rem;
}
.text-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
border: 2px solid #e9ecef;
border-radius: 6px;
background: white;
transition: all 0.2s ease;
}
.text-item:hover {
border-color: #007bff;
background: #f0f8ff;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.control-group label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: #666;
}
.control-group input[type="range"] {
flex: 1;
cursor: pointer;
}
.control-group input[type="color"] {
width: 40px;
height: 40px;
border: none;
border-radius: 4px;
cursor: pointer;
padding: 0;
}
.value-display {
min-width: 40px;
text-align: right;
font-weight: 500;
color: #333;
}
</style>
+23 -3
View File
@@ -7,7 +7,8 @@
import { panelService } from "../services/panelService";
import { textSettingsStore } from "../stores/panelStore";
import AppContainer from "../components/layout/AppContainer.svelte";
import PanelBar from "$components/layout/PanelBar.svelte";
import TextBar from "$components/layout/TextBar.svelte";
let uploadedImage = $state<string | undefined>(undefined);
let panels = $state<Panel[]>([]);
@@ -103,7 +104,12 @@
}
</script>
<AppContainer
<div class="main-grid">
<TextBar />
<PanelBar />
</div>
<!-- <AppContainer
errorMessage={exportService.getErrorMessage() || undefined}
{uploadedImage}
{texts}
@@ -118,4 +124,18 @@
onTextDelete={handleDeleteText}
onDownload={handleDownload}
onDownloadAll={handleDownloadAll}
/>
/> -->
<style>
.main-grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 1024px) {
.main-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
+25
View File
@@ -0,0 +1,25 @@
export interface TextsState {
texts: string[];
addText(text: string): void;
removeText(text: string): void;
}
const defaultTexts: string[] = ["About me", "Links", "Projects"];
function createTextsState(): TextsState {
let texts: string[] = $state(defaultTexts);
return {
get texts() {
return texts;
},
addText(text: string) {
texts = [...texts, text];
},
removeText(text: string) {
texts = texts.filter((t) => t !== text);
},
};
}
export const textsState = createTextsState();
+1
View File
@@ -15,6 +15,7 @@ const config = {
alias: {
$components: "src/components",
$stores: "src/stores",
$states: "src/states",
$services: "src/services",
},
},
+2 -1
View File
@@ -12,6 +12,7 @@
"strict": true,
"strictNullChecks": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"allowArbitraryExtensions": true
}
}