Filters can apply to base or all layers

This commit is contained in:
Brian Beck 2023-06-28 23:26:49 -07:00
parent 8cad00552c
commit 0df14f1288
19 changed files with 145 additions and 32 deletions

View file

@ -40,11 +40,13 @@ export default function CanvasTools() {
setSaturation,
brightness,
setBrightness,
layerMode,
setLayerMode,
activeCanvasType,
addImages,
exportSkin,
} = useTools();
const { isDrawingMode, setDrawingMode } = useCanvas(activeCanvas);
const { canvas, isDrawingMode, setDrawingMode } = useCanvas(activeCanvas);
const [isMac, setIsMac] = useState(false);
const commandKeyPrefix = isMac ? "⌘" : "Ctrl ";
const shiftKeySymbol = "⇧";
@ -69,10 +71,6 @@ export default function CanvasTools() {
],
});
if (isFilterToolsOpen && !selectedObjects.length) {
setFilterToolsOpen(false);
}
const isSelectionLocked = selectedObjects.length
? selectedObjects.every((object) => lockedObjects.has(object))
: false;
@ -179,7 +177,6 @@ export default function CanvasTools() {
type="button"
ref={setReferenceElement}
data-active={isFilterToolsOpen ? "" : undefined}
disabled={!selectedObjects.length}
aria-label="Filters"
title="Filters"
onClick={() => {
@ -207,6 +204,60 @@ export default function CanvasTools() {
{...attributes.popper}
>
<div className="Fields">
<div className="Field ApplyTo">
<label>Layer:</label>
<ul>
{selectedObjects.length ? (
<li>
<input
type="radio"
name="FilterLayer"
value="SelectedLayer"
id="FilterLayer-SelectedLayer"
checked={layerMode === "SelectedLayer"}
onChange={() => {
setLayerMode("SelectedLayer");
}}
/>
<label htmlFor="FilterLayer-SelectedLayer">
selected ({selectedObjects.length.toLocaleString()})
</label>
</li>
) : (
<>
<li>
<input
type="radio"
name="FilterLayer"
value="BaseLayer"
id="FilterLayer-BaseLayer"
checked={layerMode === "BaseLayer"}
onChange={() => {
setLayerMode("BaseLayer");
}}
/>{" "}
<label htmlFor="FilterLayer-BaseLayer">base</label>
</li>
<li>
<input
type="radio"
name="FilterLayer"
value="AllLayers"
id="FilterLayer-AllLayers"
checked={layerMode === "AllLayers"}
onChange={() => {
setLayerMode("AllLayers");
}}
/>
<label htmlFor="FilterLayer-AllLayers">
all (
{canvas?._objects.length.toLocaleString() ?? 0})
</label>
</li>
</>
)}
</ul>
</div>
<div className="Field">
<label>
Hue:{" "}

View file

@ -90,16 +90,33 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const [filterChanges, setFilterChanges] = useState<
Array<[fabric.Object, ObjectFilters]>
>(() => []);
const [layerMode, setLayerMode] = useState("BaseLayer");
if (selectedObjects.length) {
if (layerMode !== "SelectedLayer") {
setLayerMode("SelectedLayer");
}
} else {
if (layerMode === "SelectedLayer") {
setLayerMode("BaseLayer");
}
}
const getFilter = (name: keyof ObjectFilters) => {
if (selectedObjects.length) {
let applyObjects = selectedObjects;
if (layerMode === "AllLayers") {
applyObjects = canvas?._objects ?? [];
} else if (layerMode === "BaseLayer") {
applyObjects = canvas?._objects.slice(0, 1) ?? [];
}
if (applyObjects.length) {
const getValue = (i: number) =>
(filterMap.get(selectedObjects[i]) ?? {})[name] ?? 0;
(filterMap.get(applyObjects[i]) ?? {})[name] ?? 0;
const firstValue = getValue(0);
if (
selectedObjects
applyObjects
.slice(1)
.every((selectedObject, i) => getValue(i + 1) === firstValue)
.every((applyObject, i) => getValue(i + 1) === firstValue)
) {
return firstValue;
}
@ -115,22 +132,24 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const setFilter = useCallback(
(name: keyof ObjectFilters, value: number) => {
if (!selectedObjects.length) {
setFilterChanges([]);
return;
}
const filterChanges: Array<[fabric.Object, ObjectFilters]> = [];
const newFilterMap = new Map(filterMap);
for (const selectedObject of selectedObjects) {
const existingFilters = filterMap.get(selectedObject) ?? {};
let applyObjects = selectedObjects;
if (layerMode === "AllLayers") {
applyObjects = canvas?._objects ?? [];
} else if (layerMode === "BaseLayer") {
applyObjects = canvas?._objects.slice(0, 1) ?? [];
}
for (const applyObject of applyObjects) {
const existingFilters = filterMap.get(applyObject) ?? {};
const newFilters = { ...existingFilters, [name]: value };
newFilterMap.set(selectedObject, newFilters);
filterChanges.push([selectedObject, newFilters]);
newFilterMap.set(applyObject, newFilters);
filterChanges.push([applyObject, newFilters]);
}
setFilterMap(newFilterMap);
setFilterChanges(filterChanges);
},
[filterMap, selectedObjects]
[canvas, layerMode, filterMap, selectedObjects]
);
const setHueRotate = useCallback(
@ -434,6 +453,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
setSaturation,
brightness,
setBrightness,
layerMode,
setLayerMode,
selectedObjects,
lockSelection,
unlockSelection,
@ -464,6 +485,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
hueRotate,
saturation,
brightness,
layerMode,
setHueRotate,
setSaturation,
setBrightness,

View file

@ -385,3 +385,41 @@ select {
transform: translate3d(0, -50%, 0);
z-index: 2;
}
h6 {
margin: 0;
padding: 0;
font-weight: bold;
font-size: 11px;
}
.Field.ApplyTo {
display: flex;
flex-direction: row;
align-self: flex-start;
align-items: center;
padding-top: 6px;
padding-bottom: 10px;
}
.Field.ApplyTo input[type="radio"] {
margin: 2px 3px;
}
.Field.ApplyTo ul {
font-size: 12px;
display: flex;
align-items: center;
gap: 12px;
list-style: none;
margin: 0;
padding: 0;
}
.Field.ApplyTo li {
display: flex;
align-items: center;
gap: 4px;
margin: 0;
padding: 0;
}

View file

@ -16,6 +16,8 @@ interface ToolsContextValue {
setSaturation: (saturation: number) => void;
brightness: number | null;
setBrightness: (brightness: number) => void;
layerMode: string;
setLayerMode: (layerMode: string) => void;
deleteSelection: () => void;
undo: () => void;
redo: () => void;