bump fabric

This commit is contained in:
Brian Beck 2025-10-19 10:23:35 -07:00
parent 9e127b683c
commit c1cc0c73a1
25 changed files with 2729 additions and 162 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -11,7 +11,7 @@ f:"$Sreact.suspense"
11:I[68027,[],"default"]
:HL["/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","style"]
:HL["/t2-model-skinner/_next/static/chunks/be25e0c53b3e76c3.css","style"]
0:{"P":null,"b":"VY3oqBwyqvlStXl38ih2v","p":"/t2-model-skinner","c":["","gallery",""],"i":false,"f":[[["",{"children":["gallery",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["gallery",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/be25e0c53b3e76c3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-model-skinner/_next/static/chunks/6d7b92f74fb4b5af.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-model-skinner/_next/static/chunks/3710394f99078da4.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"s":false,"S":true}
0:{"P":null,"b":"-yFPMzbzcZfNmotCttmg4","p":"/t2-model-skinner","c":["","gallery",""],"i":false,"f":[[["",{"children":["gallery",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["gallery",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/be25e0c53b3e76c3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-model-skinner/_next/static/chunks/6d7b92f74fb4b5af.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-model-skinner/_next/static/chunks/3710394f99078da4.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"s":false,"S":true}
6:{}
7:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@
2:I[39756,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
3:I[37457,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
4:I[47257,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"ClientPageRoot"]
5:I[52683,["/t2-model-skinner/_next/static/chunks/00e36f5c4a673582.js","/t2-model-skinner/_next/static/chunks/d9e502f7607fcfba.js","/t2-model-skinner/_next/static/chunks/ada16a90703ade36.js","/t2-model-skinner/_next/static/chunks/e4e6678c9c61c198.js","/t2-model-skinner/_next/static/chunks/a55df98dcb3b60ad.js"],"default"]
5:I[52683,["/t2-model-skinner/_next/static/chunks/00e36f5c4a673582.js","/t2-model-skinner/_next/static/chunks/d63fc3798baf75bd.js","/t2-model-skinner/_next/static/chunks/a55df98dcb3b60ad.js","/t2-model-skinner/_next/static/chunks/6cb39dc61a422734.js","/t2-model-skinner/_next/static/chunks/ada16a90703ade36.js"],"default"]
8:I[97367,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"OutletBoundary"]
a:I[11533,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"AsyncMetadataOutlet"]
c:I[97367,["/t2-model-skinner/_next/static/chunks/ff1a16fafef87110.js","/t2-model-skinner/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
@ -11,7 +11,7 @@ f:"$Sreact.suspense"
11:I[68027,[],"default"]
:HL["/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","style"]
:HL["/t2-model-skinner/_next/static/chunks/df18f9fdc6a3cb7c.css","style"]
0:{"P":null,"b":"VY3oqBwyqvlStXl38ih2v","p":"/t2-model-skinner","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/df18f9fdc6a3cb7c.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-model-skinner/_next/static/chunks/00e36f5c4a673582.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-model-skinner/_next/static/chunks/d9e502f7607fcfba.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-model-skinner/_next/static/chunks/ada16a90703ade36.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-model-skinner/_next/static/chunks/e4e6678c9c61c198.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-model-skinner/_next/static/chunks/a55df98dcb3b60ad.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"s":false,"S":true}
0:{"P":null,"b":"-yFPMzbzcZfNmotCttmg4","p":"/t2-model-skinner","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/df18f9fdc6a3cb7c.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-model-skinner/_next/static/chunks/00e36f5c4a673582.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-model-skinner/_next/static/chunks/d63fc3798baf75bd.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-model-skinner/_next/static/chunks/a55df98dcb3b60ad.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-model-skinner/_next/static/chunks/6cb39dc61a422734.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-model-skinner/_next/static/chunks/ada16a90703ade36.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[["$","link","0",{"rel":"stylesheet","href":"/t2-model-skinner/_next/static/chunks/258ad065ddacdc6d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"s":false,"S":true}
6:{}
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]

2736
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@
"@rc-component/slider": "^1.0.0",
"@tanstack/react-query": "^5.90.5",
"comlink": "^4.4.2",
"fabric": "^5.5.2-browser",
"fabric": "^6.7.1",
"file-saver": "^2.0.5",
"globby": "^13.1.2",
"jszip": "^3.10.1",
@ -35,13 +35,10 @@
"@eslint/js": "^9.38.0",
"@next/bundle-analyzer": "^15.5.6",
"@next/eslint-plugin-next": "^15.5.6",
"@types/fabric": "^5.3.10",
"@types/file-saver": "^2.0.7",
"@types/lodash.orderby": "^4.6.9",
"@types/pngjs": "^6.0.5",
"@types/react": "^19.2.2",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"eslint": "^9.38.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.0",

View file

@ -3,13 +3,14 @@ import { useCallback, useEffect, useRef, useState } from "react";
import useCanvas from "./useCanvas";
import useSettings from "./useSettings";
import useTools from "./useTools";
import { fabric } from "fabric";
import { Canvas as FabricCanvas, InteractiveFabricObject } from "fabric";
import { createFabricImage } from "./fabricUtils";
type JSONSnapshot = ReturnType<(typeof Canvas.prototype)["toDatalessJSON"]>;
function updateObjectControlOptions() {
fabric.Object.prototype.set({
InteractiveFabricObject.ownDefaults = {
...InteractiveFabricObject.ownDefaults,
transparentCorners: false,
borderColor: "#8afff1",
cornerSize: 9,
@ -18,13 +19,13 @@ function updateObjectControlOptions() {
cornerStrokeColor: "#1c9f7c",
strokeWidth: 10,
perPixelTargetFind: true,
});
};
}
export interface CanvasProps {
canvasId: string;
canvasType: "color" | "metallic";
onChange: (canvas: fabric.Canvas) => void;
onChange: (canvas: FabricCanvas) => void;
baseImageUrl: string | null;
textureSize: [number, number];
defaultDrawingMode?: boolean;
@ -38,7 +39,7 @@ export default function Canvas({
defaultDrawingMode = false,
}: CanvasProps) {
const canvasElementRef = useRef<HTMLCanvasElement | null>(null);
const [canvas, setCanvas] = useState<fabric.Canvas | null>(null);
const [canvas, setCanvas] = useState<FabricCanvas | null>(null);
const { activeCanvas } = useTools();
const { canvasPadding } = useSettings();
const { registerCanvas, unregisterCanvas } = useCanvas();
@ -105,13 +106,18 @@ export default function Canvas({
const isActive = activeCanvas === canvasId;
useEffect(() => {
if (!canvasElementRef.current) {
return;
}
const options = {
preserveObjectStacking: true,
targetFindTolerance: 2,
};
updateObjectControlOptions();
const canvas = new fabric.Canvas(canvasElementRef.current, options);
const canvas = new FabricCanvas(canvasElementRef.current, options);
let isSnapshotting = false;
let changeTimer: ReturnType<typeof setTimeout>;
@ -138,7 +144,7 @@ export default function Canvas({
if (JSON.stringify(snapshot) === JSON.stringify(lastSnapshot)) {
return history;
} else {
return [...history.slice(-5), snapshot];
return [...history.slice(-10), snapshot];
}
});
setRedoHistory([]);
@ -225,6 +231,11 @@ export default function Canvas({
canRedo,
]);
useEffect(() => {
setUndoHistory([]);
setRedoHistory([]);
}, [canvas, baseImageUrl, textureSize]);
useEffect(() => {
if (canvas && textureSize) {
trackChanges.current = false;

View file

@ -35,6 +35,9 @@ export default function CanvasInteractions({
className="CanvasInteractions"
tabIndex={0}
ref={ref}
onDragOver={(event) => {
event.preventDefault();
}}
onDrop={async (event) => {
event.preventDefault();
if (ref.current) {

View file

@ -1,6 +1,6 @@
"use client";
import { InputHTMLAttributes, useEffect, useRef, useState } from "react";
import { fabric } from "fabric";
import { FabricImage } from "fabric";
import useCanvas from "./useCanvas";
import useTools from "./useTools";
import {
@ -168,7 +168,7 @@ export default function CanvasTools() {
const hasSelection = selectedObjects.length > 0;
const selectionHasImages =
selectedObjects.filter((object) => object instanceof fabric.Image).length >
selectedObjects.filter((object) => object instanceof FabricImage).length >
0;
const handleBackgroundColorChange: InputHTMLAttributes<HTMLInputElement>["onChange"] =
@ -441,7 +441,7 @@ export default function CanvasTools() {
all (
{canvas?._objects
.filter(
(object) => object instanceof fabric.Image
(object) => object instanceof FabricImage
)
.length.toLocaleString() ?? 0}
)
@ -561,7 +561,7 @@ export default function CanvasTools() {
<label>
Opacity:{" "}
<strong>
{contrast == null
{opacity == null
? "MULTIPLE VALUES"
: `${Math.round((opacity ?? 1) * 100)}%`}
</strong>

View file

@ -37,6 +37,8 @@ export default function ColorCanvas({
const handleChange = useCallback<CanvasProps["onChange"]>(
async (canvas) => {
const imageUrl = canvas.toDataURL({
format: "png",
multiplier: 1,
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],

View file

@ -42,6 +42,8 @@ export default function MetallicCanvas({
async (canvas) => {
runningChangeHandlers.current += 1;
const imageUrl = canvas.toDataURL({
format: "png",
multiplier: 1,
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],

View file

@ -1,6 +1,12 @@
"use client";
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { fabric } from "fabric";
import {
FabricImage,
FabricObject,
ActiveSelection,
filters,
PencilBrush,
} from "fabric";
import { ToolsContext } from "./useTools";
import useCanvas from "./useCanvas";
import useWarrior from "./useWarrior";
@ -15,7 +21,7 @@ const { materials } = modelConfig;
const defaultTextureSize = [512, 512] as [number, number];
function lockObject(object: fabric.Object) {
function lockObject(object: FabricObject) {
object.lockMovementX = true;
object.lockMovementY = true;
object.lockScalingX = true;
@ -23,7 +29,7 @@ function lockObject(object: fabric.Object) {
object.lockRotation = true;
}
function unlockObject(object: fabric.Object) {
function unlockObject(object: FabricObject) {
object.lockMovementX = false;
object.lockMovementY = false;
object.lockScalingX = false;
@ -31,9 +37,7 @@ function unlockObject(object: fabric.Object) {
object.lockRotation = false;
}
function isActiveSelection(
object: fabric.Object
): object is fabric.ActiveSelection {
function isActiveSelection(object: FabricObject): object is ActiveSelection {
return object.type === "activeSelection";
}
@ -106,14 +110,14 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const [backgroundColor, setBackgroundColor] = useState("magenta");
const [lockedObjects, setLockedObjects] = useState(
() => new Set<fabric.Object>()
() => new Set<FabricObject>()
);
const [brushColor, setBrushColor] = useState(200);
const [brushSize, setBrushSize] = useState(10);
const [filterMap, setFilterMap] = useState(
() => new Map<fabric.Object, ObjectFilters>()
() => new Map<FabricObject, ObjectFilters>()
);
const [selectedObjects, setSelectedObjects] = useState<fabric.Object[]>(
const [selectedObjects, setSelectedObjects] = useState<FabricObject[]>(
() => []
);
@ -131,7 +135,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const { combineColorAndAlphaImageUrls } = useImageWorker();
const { canvasPadding } = useSettings();
const [filterChanges, setFilterChanges] = useState<
Array<[fabric.Object, ObjectFilters]>
Array<[FabricObject, ObjectFilters]>
>(() => []);
const [layerMode, setLayerMode] = useState("BaseLayer");
@ -147,7 +151,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const setFilter = useCallback(
(name: keyof ObjectFilters, value: number) => {
const filterChanges: Array<[fabric.Object, ObjectFilters]> = [];
const filterChanges: Array<[FabricObject, ObjectFilters]> = [];
const newFilterMap = new Map(filterMap);
let applyObjects = selectedObjects;
if (layerMode === "AllLayers") {
@ -156,7 +160,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
applyObjects = canvas?._objects.slice(0, 1) ?? [];
}
for (const applyObject of applyObjects) {
if (applyObject instanceof fabric.Image) {
if (applyObject instanceof FabricImage) {
const existingFilters = filterMap.get(applyObject) ?? {};
const newFilters = { ...existingFilters, [name]: value };
newFilterMap.set(applyObject, newFilters);
@ -180,7 +184,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
applyObjects = canvas?._objects.slice(0, 1) ?? [];
}
applyObjects = applyObjects.filter(
(object) => object instanceof fabric.Image
(object) => object instanceof FabricImage
);
if (applyObjects.length) {
const getValue = (i: number) =>
@ -210,10 +214,10 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
return;
}
for (const [selectedObject, newFilters] of filterChanges) {
if (selectedObject instanceof fabric.Image) {
if (selectedObject instanceof FabricImage) {
selectedObject.filters = [];
if (activeCanvasType === "metallic") {
const grayscaleFilter = new fabric.Image.filters.Grayscale();
const grayscaleFilter = new filters.Grayscale();
selectedObject.filters.push(grayscaleFilter);
}
for (const key in newFilters) {
@ -222,28 +226,28 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
switch (key) {
case "HueRotation":
selectedObject.filters.push(
new fabric.Image.filters.HueRotation({
new filters.HueRotation({
rotation: filterValue,
})
);
break;
case "Saturation":
selectedObject.filters.push(
new fabric.Image.filters.Saturation({
new filters.Saturation({
saturation: filterValue,
})
);
break;
case "Brightness":
selectedObject.filters.push(
new fabric.Image.filters.Brightness({
new filters.Brightness({
brightness: filterValue,
})
);
break;
case "Contrast":
selectedObject.filters.push(
new fabric.Image.filters.Contrast({
new filters.Contrast({
contrast: filterValue,
})
);
@ -292,7 +296,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const bringForward = useCallback(async () => {
const object = canvas.getActiveObject();
if (object) {
canvas.bringForward(object, true);
canvas.bringObjectForward(object, true);
notifyChange();
}
}, [canvas, notifyChange]);
@ -304,7 +308,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
if (canvas._objects[0] === object || canvas._objects[1] === object) {
return;
}
canvas.sendBackwards(object, true);
canvas.sendObjectBackwards(object, true);
notifyChange();
}
}, [canvas, notifyChange]);
@ -333,7 +337,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
if (!image.filters) {
image.filters = [];
}
const grayscaleFilter = new fabric.Image.filters.Grayscale();
const grayscaleFilter = new filters.Grayscale();
image.filters.push(grayscaleFilter);
image.applyFilters();
}
@ -346,6 +350,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
}
if (lastAddedImage) {
canvas.setActiveObject(lastAddedImage);
canvas.requestRenderAll();
}
},
[textureSize, activeCanvasType, metallicCanvas, canvas, setDrawingMode]
@ -354,9 +359,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const duplicate = useCallback(async () => {
const object = canvas.getActiveObject();
if (object) {
const copy = await new Promise<fabric.Object>((resolve) =>
object.clone(resolve)
);
const copy = await object.clone();
copy.set({
top: (copy.top ?? 0) + 20,
left: (copy.left ?? 0) + 20,
@ -388,6 +391,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const copyToMetallic = useCallback(async () => {
if (activeCanvasType === "color" && metallicCanvas) {
const colorImageUrl = canvas.toDataURL({
format: "png",
multiplier: 1,
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
@ -397,7 +402,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
if (!image.filters) {
image.filters = [];
}
const grayscaleFilter = new fabric.Image.filters.Grayscale();
const grayscaleFilter = new filters.Grayscale();
image.filters.push(grayscaleFilter);
image.applyFilters();
metallicCanvas.centerObject(image);
@ -450,6 +455,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
let outputImageUrl;
const colorImageUrl = colorCanvas.toDataURL({
format: "png",
multiplier: 1,
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
@ -458,6 +465,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
if (metallicCanvas) {
const metallicImageUrl = metallicCanvas.toDataURL({
format: "png",
multiplier: 1,
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
@ -676,12 +685,18 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
useEffect(() => {
if (metallicCanvas) {
if (!metallicCanvas.freeDrawingBrush) {
metallicCanvas.freeDrawingBrush = new PencilBrush(metallicCanvas);
}
metallicCanvas.freeDrawingBrush.width = brushSize;
}
}, [metallicCanvas, brushSize]);
useEffect(() => {
if (metallicCanvas) {
if (!metallicCanvas.freeDrawingBrush) {
metallicCanvas.freeDrawingBrush = new PencilBrush(metallicCanvas);
}
metallicCanvas.freeDrawingBrush.color = `rgb(${brushColor}, ${brushColor}, ${brushColor})`;
}
}, [metallicCanvas, brushColor]);

View file

@ -1,12 +1,6 @@
"use client";
import { fabric } from "fabric";
import { FabricImage } from "fabric";
export async function createFabricImage(url: string) {
const promise = new Promise<fabric.Image>((resolve) =>
fabric.Image.fromURL(url, resolve, {
crossOrigin: "anonymous",
})
);
const img = await promise;
const img = await FabricImage.fromURL(url, { crossOrigin: "anonymous" });
return img;
}

View file

@ -1,9 +1,9 @@
"use client";
import React, { useContext } from "react";
import { fabric } from "fabric";
import { Canvas as FabricCanvas } from "fabric";
export interface CanvasInfo {
canvas: fabric.Canvas;
canvas: FabricCanvas;
notifyChange: () => void;
isDrawingMode: boolean;
setDrawingMode: (isDrawingMode: boolean) => void;

View file

@ -1,12 +1,12 @@
"use client";
import React, { useContext } from "react";
import { fabric } from "fabric";
import { FabricObject } from "fabric";
interface ToolsContextValue {
activeCanvas: string | null;
activeCanvasType: string;
setActiveCanvasType: (canvasType: string) => void;
selectedObjects: Array<fabric.Object>;
selectedObjects: Array<FabricObject>;
brushSize: number;
setBrushSize: (brushSize: number) => void;
brushColor: number;
@ -42,7 +42,7 @@ interface ToolsContextValue {
name: string;
format: string;
}) => Promise<void>;
lockedObjects: Set<fabric.Object>;
lockedObjects: Set<FabricObject>;
backgroundColor: string;
setBackgroundColor: (backgroundColor: string) => void;
selectedMaterialIndex: number;

5
types/fabric.d.ts vendored
View file

@ -1,5 +0,0 @@
/// <reference types="fabric" />
declare module "fabric" {
// Re-export the DT namespace as the module's export.
export { fabric };
}