Some checks failed
Deploy Production / deploy (push) Failing after 1m11s
- Added canvasId prop to DesignerCanvas for identifying multiple canvases. - Implemented active view selection (front, top, left, right) in designer page. - Updated DesignerCanvas to maintain aspect ratio and dimensions based on view. - Integrated @google/model-viewer for 3D model rendering on the index page. - Refactored useSlipmatDesigner to manage multiple canvases and their states. - Added LAMESA.glb model file for 3D representation. - Updated package.json and package-lock.json to include @google/model-viewer dependency.
160 lines
4.1 KiB
Vue
160 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
|
|
import type { Canvas as FabricCanvas } from "fabric";
|
|
|
|
const props = defineProps<{
|
|
canvasId?: string;
|
|
size: number;
|
|
registerCanvas: (payload: {
|
|
canvas: FabricCanvas;
|
|
fabric: typeof import("fabric");
|
|
canvasId?: string;
|
|
}) => void;
|
|
unregisterCanvas: (canvasId?: string) => void;
|
|
backgroundColor: string;
|
|
}>();
|
|
|
|
const canvasElement = ref<HTMLCanvasElement | null>(null);
|
|
const containerElement = ref<HTMLDivElement | null>(null);
|
|
const isReady = ref(false);
|
|
|
|
let fabricCanvas: FabricCanvas | null = null;
|
|
let fabricModule: typeof import("fabric") | null = null;
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
const syncCanvasDomStyles = () => {
|
|
if (!fabricCanvas) {
|
|
return;
|
|
}
|
|
const canvases = [fabricCanvas.lowerCanvasEl, fabricCanvas.upperCanvasEl];
|
|
canvases.forEach((element) => {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
element.classList.add("rounded-lg");
|
|
element.style.borderRadius = "8px";
|
|
element.style.backgroundColor = "transparent";
|
|
});
|
|
};
|
|
|
|
const updateCssDimensions = (containerWidth?: number) => {
|
|
if (!fabricCanvas) {
|
|
return;
|
|
}
|
|
const targetWidth =
|
|
containerWidth ??
|
|
containerElement.value?.clientWidth ??
|
|
null;
|
|
if (!targetWidth) {
|
|
return;
|
|
}
|
|
const targetHeight = Math.round(targetWidth * 0.67); // 3:2 aspect ratio
|
|
fabricCanvas.setDimensions(
|
|
{
|
|
width: targetWidth,
|
|
height: targetHeight,
|
|
},
|
|
{ cssOnly: true }
|
|
);
|
|
fabricCanvas.calcOffset();
|
|
fabricCanvas.requestRenderAll();
|
|
syncCanvasDomStyles();
|
|
};
|
|
|
|
const observeContainer = () => {
|
|
if (!containerElement.value || !fabricCanvas) {
|
|
return;
|
|
}
|
|
resizeObserver?.disconnect();
|
|
resizeObserver = new ResizeObserver((entries) => {
|
|
const entry = entries[0];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
const containerWidth = entry.contentRect.width;
|
|
updateCssDimensions(containerWidth);
|
|
});
|
|
resizeObserver.observe(containerElement.value);
|
|
updateCssDimensions();
|
|
};
|
|
|
|
const setupCanvas = async () => {
|
|
if (typeof window === "undefined" || !canvasElement.value) {
|
|
return;
|
|
}
|
|
|
|
fabricModule = await import("fabric");
|
|
|
|
const { Canvas } = fabricModule;
|
|
fabricCanvas = new Canvas(canvasElement.value, {
|
|
backgroundColor: "transparent",
|
|
selection: true,
|
|
preserveObjectStacking: true,
|
|
});
|
|
|
|
const canvasHeight = Math.round(props.size * 0.67); // 3:2 aspect ratio
|
|
fabricCanvas.setDimensions({ width: props.size, height: canvasHeight });
|
|
|
|
props.registerCanvas({
|
|
canvas: fabricCanvas,
|
|
fabric: fabricModule,
|
|
canvasId: props.canvasId
|
|
});
|
|
observeContainer();
|
|
syncCanvasDomStyles();
|
|
isReady.value = true;
|
|
};
|
|
|
|
onMounted(() => {
|
|
setupCanvas();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
props.unregisterCanvas(props.canvasId);
|
|
resizeObserver?.disconnect();
|
|
resizeObserver = null;
|
|
fabricCanvas = null;
|
|
fabricModule = null;
|
|
isReady.value = false;
|
|
});
|
|
|
|
watch(
|
|
() => props.size,
|
|
(next, prev) => {
|
|
if (!isReady.value || next === prev || !fabricCanvas) {
|
|
return;
|
|
}
|
|
const canvasHeight = Math.round(next * 0.67); // 3:2 aspect ratio
|
|
fabricCanvas.setDimensions({ width: next, height: canvasHeight });
|
|
updateCssDimensions();
|
|
fabricCanvas.renderAll();
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="relative mx-auto flex max-w-[min(900px,100%)] items-center justify-center overflow-hidden rounded-3xl border border-slate-700/60 bg-slate-900/80 p-6 shadow-xl shadow-slate-950/40"
|
|
>
|
|
<div class="relative aspect-[3/2] w-full">
|
|
<div class="absolute inset-4 sm:inset-5 md:inset-6 lg:inset-8">
|
|
<div ref="containerElement" class="relative h-full w-full">
|
|
<canvas
|
|
ref="canvasElement"
|
|
class="absolute inset-0 h-full w-full rounded-lg"
|
|
:width="size"
|
|
:height="size"
|
|
/>
|
|
<div
|
|
v-if="!isReady"
|
|
class="absolute inset-0 grid place-items-center rounded-lg bg-slate-900/70"
|
|
>
|
|
<span class="text-sm font-medium text-slate-400">Loading canvas…</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|