Files
tablejerseys-web/app/components/designer/DesignerToolbar.vue
Frank John Begornia 3ba0b250ed
Some checks failed
Deploy Production / deploy (push) Has been cancelled
first commit
2026-01-12 22:16:36 +08:00

243 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, ref, watch } from "vue";
const props = defineProps<{
onAddText: () => void;
onAddCircle: () => void;
onAddRectangle: () => void;
onClear: () => void;
onImportImage: (file: File) => Promise<void>;
onFillChange: (fill: string) => void;
onStrokeChange: (stroke: string) => void;
onBackgroundChange: (background: string) => void;
activeFill: string | null;
activeStroke: string | null;
activeBackground: string;
canStyleSelection: boolean;
zoom: number;
minZoom: number;
maxZoom: number;
onZoomChange: (zoom: number) => void;
onZoomIn: () => void;
onZoomOut: () => void;
onZoomReset: () => void;
}>();
const emit = defineEmits<{
(e: "request-image"): void;
}>();
const fileInput = ref<HTMLInputElement | null>(null);
const fillValue = ref(props.activeFill ?? "#111827");
const strokeValue = ref(props.activeStroke ?? "#3b82f6");
const backgroundValue = ref(props.activeBackground ?? "#ffffff");
const zoomSliderValue = ref(Math.round(props.zoom * 100));
watch(
() => props.activeFill,
(next) => {
fillValue.value = next ?? "#111827";
}
);
watch(
() => props.activeStroke,
(next) => {
strokeValue.value = next ?? "#3b82f6";
}
);
watch(
() => props.activeBackground,
(next) => {
backgroundValue.value = next ?? "#ffffff";
}
);
watch(
() => props.zoom,
(next) => {
zoomSliderValue.value = Math.round(next * 100);
}
);
const openFilePicker = () => {
if (!fileInput.value) {
return;
}
fileInput.value.value = "";
fileInput.value.click();
};
const handleFileChange = async (event: Event) => {
const input = event.target as HTMLInputElement;
const files = input.files;
if (!files || !files.length) {
return;
}
const [file] = files;
if (!file) {
return;
}
await props.onImportImage(file);
};
const stylingDisabled = computed(() => !props.canStyleSelection);
const handleFillChange = (event: Event) => {
const input = event.target as HTMLInputElement;
const value = input.value;
fillValue.value = value;
props.onFillChange(value);
};
const handleStrokeChange = (event: Event) => {
const input = event.target as HTMLInputElement;
const value = input.value;
strokeValue.value = value;
props.onStrokeChange(value);
};
const handleBackgroundChange = (event: Event) => {
const input = event.target as HTMLInputElement;
const value = input.value;
backgroundValue.value = value;
props.onBackgroundChange(value);
};
const handleZoomInput = (event: Event) => {
const input = event.target as HTMLInputElement;
const value = Number(input.value);
if (Number.isNaN(value)) {
return;
}
zoomSliderValue.value = value;
props.onZoomChange(value / 100);
};
const zoomLabel = computed(() => `${Math.round(props.zoom * 100)}%`);
</script>
<template>
<div class="flex flex-wrap items-center gap-2 border-b border-slate-200 bg-slate-50 px-4 py-3">
<!-- Add Elements Group -->
<div class="flex items-center gap-1 border-r border-slate-200 pr-3">
<button
type="button"
class="flex h-9 w-9 items-center justify-center rounded-lg text-lg font-bold text-slate-700 transition hover:bg-slate-200"
title="Add Text"
@click="props.onAddText"
>
T
</button>
<button
type="button"
class="flex h-9 w-9 items-center justify-center rounded-lg text-xl font-bold text-slate-700 transition hover:bg-slate-200"
title="Add Circle"
@click="props.onAddCircle"
>
</button>
<button
type="button"
class="flex h-9 w-9 items-center justify-center rounded-lg text-xl font-bold text-slate-700 transition hover:bg-slate-200"
title="Add Rectangle"
@click="props.onAddRectangle"
>
</button>
<button
type="button"
class="flex h-9 w-9 items-center justify-center rounded-lg text-xl font-bold text-slate-700 transition hover:bg-slate-200"
title="Upload Image"
@click="openFilePicker"
>
🖼
</button>
</div>
<!-- Color Pickers Group -->
<div class="flex items-center gap-2 border-r border-slate-200 pr-3">
<label class="flex items-center gap-1.5" title="Canvas Background">
<span class="text-xs text-slate-600">Canvas</span>
<input
type="color"
class="h-8 w-12 cursor-pointer rounded border border-slate-300 bg-white p-0.5"
:value="backgroundValue"
@input="handleBackgroundChange"
/>
</label>
<label class="flex items-center gap-1.5" title="Fill Color">
<span class="text-xs text-slate-600">Fill</span>
<input
type="color"
class="h-8 w-12 cursor-pointer rounded border border-slate-300 bg-white p-0.5"
:disabled="stylingDisabled"
:value="fillValue"
@input="handleFillChange"
/>
</label>
<label class="flex items-center gap-1.5" title="Stroke Color">
<span class="text-xs text-slate-600">Stroke</span>
<input
type="color"
class="h-8 w-12 cursor-pointer rounded border border-slate-300 bg-white p-0.5"
:disabled="stylingDisabled"
:value="strokeValue"
@input="handleStrokeChange"
/>
</label>
</div>
<!-- Zoom Controls -->
<div class="flex items-center gap-2 border-r border-slate-200 pr-3">
<button
type="button"
class="flex h-8 w-8 items-center justify-center rounded border border-slate-300 bg-white text-sm font-bold text-slate-900 transition hover:bg-slate-50"
title="Zoom Out"
@click="props.onZoomOut"
>
</button>
<span class="min-w-12 text-center text-xs font-medium text-slate-700">
{{ zoomLabel }}
</span>
<button
type="button"
class="flex h-8 w-8 items-center justify-center rounded border border-slate-300 bg-white text-sm font-bold text-slate-900 transition hover:bg-slate-50"
title="Zoom In"
@click="props.onZoomIn"
>
+
</button>
<button
type="button"
class="rounded border border-slate-300 bg-white px-2 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-50"
title="Reset Zoom"
@click="props.onZoomReset"
>
Reset
</button>
</div>
<!-- Clear Button -->
<button
type="button"
class="ml-auto rounded border border-rose-300 bg-rose-50 px-3 py-1.5 text-xs font-medium text-rose-700 transition hover:bg-rose-100"
@click="props.onClear"
>
Clear Canvas
</button>
<!-- Hidden File Input -->
<input
ref="fileInput"
type="file"
accept="image/png,image/jpeg,image/webp,image/svg+xml"
class="hidden"
@change="handleFileChange"
/>
</div>
</template>