refactor: change null to undefined

This commit is contained in:
2026-02-04 12:50:38 +05:00
parent 2797fea079
commit dfa6c40743
21 changed files with 89 additions and 112 deletions
+8 -4
View File
@@ -40,6 +40,7 @@ interface ImageUploadProps {
- **Error Handling**: Invalid crop areas ✅
**Implementation Details:**
- Integrated cropperjs v2.1.0 with Web Components
- Used `$toCanvas()` method to get HTMLCanvasElement
- Implemented proper error handling with user-friendly messages
@@ -147,7 +148,7 @@ interface Panel {
}
interface PanelManagerProps {
currentPanel: Panel | null;
currentPanel: Panel | undefined;
onPanelUpdate: (panel: Panel) => void;
}
@@ -255,11 +256,11 @@ interface ImageUploadResult {
```typescript
// Core stores
export const panelStore = writable<Panel | null>(null);
export const panelStore = writable<Panel | undefined>(undefined);
export const uiStore = writable({
isLoading: false,
error: null as string | null,
currentStep: "upload" as "upload" | "crop" | "text" | "preview",
error: string | undefined,
currentStep: "upload" | "crop" | "text" | "preview",
});
```
@@ -359,11 +360,13 @@ _Estimated Duration: 5 weeks_
**Challenge:** cropperjs v2.x uses Web Components API which differs significantly from v1.x
**Solution:**
1. Used `getCropperCanvas()` to get `<cropper-canvas>` Web Component
2. Called `$toCanvas()` method to convert to HTMLCanvasElement
3. Applied `toDataURL()` on the resulting canvas to get base64 image
**Code Example:**
```typescript
const cropperCanvasElement = cropper.getCropperCanvas();
const canvas = await cropperCanvasElement.$toCanvas({
@@ -375,6 +378,7 @@ const croppedImage = canvas.toDataURL("image/png");
```
**Key Points:**
- cropperjs v2.x methods return Web Components, not standard DOM elements
- `$toCanvas()` is an async method that returns HTMLCanvasElement
- Proper error handling is essential for user experience
+14 -14
View File
@@ -4,10 +4,10 @@
import AppContent from "./AppContent.svelte";
interface Props {
errorMessage: string | null;
uploadedImage: string | null;
errorMessage: string | undefined;
uploadedImage: string | undefined;
texts: Array<{ id: string; text: string }>;
backgroundImage: string | null;
backgroundImage: string | undefined;
panels: any[];
onUploadNewImage: () => void;
onImageUpload: (image: string) => void;
@@ -34,12 +34,12 @@
onTextUpdate,
onTextDelete,
onDownload,
onDownloadAll
onDownloadAll,
}: Props = $props();
</script>
<div class="app-container">
<AppHeader onUploadNewImage={onUploadNewImage} />
<AppHeader {onUploadNewImage} />
<ErrorMessage {errorMessage} />
@@ -48,15 +48,15 @@
{texts}
{backgroundImage}
{panels}
onImageUpload={onImageUpload}
onCropComplete={onCropComplete}
onCropCancel={onCropCancel}
onTextAdd={onTextAdd}
onTextUpdate={onTextUpdate}
onTextDelete={onTextDelete}
onUploadNewImage={onUploadNewImage}
onDownload={onDownload}
onDownloadAll={onDownloadAll}
{onImageUpload}
{onCropComplete}
{onCropCancel}
{onTextAdd}
{onTextUpdate}
{onTextDelete}
{onUploadNewImage}
{onDownload}
{onDownloadAll}
/>
</div>
+10 -16
View File
@@ -3,9 +3,9 @@
import Sidebar from "./Sidebar.svelte";
interface Props {
uploadedImage: string | null;
uploadedImage: string | undefined;
texts: Array<{ id: string; text: string }>;
backgroundImage: string | null;
backgroundImage: string | undefined;
panels: any[];
onImageUpload: (image: string) => void;
onCropComplete: (croppedImage: string) => void;
@@ -31,7 +31,7 @@
onTextDelete,
onUploadNewImage,
onDownload,
onDownloadAll
onDownloadAll,
}: Props = $props();
</script>
@@ -39,20 +39,14 @@
<MainSection
{uploadedImage}
{texts}
onImageUpload={onImageUpload}
onCropComplete={onCropComplete}
onCropCancel={onCropCancel}
onTextAdd={onTextAdd}
onTextUpdate={onTextUpdate}
onTextDelete={onTextDelete}
/>
<Sidebar
{backgroundImage}
{panels}
onUploadNewImage={onUploadNewImage}
onDownload={onDownload}
onDownloadAll={onDownloadAll}
{onImageUpload}
{onCropComplete}
{onCropCancel}
{onTextAdd}
{onTextUpdate}
{onTextDelete}
/>
<Sidebar {backgroundImage} {panels} {onUploadNewImage} {onDownload} {onDownloadAll} />
</div>
<style>
+1 -1
View File
@@ -2,7 +2,7 @@
import { Button } from "../lib/components/ui";
interface Props {
backgroundImage?: string | null;
backgroundImage?: string | undefined;
onUploadNewImage: () => void;
}
+4 -4
View File
@@ -1,14 +1,14 @@
<script lang="ts">
interface Props {
message: string | null;
errorMessage: string | undefined;
}
let { message }: Props = $props();
let { errorMessage }: Props = $props();
</script>
{#if message}
{#if errorMessage}
<div class="error-message">
{message}
{errorMessage}
</div>
{/if}
+3 -3
View File
@@ -15,9 +15,9 @@
let { imageSrc, onCropComplete, onCancel }: Props = $props();
let imageElement: HTMLImageElement;
let cropper: CropperJS | null = null;
let cropper: CropperJS | undefined = undefined;
let isProcessing = $state(false);
let errorMessage = $state<string | null>(null);
let errorMessage = $state<string | undefined>(undefined);
let imageService = new ImageService();
@@ -46,7 +46,7 @@
try {
isProcessing = true;
errorMessage = null;
errorMessage = undefined;
setLoading(true);
console.log("Calling getCropperCanvas");
+1 -1
View File
@@ -4,7 +4,7 @@
interface Props {
currentStep: string;
uploadedImage: string | null;
uploadedImage: string | undefined;
onImageUpload: (image: string) => void;
onCropComplete: (croppedImage: string) => void;
onCropCancel: () => void;
+8 -8
View File
@@ -15,15 +15,15 @@
let { onImageSelect }: Props = $props();
let dropZone = $state<HTMLElement | null>(null);
let fileInput = $state<HTMLInputElement | null>(null);
let urlInput = $state<HTMLInputElement | null>(null);
let urlForm = $state<HTMLFormElement | null>(null);
let dropZone = $state<HTMLElement | undefined>(undefined);
let fileInput = $state<HTMLInputElement | undefined>(undefined);
let urlInput = $state<HTMLInputElement | undefined>(undefined);
let urlForm = $state<HTMLFormElement | undefined>(undefined);
let isDragOver = $state(false);
let showUrlInput = $state(false);
let uploadedImage = $state<string | null>(null);
let errorMessage = $state<string | null>(null);
let uploadedImage = $state<string | undefined>(undefined);
let errorMessage = $state<string | undefined>(undefined);
const uiError = $derived($uiStore.error?.error);
@@ -148,8 +148,8 @@
}
function resetUpload() {
uploadedImage = null;
errorMessage = null;
uploadedImage = undefined;
errorMessage = undefined;
showUrlInput = false;
setCurrentStep("upload");
}
+4 -15
View File
@@ -4,7 +4,7 @@
import TextSection from "./TextSection.svelte";
interface Props {
uploadedImage: string | null;
uploadedImage: string | undefined;
texts: Array<{ id: string; text: string }>;
onImageUpload: (image: string) => void;
onCropComplete: (croppedImage: string) => void;
@@ -22,25 +22,14 @@
onCropCancel,
onTextAdd,
onTextUpdate,
onTextDelete
onTextDelete,
}: Props = $props();
</script>
<div class="main-section">
<ImageManager
currentStep={$uiStore.currentStep}
{uploadedImage}
onImageUpload={onImageUpload}
onCropComplete={onCropComplete}
onCropCancel={onCropCancel}
/>
<ImageManager currentStep={$uiStore.currentStep} {uploadedImage} {onImageUpload} {onCropComplete} {onCropCancel} />
{#if $uiStore.currentStep === "text"}
<TextSection
{texts}
onTextAdd={onTextAdd}
onTextUpdate={onTextUpdate}
onTextDelete={onTextDelete}
/>
<TextSection {texts} {onTextAdd} {onTextUpdate} {onTextDelete} />
{/if}
</div>
+5 -5
View File
@@ -12,8 +12,8 @@
let { onPanelSelect, onPanelDelete }: Props = $props();
let panels = $state<Panel[]>([]);
let selectedPanelId = $state<string | null>(null);
let errorMessage = $state<string | null>(null);
let selectedPanelId = $state<string | undefined>(undefined);
let errorMessage = $state<string | undefined>(undefined);
onMount(() => {
loadPanels();
@@ -21,7 +21,7 @@
function loadPanels() {
try {
errorMessage = null;
errorMessage = undefined;
panels = panelStorage.getAllPanels();
// Если есть текущая панель в store, отмечаем её как выбранную
@@ -41,7 +41,7 @@
function handlePanelDelete(panelId: string) {
try {
errorMessage = null;
errorMessage = undefined;
const result = panelStorage.deletePanel(panelId);
if (result.success) {
@@ -49,7 +49,7 @@
// Если удалили выбранную панель, сбрасываем выбор
if (selectedPanelId === panelId) {
selectedPanelId = null;
selectedPanelId = undefined;
}
// Уведомляем родительский компонент
+4 -17
View File
@@ -4,33 +4,20 @@
import PanelsList from "./PanelsList.svelte";
interface Props {
backgroundImage: string | null;
backgroundImage: string | undefined;
panels: any[];
onUploadNewImage: () => void;
onDownload: (panel: any) => Promise<void>;
onDownloadAll: () => Promise<void>;
}
let {
backgroundImage,
panels,
onUploadNewImage,
onDownload,
onDownloadAll
}: Props = $props();
let { backgroundImage, panels, onUploadNewImage, onDownload, onDownloadAll }: Props = $props();
</script>
<div class="sidebar">
{#if $uiStore.currentStep === "text"}
<BackgroundPreview
{backgroundImage}
onUploadNewImage={onUploadNewImage}
/>
<PanelsList
{panels}
onDownload={onDownload}
onDownloadAll={onDownloadAll}
/>
<BackgroundPreview {backgroundImage} {onUploadNewImage} />
<PanelsList {panels} {onDownload} {onDownloadAll} />
{/if}
</div>
+3 -3
View File
@@ -11,14 +11,14 @@
let { onTextAdd, onTextUpdate, onTextDelete, texts }: Props = $props();
let newText = $state("");
let errorMessage = $state<string | null>(null);
let errorMessage = $state<string | undefined>(undefined);
// Общие настройки текста для всех панелей
let commonTextSettings = $state({
fontSize: 18,
fontFamily: "Arial",
color: "#ffffff",
textAlign: "center" as "left" | "center" | "right",
textAlign: "left" | "center" | "right",
paddingX: 10,
verticalOffset: 0,
});
@@ -47,7 +47,7 @@
}
try {
errorMessage = null;
errorMessage = undefined;
onTextAdd(newText.trim());
newText = "";
} catch (error) {
+2 -1
View File
@@ -87,7 +87,7 @@ export class ImageService {
}
// Find image item
let imageItem: DataTransferItem | null = null;
let imageItem: DataTransferItem | undefined = undefined;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
imageItem = items[i];
@@ -184,6 +184,7 @@ export class ImageService {
private fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
// TODO: look for a better way to handle errors non strings
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(new ImageError("Ошибка чтения файла"));
reader.readAsDataURL(file);
+1 -1
View File
@@ -42,7 +42,7 @@ export interface ImageCropResult {
export interface UIState {
isLoading: boolean;
error: string | null;
error: string | undefined;
currentStep: "upload" | "crop" | "text" | "preview";
showCropModal: boolean;
showTextManager: boolean;
+3 -4
View File
@@ -1,4 +1,3 @@
import type { Panel } from "../types/panel";
import { ImageError } from "../types/errors";
import { handleError, logError } from "./errorHandler";
@@ -72,13 +71,13 @@ export class PanelStorage {
/**
* Загружает панель по ID
*/
getPanelById(id: string): Panel | null {
getPanelById(id: string): Panel | undefined {
try {
const panels = this.getAllPanels();
return panels.find((p) => p.id === id) || null;
return panels.find((p) => p.id === id) || undefined;
} catch (error) {
logError(error, "Failed to get panel by id");
return null;
return undefined;
}
}
+4 -4
View File
@@ -8,10 +8,10 @@
import AppContainer from "../components/AppContainer.svelte";
let uploadedImage = $state<string | null>(null);
let uploadedImage = $state<string | undefined>(undefined);
let panels = $state<Panel[]>([]);
let texts = $state<Array<{ id: string; text: string }>>([]);
let backgroundImage = $state<string | null>(null);
let backgroundImage = $state<string | undefined>(undefined);
onMount(async () => {
try {
@@ -32,14 +32,14 @@
function handleCropComplete(croppedImage: string) {
backgroundImage = croppedImage;
uploadedImage = null;
uploadedImage = undefined;
imageService.handleCropComplete(croppedImage);
panels = panelService.updatePanelsBackground(panels, croppedImage);
panels = panelService.updatePanelsFromTexts(texts, panels, croppedImage);
}
function handleCropCancel() {
uploadedImage = null;
uploadedImage = undefined;
imageService.handleCropCancel();
}
+3 -3
View File
@@ -4,7 +4,7 @@ import { setLoading } from "../stores/uiStore";
export class ExportService {
private static instance: ExportService;
private errorMessage: string | null = null;
private errorMessage: string | undefined = undefined;
private constructor() {}
@@ -15,11 +15,11 @@ export class ExportService {
return ExportService.instance;
}
getErrorMessage(): string | null {
getErrorMessage(): string | undefined {
return this.errorMessage;
}
setErrorMessage(message: string | null): void {
setErrorMessage(message: string | undefined): void {
this.errorMessage = message;
}
+1
View File
@@ -19,6 +19,7 @@ export class ImageService {
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
// TODO: look for a better handle non string
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(blob);
+3 -3
View File
@@ -2,7 +2,7 @@ import { writable, type Writable } from "svelte/store";
import { type Panel } from "../lib/types/panel";
import { v4 as uuidv4 } from "uuid";
export const panelStore: Writable<Panel | null> = writable(null);
export const panelStore: Writable<Panel | undefined> = writable(undefined);
export const createEmptyPanel = (height: number = 100): Panel => {
return {
@@ -30,7 +30,7 @@ export const addTextToPanel = (panel: Panel, text: string): Panel => {
fontSize: 18,
fontFamily: "Arial",
color: "#ffffff",
textAlign: "center" as const,
textAlign: "center",
paddingX: 10,
verticalOffset: 0,
};
@@ -58,7 +58,7 @@ export const createPanelFromText = (backgroundImage: string, text: string, heigh
fontSize: 18,
fontFamily: "Arial",
color: "#ffffff",
textAlign: "center" as const,
textAlign: "center",
paddingX: 10,
verticalOffset: 0,
};
+4 -4
View File
@@ -3,7 +3,7 @@ import { type UIState } from "../lib/types/panel";
export const uiStore: Writable<UIState> = writable({
isLoading: false,
error: null,
error: undefined,
currentStep: "text",
showCropModal: false,
showTextManager: false,
@@ -13,12 +13,12 @@ export const setLoading = (loading: boolean): void => {
uiStore.update((state) => ({ ...state, isLoading: loading }));
};
export const setError = (error: string | null): void => {
export const setError = (error: string | undefined): void => {
uiStore.update((state) => ({ ...state, error }));
};
export const clearError = (): void => {
setError(null);
setError(undefined);
};
export const setCurrentStep = (step: UIState["currentStep"]): void => {
@@ -36,7 +36,7 @@ export const showTextManager = (show: boolean): void => {
export const resetUI = (): void => {
uiStore.set({
isLoading: false,
error: null,
error: undefined,
currentStep: "text",
showCropModal: false,
showTextManager: false,
+3 -1
View File
@@ -10,6 +10,8 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"strictNullChecks": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
@@ -17,4 +19,4 @@
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}
}