<template>
	<div class="pingpu" ref="el"><canvas ref="canvasRef"></canvas></div>
</template>

<script setup>
	import { fabric } from "fabric-with-all";
	import { ref, watch, onMounted } from "vue";
	const KEYS = {
		FILL_X: "location[0]",
		FILL_Y: "location[1]",
		FILL_ANGLE: "angle",
		FILL_SCALEX: "scale[0]",
		FILL_SCALEY: "scale[1]",
		FILL_GAPX: "object.gapX",
		FILL_GAPY: "object.gapY",
		O_TOP: "object.top",
		O_LEFT: "object.left",
		O_SCALE_X: "object.scaleX",
		O_SCALE_Y: "object.scaleY",
		O_OPACITY: "object.opacity",
		O_ANGLE: "object.angle",
		O_FLIPX: "object.flipX",
		O_FLIPY: "object.flipY",
		O_BLENDMODE: "object.blendMode",
	};
	const ACTIONS = {
		ADD: "add",
		SELECT: "select",
		UPDATE: "update",
		DELETE: "delete",
		SORT: "sort",
	};
	const emit = defineEmits(["change-canvas"]);
	const props = defineProps({
		list: { type: Array, default: () => [] },
	});
	const el = ref(null);
	const canvasRef = ref(null);
	const observer = ref(null);
	var canvas = null;
	onMounted(async () => {
		initCanvas();
		await setCanvasData();

		let throttleTimeout = null;
		let lastRunTime = 0;
		let trailingTimeout = null;
		observer.value = new ResizeObserver((entries) => {
			const now = Date.now();
			const throttleDelay = 100;
			if (!throttleTimeout) {
				updateCanvasSize();
				lastRunTime = now;
				throttleTimeout = setTimeout(() => {
					throttleTimeout = null;
				}, throttleDelay);
			} else {
				clearTimeout(trailingTimeout);
				trailingTimeout = setTimeout(() => {
					updateCanvasSize();
					lastRunTime = Date.now();
				}, throttleDelay);
			}
		});
		observer.value.observe(el.value);
	});
	onBeforeUnmount(() => {
		observer.value.disconnect();
		unbindEvent();
	});
	const initCanvas = () => {
		canvas = new fabric.Canvas(canvasRef.value, {
			selection: false,
			preserveObjectStacking: true,
		});
		canvas.setWidth(el.value.offsetWidth);
		canvas.setHeight(el.value.offsetHeight);
		bindEvent();
	};
	const updateCanvasSize = async () => {
		canvas.setWidth(el.value.offsetWidth);
		canvas.setHeight(el.value.offsetHeight);
		await setCanvasData();
	};
	// 绑定事件
	const bindEvent = () => {
		canvas.on("object:modified", onObjectModified);
		canvas.on("selection:created", onObjectSelected);
		canvas.on("selection:updated", onObjectSelected);
	};
	// 解绑事件
	const unbindEvent = () => {
		canvas.off("object:modified", onObjectModified);
		canvas.off("selection:created", onObjectSelected);
		canvas.off("selection:updated", onObjectSelected);
	};
	// 处理对象修改事件
	const onObjectModified = (e) => {
		console.log(e);
		const object = e.target;
		const action = e.action;
		const list = [];
		const id = object.id;
		if (action === "drag") {
			list.push({
				id: id,
				action: ACTIONS.UPDATE,
				key: KEYS.O_TOP,
				value: object.top,
			});
			list.push({
				id: id,
				action: ACTIONS.UPDATE,
				key: KEYS.O_LEFT,
				value: object.left,
			});
		} else if (action === "rotate") {
			list.push({
				id: id,
				action: ACTIONS.UPDATE,
				key: KEYS.O_ANGLE,
				value: object.angle,
			});
		} else if (action === "scale") {
			list.push({
				id: id,
				action: ACTIONS.UPDATE,
				key: KEYS.O_SCALE_X,
				value: object.scaleX,
			});
			list.push({
				id: id,
				action: ACTIONS.UPDATE,
				key: KEYS.O_SCALE_Y,
				value: object.scaleY,
			});
		}
		emit("change-canvas", list);
	};
	// 对象选中
	const onObjectSelected = (e) => {
		const id = e.selected[0].id;
		const list = [
			{
				id: id,
				action: ACTIONS.SELECT,
			},
		];
		emit("change-canvas", list);
	};
	const urlToCanvas = (url) => {
		return new Promise((resolve, reject) => {
			fabric.Image.fromURL(
				url,
				(object) => {
					const imgElement = object.getElement();
					// 创建透明 Canvas
					const tcanvas = document.createElement("canvas");
					tcanvas.width = imgElement.width;
					tcanvas.height = imgElement.height;
					const ctx = tcanvas.getContext("2d");
					ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
					ctx.drawImage(imgElement, 0, 0);
					resolve(tcanvas);
				},
				{ crossOrigin: "anonymous" }
			);
		});
	};
	const setCanvasData = async () => {
		canvas.clear();
		for (let i = 0; i < props.list.length; i++) {
			let item = props.list[i];
			await addObject(item);
		}
		canvas.renderAll();
	};
	const addObject = async (item) => {
		const cwidth = canvas.width;
		const cheight = canvas.height;
		let pattern = await setFill(item);
		let rect = new fabric.Rect({
			id: item.id,
			width: cwidth,
			height: cheight,
			fill: pattern,
			...item.object,
		});
		canvas.add(rect);
	};
	const setFill = async (item) => {
		if (!item) return null;
		const cwidth = canvas.width;
		const cheight = canvas.height;
		let image = await urlToCanvas(item.path);
		let offsetX = item.location[0];
		let offsetY = item.location[1];
		let scaleX = ((cwidth / image.width) * item.scale[0]) / 5;
		let scaleY = ((cheight / image.height) * item.scale[1]) / 5;
		let scale = cwidth > cheight ? scaleX : scaleY;
		let angle = item.angle;
		let gapX = item.object.gapX;
		let gapY = item.object.gapY;
		let patternTransform = fabric.util.composeMatrix({
			scaleX: scale,
			scaleY: scale,
			angle: angle,
		});
		// 创建透明 Canvas
		let tcanvas = document.createElement("canvas");
		tcanvas.width = image.width + gapX;
		tcanvas.height = image.height + gapY;
		let ctx = tcanvas.getContext("2d");
		ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
		ctx.drawImage(image, 0, 0);
		let pattern = new fabric.Pattern({
			source: tcanvas,
			repeat: "repeat",
			patternTransform,
			offsetX, // 水平偏移
			offsetY, // 垂直偏移
		});
		return pattern;
	};
	const updataList = async (list) => {
		const objects = canvas.getObjects();
		// list.forEach((item) => {
		for (let i = 0; i < list.length; i++) {
			let item = list[i];
			let object = objects.find((o) => o.id === item.id);
			if (item.action === ACTIONS.UPDATE) {
				let value = item.value;
				switch (item.key) {
					case KEYS.O_TOP:
						object.set("top", value);
						break;
					case KEYS.O_LEFT:
						object.set("left", value);
						break;
					case KEYS.O_OPACITY:
						object.set("opacity", value);
						break;
					case KEYS.O_SCALE_X:
						object.set("scaleX", value);
						break;
					case KEYS.O_SCALE_Y:
						object.set("scaleY", value);
						break;
					case KEYS.O_ANGLE:
						object.set("angle", value);
						break;
					case KEYS.O_FLIPX:
						object.set("flipX", value);
						break;
					case KEYS.O_FLIPY:
						object.set("flipY", value);
						break;
					case KEYS.O_BLENDMODE:
						object.set("blendMode", value);
						break;
					case KEYS.FILL_X:
					case KEYS.FILL_Y:
					case KEYS.FILL_ANGLE:
					case KEYS.FILL_SCALEX:
					case KEYS.FILL_SCALEY:
					case KEYS.FILL_GAPX:
					case KEYS.FILL_GAPY:
						let pattern = await setFill(
							props.list.find((v) => v.id === item.id)
						);
						object.set("fill", pattern);
						break;
				}
			} else if (item.action === ACTIONS.SELECT) {
				canvas.setActiveObject(object);
			} else if (item.action === ACTIONS.SORT) {
				let ids = item.ids;
				canvas.clear();
				for (let j = 0; j < ids.length; j++) {
					let id = ids[j];
					let object = objects.find((o) => o.id === id);
					canvas.add(object);
				}
				canvas.renderAll();
			} else if (item.action === ACTIONS.DELETE) {
				canvas.remove(object);
			} else if (item.action === ACTIONS.ADD) {
				await addObject(item.data);
			}
		}
		canvas.renderAll();
	};
	defineExpose({
		updataList,
	});
</script>

<style lang='less' scoped>
	.pingpu {
		width: 100%;
		height: 100%;
	}
</style>