Update export UI, allow selecting exported materials

This commit is contained in:
Brian Beck 2025-01-30 17:18:32 -08:00
parent 911eecd4c3
commit 11b8a03ad9
25 changed files with 215 additions and 56 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

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

@ -1,2 +1,2 @@
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={id:e,loaded:!1,exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.loaded=!0,_.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"10c119307e239c98",737:"bc4a70b34221e8c8",767:"0dd6b240996f3455",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/"+({214:"922e89893536f2f9",888:"4f6df96970b4e27f"})[e]+".css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(f);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},f=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
//# sourceMappingURL=webpack-052f8e41ec24217f.js.map
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={id:e,loaded:!1,exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.loaded=!0,_.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"10c119307e239c98",737:"bc4a70b34221e8c8",767:"0dd6b240996f3455",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/"+({214:"922e89893536f2f9",888:"fa467350f3c0ad7f"})[e]+".css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(f);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},f=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
//# sourceMappingURL=webpack-dd07d6e501d595e1.js.map

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

@ -1 +1 @@
self.__BUILD_MANIFEST=function(s){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,"static/chunks/e21e5bbe-b28e0079b469d4e8.js","static/chunks/ebc70433-4eccd1cb3af29a3e.js","static/chunks/6eb5140f-31a2b2da7903b885.js","static/chunks/85d7bc83-1ca530d7d3f44153.js","static/chunks/3a17f596-9aeae038dfa51955.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/515-13ff0773d41722ae.js","static/chunks/pages/index-4060c642ab2f1a1b.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],"/gallery":[s,"static/chunks/737a5600-aea383aaa2061cc6.js","static/chunks/918-3c6747f76df39072.js","static/css/922e89893536f2f9.css","static/chunks/pages/gallery-af1406fdc1af13f5.js"],sortedPages:["/","/_app","/_error","/gallery"]}}("static/chunks/cb355538-dbf1c108320fc6a1.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
self.__BUILD_MANIFEST=function(s){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,"static/chunks/e21e5bbe-b28e0079b469d4e8.js","static/chunks/ebc70433-4eccd1cb3af29a3e.js","static/chunks/6eb5140f-31a2b2da7903b885.js","static/chunks/85d7bc83-1ca530d7d3f44153.js","static/chunks/3a17f596-9aeae038dfa51955.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/515-13ff0773d41722ae.js","static/chunks/pages/index-1a2b7dc61d221db6.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],"/gallery":[s,"static/chunks/737a5600-aea383aaa2061cc6.js","static/chunks/918-3c6747f76df39072.js","static/css/922e89893536f2f9.css","static/chunks/pages/gallery-af1406fdc1af13f5.js"],sortedPages:["/","/_app","/_error","/gallery"]}}("static/chunks/cb355538-e538db8a1761f402.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,5 @@
import { InputHTMLAttributes, useEffect, useRef, useState } from "react";
import getConfig from "next/config";
import useCanvas from "./useCanvas";
import useTools from "./useTools";
import { usePopper } from "react-popper";
@ -6,6 +7,7 @@ import Slider from "rc-slider";
import { RiFileCopyFill } from "react-icons/ri";
import {
FaTrashAlt,
FaAngleDown,
FaLock,
FaUnlock,
FaArrowUp,
@ -14,11 +16,16 @@ import {
import { GiArrowCursor } from "react-icons/gi";
import { IoMdBrush } from "react-icons/io";
import { ImPlus, ImUndo2, ImRedo2, ImContrast } from "react-icons/im";
import useWarrior from "./useWarrior";
import { MaterialDefinition } from "./Material";
const { publicRuntimeConfig } = getConfig();
const { materials } = publicRuntimeConfig;
export default function CanvasTools() {
const nameInputRef = useRef<HTMLInputElement | null>(null);
const fileInputRef = useRef<HTMLInputElement | null>(null);
const fileTypeRef = useRef<HTMLSelectElement | null>(null);
const [exportFileType, setExportFileType] = useState("vl2");
const {
activeCanvas,
backgroundColor,
@ -50,7 +57,11 @@ export default function CanvasTools() {
activeCanvasType,
addImages,
exportSkin,
selectedExportMaterials,
setSelectedExportMaterials,
} = useTools();
const { actualModel } = useWarrior();
const materialDefs: MaterialDefinition[] = materials[actualModel];
const { canvas, isDrawingMode, setDrawingMode } = useCanvas(activeCanvas);
const [isMac, setIsMac] = useState(false);
const commandKeyPrefix = isMac ? "⌘" : "Ctrl ";
@ -61,12 +72,10 @@ export default function CanvasTools() {
null
);
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);
const [isBrushToolsOpen, setBrushToolsOpen] = useState(false);
const [isFilterToolsOpen, setFilterToolsOpen] = useState(false);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
modifiers: [
{ name: "arrow", options: { element: arrowElement } },
{
name: "offset",
options: {
@ -76,6 +85,25 @@ export default function CanvasTools() {
],
});
// Export popup
const [isExportOptionsOpen, setExportOptionsOpen] = useState(false);
const [exportReferenceElement, setExportReferenceElement] =
useState<HTMLElement | null>(null);
const { styles: exportStyles, attributes: exportAttributes } = usePopper(
exportReferenceElement,
popperElement,
{
modifiers: [
{
name: "offset",
options: {
offset: [0, 10],
},
},
],
}
);
const isSelectionLocked = selectedObjects.length
? selectedObjects.every((object) => lockedObjects.has(object))
: false;
@ -394,12 +422,6 @@ export default function CanvasTools() {
</div>
</div>
</div>
<div
className="PopupArrow"
ref={setArrowElement}
style={styles.arrow}
/>
</div>
) : null}
<button
@ -587,12 +609,6 @@ export default function CanvasTools() {
</div>
</div>
</div>
<div
className="PopupArrow"
ref={setArrowElement}
style={styles.arrow}
/>
</div>
) : null}
</>
@ -607,22 +623,104 @@ export default function CanvasTools() {
size={12}
/>
<button
className="ExportOptionsButton"
type="button"
ref={setExportReferenceElement}
data-active={isExportOptionsOpen ? "" : undefined}
aria-label="Export Options"
title="Export Options"
onClick={() => {
const name = nameInputRef.current ? nameInputRef.current.value : "";
const format = fileTypeRef.current
? fileTypeRef.current.value
: ".png";
exportSkin({ name, format });
setExportOptionsOpen((isOpen) => !isOpen);
}}
>
Export
.{exportFileType}
<FaAngleDown />
</button>
<select ref={fileTypeRef} defaultValue="vl2">
{isExportOptionsOpen ? (
<div
className="ExportOptionsPopup"
ref={setPopperElement}
style={exportStyles.popper}
tabIndex={-1}
onBlur={(event) => {
const newFocusElement = event.relatedTarget;
const isFocusLeaving =
!newFocusElement ||
!event.currentTarget.contains(newFocusElement);
if (isFocusLeaving) {
setExportOptionsOpen(false);
}
}}
{...exportAttributes.popper}
>
<div className="Fields">
<div className="Field">
<label>Export Materials</label>
<ul className="ExportOptionsList">
{materialDefs.map((material, i) => {
if (
material &&
material.selectable !== false &&
!material.hidden
) {
return (
<li key={material.name}>
<input
id={`MaterialSelect-${material.name}`}
type="checkbox"
checked={selectedExportMaterials[i] !== false}
onChange={(event) => {
setSelectedExportMaterials(
(selectedExportMaterials) => {
const newSelectedExportMaterials =
selectedExportMaterials.slice();
newSelectedExportMaterials[i] =
event.target.checked;
return newSelectedExportMaterials;
}
);
}}
/>
<label htmlFor={`MaterialSelect-${material.name}`}>
{material.label}
</label>
</li>
);
} else {
return null;
}
})}
</ul>
</div>
<div className="Field">
<label htmlFor="ExportFormat">Export Format</label>
<select
id="ExportFormat"
value={exportFileType}
onChange={(event) => {
setExportFileType(event.target.value);
}}
>
<option value="png">.png</option>
<option value="vl2">.vl2</option>
</select>
</div>
</div>
</div>
) : null}
<button
type="button"
onClick={() => {
const name = nameInputRef.current ? nameInputRef.current.value : "";
exportSkin({ name, format: exportFileType });
}}
>
Export
</button>
</div>
</div>
);
}

View file

@ -46,13 +46,15 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const { actualModel, selectedModelType } = useWarrior();
const [selectedMaterialIndex, setSelectedMaterialIndex] = useState(0);
const [selectedFrameIndex, setSelectedFrameIndex] = useState(0);
const materialDefs = materials[actualModel];
const materialDefs: MaterialDefinition[] = materials[actualModel];
const materialDef = materialDefs[selectedMaterialIndex] ?? null;
const frameCount = materialDef.frameCount ?? 1;
const hasAnimation = frameCount > 1;
const [selectedExportMaterials, setSelectedExportMaterials] = useState<
boolean[]
>([]);
const textureSize = useMemo(
const textureSize: [number, number] = useMemo(
() => materialDef.size ?? [512, 512],
[materialDef]
);
@ -71,6 +73,14 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
setSelectedFrameIndex(0);
}
useEffect(() => {
setSelectedExportMaterials(
materialDefs.map((material) =>
Boolean(material && material.selectable !== false && !material.hidden)
)
);
}, [materialDefs]);
useEffect(() => {
setSelectedFrameIndex(0);
}, [materialDef]);
@ -357,10 +367,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
const materialExports = await Promise.all(
materialDefs
.filter(
(materialDef: MaterialDefinition) =>
materialDef &&
!materialDef.hidden &&
materialDef.selectable !== false
(materialDef: MaterialDefinition, i) =>
selectedExportMaterials[i] !== false
)
.map((materialDef: MaterialDefinition) => {
const frameCount = materialDef.frameCount ?? 1;
@ -396,7 +404,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
outputImageUrl = colorImageUrl;
}
let filename;
let filename: string;
switch (selectedModelType) {
case "player":
filename = `${name}.${actualModel}.png`;
@ -423,6 +431,9 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
} else {
filename = `${actualModel}.png`;
}
break;
default:
throw new Error("Unknown model type");
}
return { imageUrl: outputImageUrl, filename };
@ -433,8 +444,10 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
switch (format) {
case "png": {
const { imageUrl, filename } = materialExports[selectedMaterialIndex];
materialExports.forEach((materialExport) => {
const { imageUrl, filename } = materialExport;
savePngFile(imageUrl, filename);
});
break;
}
case "vl2": {
@ -472,8 +485,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
canvases,
combineColorAndAlphaImageUrls,
materialDefs,
selectedMaterialIndex,
selectedModelType,
selectedExportMaterials,
]
);
@ -521,6 +534,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
setSelectedFrameIndex,
hasAnimation,
frameCount,
selectedExportMaterials,
setSelectedExportMaterials,
}),
[
activeCanvas,
@ -556,6 +571,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
selectedFrameIndex,
hasAnimation,
frameCount,
selectedExportMaterials,
]
);

View file

@ -177,6 +177,18 @@ select {
margin-left: 3px;
}
.Export .ExportOptionsButton {
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 4px;
padding-right: 6px;
}
.Export .ExportOptionsButton svg {
margin-top: 2px;
}
.Export select {
width: 6em;
min-width: 6em;
@ -184,6 +196,37 @@ select {
margin-left: 3px;
}
.ExportOptionsPopup {
background: rgba(5, 22, 21, 0.9);
border-radius: 4px;
border: 1px solid rgb(0, 255, 200);
padding: 10px 12px;
user-select: none;
}
.ExportOptionsPopup .Fields {
align-items: flex-start;
}
.ExportOptionsList {
list-style: none;
margin: 0;
padding: 0;
font-size: 13px;
}
.ExportOptionsList li {
display: flex;
align-items: center;
gap: 5px;
margin: 4px 0;
padding: 0 10px 0 0;
}
.ExportOptionsPopup select {
margin: 4px;
}
.SliderContainer {
min-width: 200px;
min-height: 30px;
@ -322,10 +365,6 @@ select {
user-select: none;
}
.PopupArrow {
display: none;
}
.Fields {
display: flex;
flex-direction: column;

View file

@ -49,6 +49,12 @@ interface ToolsContextValue {
hasMetallic: boolean;
hasAnimation: boolean;
frameCount: number;
selectedExportMaterials: boolean[];
setSelectedExportMaterials: (
selectedExportMaterials:
| boolean[]
| ((selectedExportMaterials: boolean[]) => boolean[])
) => void;
}
const ToolsContext = React.createContext<ToolsContextValue | null>(null);