feat: start implement panels rendering, remove unused files
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
import { building } from "$app/environment";
|
||||
import { readdirSync } from "node:fs";
|
||||
import { join } from "path";
|
||||
|
||||
export function loadFonts() {
|
||||
try {
|
||||
const fontsDir = join(process.cwd(), "static", "fonts");
|
||||
const files = readdirSync(fontsDir);
|
||||
|
||||
let fonts = files
|
||||
.filter((file) => /\.(woff2|woff|ttf|otf)$/i.test(file))
|
||||
.map((file) => ({
|
||||
name: file.replace(/\.[^/.]+$/, ""),
|
||||
file,
|
||||
url: `/fonts/${file}`,
|
||||
format: getFormat(file),
|
||||
}));
|
||||
|
||||
return fonts;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getFormat(filename: string) {
|
||||
if (filename.endsWith(".woff2")) return "woff2";
|
||||
if (filename.endsWith(".woff")) return "woff";
|
||||
if (filename.endsWith(".ttf")) return "truetype";
|
||||
return "opentype";
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { building } from "$app/environment";
|
||||
import { readdirSync } from "node:fs";
|
||||
import { join } from "path";
|
||||
|
||||
export function loadImages() {
|
||||
try {
|
||||
const imagesDir = join(process.cwd(), "static", "backgrounds");
|
||||
const files = readdirSync(imagesDir);
|
||||
|
||||
let fonts = files.filter((file) => /\.(png|jpg)$/i.test(file));
|
||||
|
||||
return fonts;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export class AppError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code: string,
|
||||
public recoverable: boolean = true,
|
||||
public details?: unknown,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "AppError";
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageError extends AppError {
|
||||
constructor(message: string, recoverable: boolean = true) {
|
||||
super(message, "IMAGE_ERROR", recoverable);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextError extends AppError {
|
||||
constructor(message: string, recoverable: boolean = true) {
|
||||
super(message, "TEXT_ERROR", recoverable);
|
||||
}
|
||||
}
|
||||
|
||||
export class CanvasError extends AppError {
|
||||
constructor(message: string, recoverable: boolean = true) {
|
||||
super(message, "CANVAS_ERROR", recoverable);
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageError extends AppError {
|
||||
constructor(message: string, recoverable: boolean = true) {
|
||||
super(message, "STORAGE_ERROR", recoverable);
|
||||
}
|
||||
}
|
||||
|
||||
export type ErrorType = AppError | ImageError | TextError | CanvasError | StorageError;
|
||||
@@ -1,63 +0,0 @@
|
||||
import { ERROR_HANDLING } from "../constants";
|
||||
import { AppError } from "../types/errors";
|
||||
|
||||
export function handleError(error: unknown, defaultMessage: string = "Произошла ошибка"): string {
|
||||
if (error instanceof AppError) {
|
||||
return error.recoverable ? `Ошибка: ${error.message}. Попробуйте снова.` : `Критическая ошибка: ${error.message}`;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return `${defaultMessage}: ${error.message}`;
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return `${defaultMessage}: ${error}`;
|
||||
}
|
||||
|
||||
return `${defaultMessage}: Произошла неизвестная ошибка`;
|
||||
}
|
||||
|
||||
export function isRecoverableError(error: unknown): boolean {
|
||||
if (error instanceof AppError) {
|
||||
return error.recoverable;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createError(message: string, code: string, recoverable: boolean = true, details?: unknown): AppError {
|
||||
return new AppError(message, code, recoverable, details);
|
||||
}
|
||||
|
||||
export function logError(error: unknown, context?: string): void {
|
||||
console.error("Error occurred:", {
|
||||
error,
|
||||
context,
|
||||
timestamp: new Date().toISOString(),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function retryOperation<T>(
|
||||
operation: () => Promise<T>,
|
||||
maxRetries: number = ERROR_HANDLING.MAX_RETRIES,
|
||||
delayMs: number = ERROR_HANDLING.RETRY_DELAY_MS,
|
||||
): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let attempt = 0;
|
||||
|
||||
const attemptOperation = () => {
|
||||
attempt++;
|
||||
operation()
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (attempt >= maxRetries) {
|
||||
reject(error);
|
||||
} else {
|
||||
setTimeout(attemptOperation, delayMs * attempt);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
attemptOperation();
|
||||
});
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { IMAGE_SETTINGS } from "../constants";
|
||||
import { ImageError } from "../types/errors";
|
||||
|
||||
export const MAX_FILE_SIZE = IMAGE_SETTINGS.MAX_FILE_SIZE;
|
||||
export const SUPPORTED_FORMATS = IMAGE_SETTINGS.SUPPORTED_FORMATS;
|
||||
|
||||
export type ValidationResult =
|
||||
| {
|
||||
isValid: true;
|
||||
}
|
||||
| {
|
||||
isValid: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export function validateFileSize(file: File): ValidationResult {
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Файл слишком большой. Максимальный размер: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
||||
};
|
||||
}
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
export function validateFileType(file: File): ValidationResult {
|
||||
if (!SUPPORTED_FORMATS.includes(file.type as any)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Неподдерживаемый формат. Допустимые форматы: ${SUPPORTED_FORMATS.join(", ")}`,
|
||||
};
|
||||
}
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
export function validateImageFile(file: File): ValidationResult {
|
||||
const sizeValidation = validateFileSize(file);
|
||||
if (!sizeValidation.isValid) {
|
||||
return sizeValidation;
|
||||
}
|
||||
|
||||
const typeValidation = validateFileType(file);
|
||||
if (!typeValidation.isValid) {
|
||||
return typeValidation;
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
export function validateImageUrl(url: string): ValidationResult {
|
||||
try {
|
||||
new URL(url);
|
||||
return { isValid: true };
|
||||
} catch {
|
||||
return {
|
||||
isValid: false,
|
||||
error: "Неверный URL формат",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function validateImageElement(img: HTMLImageElement): ValidationResult {
|
||||
if (!img.naturalWidth || !img.naturalHeight) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: "Изображение не может быть загружено или повреждено",
|
||||
};
|
||||
}
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
export async function loadImage(src: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = document.createElement("img");
|
||||
img.crossOrigin = "anonymous";
|
||||
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject(new ImageError("Не удалось загрузить изображение"));
|
||||
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
export function getImageDimensions(img: HTMLImageElement): { width: number; height: number } {
|
||||
return {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateAspectRatioFit(
|
||||
srcWidth: number,
|
||||
srcHeight: number,
|
||||
maxWidth: number,
|
||||
maxHeight: number,
|
||||
): { width: number; height: number } {
|
||||
const ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
|
||||
return {
|
||||
width: srcWidth * ratio,
|
||||
height: srcHeight * ratio,
|
||||
};
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import { PANEL_SETTINGS } from "../constants";
|
||||
import type { Panel } from "../types/panel";
|
||||
import { handleError, logError } from "./errorHandler";
|
||||
|
||||
const STORAGE_KEY = PANEL_SETTINGS.STORAGE_KEY;
|
||||
const MAX_PANELS = PANEL_SETTINGS.MAX_PANELS_COUNT;
|
||||
|
||||
export interface StorageResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class PanelStorage {
|
||||
savePanel(panel: Panel): StorageResult {
|
||||
try {
|
||||
const panels = this.getAllPanels();
|
||||
|
||||
const existingIndex = panels.findIndex((p) => p.id === panel.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
panels[existingIndex] = panel;
|
||||
} else {
|
||||
panels.unshift(panel);
|
||||
|
||||
if (panels.length > MAX_PANELS) {
|
||||
panels.length = MAX_PANELS;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(panels));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
logError(error, "Failed to save panel");
|
||||
return {
|
||||
success: false,
|
||||
error: handleError(error, "Ошибка сохранения панели"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getAllPanels(): Panel[] {
|
||||
try {
|
||||
const data = localStorage.getItem(STORAGE_KEY);
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const panels: Panel[] = JSON.parse(data);
|
||||
|
||||
// Convert date strings back to Date objects and validate
|
||||
return panels
|
||||
.map((panel) => ({
|
||||
...panel,
|
||||
createdAt: new Date(panel.createdAt),
|
||||
updatedAt: new Date(panel.updatedAt),
|
||||
}))
|
||||
.filter((panel) => this.validatePanel(panel));
|
||||
} catch (error) {
|
||||
logError(error, "Failed to load panels");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getPanelById(id: string): Panel | undefined {
|
||||
try {
|
||||
const panels = this.getAllPanels();
|
||||
return panels.find((p) => p.id === id) || undefined;
|
||||
} catch (error) {
|
||||
logError(error, "Failed to get panel by id");
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
deletePanel(id: string): StorageResult {
|
||||
try {
|
||||
const panels = this.getAllPanels();
|
||||
const filteredPanels = panels.filter((p) => p.id !== id);
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(filteredPanels));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
logError(error, "Failed to delete panel");
|
||||
return {
|
||||
success: false,
|
||||
error: handleError(error, "Ошибка удаления панели"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
clearAll(): StorageResult {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
logError(error, "Failed to clear panels");
|
||||
return {
|
||||
success: false,
|
||||
error: handleError(error, "Ошибка очистки хранилища"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private validatePanel(panel: any): panel is Panel {
|
||||
return (
|
||||
typeof panel === "object" &&
|
||||
typeof panel.id === "string" &&
|
||||
typeof panel.backgroundImage === "string" &&
|
||||
typeof panel.text === "object" &&
|
||||
typeof panel.text?.id === "string" &&
|
||||
typeof panel.text?.text === "string" &&
|
||||
typeof panel.height === "number" &&
|
||||
panel.height > 0 &&
|
||||
panel.height <= PANEL_SETTINGS.PANEL_HEIGHT_MAX &&
|
||||
panel.createdAt instanceof Date &&
|
||||
panel.updatedAt instanceof Date
|
||||
);
|
||||
}
|
||||
|
||||
getPanelCount(): number {
|
||||
return this.getAllPanels().length;
|
||||
}
|
||||
|
||||
hasSpaceForNewPanel(): boolean {
|
||||
return this.getPanelCount() < MAX_PANELS;
|
||||
}
|
||||
}
|
||||
|
||||
export const panelStorage = new PanelStorage();
|
||||
Reference in New Issue
Block a user