diff --git a/.github/workflows/Automated Test (full).yml b/.github/workflows/Automated Test (full).yml index cc81b62..3fc879a 100644 --- a/.github/workflows/Automated Test (full).yml +++ b/.github/workflows/Automated Test (full).yml @@ -53,7 +53,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '15' - name: Cache modules psychojs_testing uses: actions/cache@v2 env: diff --git a/.github/workflows/Automated Test (short).yml b/.github/workflows/Automated Test (short).yml index 7d6001b..8d25449 100644 --- a/.github/workflows/Automated Test (short).yml +++ b/.github/workflows/Automated Test (short).yml @@ -45,7 +45,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: '12' + node-version: '15' # START: install psychojs_testing - name: Checkout psychojs_testing diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6ca79e2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Build Branch +on: workflow_dispatch + +jobs: + build_all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + path: app + - uses: actions/setup-node@master + with: + node-version: 19 + - name: Install Node dependencies + run: | + cd app + npm install + + - name: Build + run: | + cd app + echo "testing GITHUB_REF with details availability: ${GITHUB_REF#refs/heads/}" + npm run build:js && npm run build:css + echo "executing ls out on the directory:" + ls out diff --git a/.gitignore b/.gitignore index c19bd94..a92ecbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode/ dist out node_modules diff --git a/package-lock.json b/package-lock.json index cb6071b..0188cd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,36 @@ { "name": "psychojs", - "version": "2022.2.0", + "version": "2024.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "psychojs", - "version": "2022.2.0", + "version": "2024.1.0", "license": "MIT", "dependencies": { "@pixi/filter-adjustment": "^4.1.3", "a11y-dialog": "^7.5.0", "docdash": "^1.2.0", "esbuild-plugin-glsl": "^1.0.5", + "gifuct-js": "^2.1.2", "howler": "^2.2.1", "log4javascript": "github:Ritzlgrmft/log4javascript", "pako": "^1.0.10", + "pixi-filters": "^5.0.0", "pixi.js-legacy": "^6.0.4", "seedrandom": "^3.0.5", "tone": "^14.7.77", - "xlsx": "^0.17.0" + "xlsx": "^0.18.5" }, "devDependencies": { "csslint": "^1.0.5", "dprint": "^0.15.3", "esbuild": "^0.12.1", "eslint": "^7.26.0", - "jsdoc": "^3.6.7" + "jsdoc": "^3.6.7", + "vite": "^5.1.6", + "vite-plugin-glsl": "^1.2.1" }, "engines": { "node": ">=14.15.0", @@ -79,6 +83,374 @@ "regenerator-runtime": "^0.13.4" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", @@ -288,6 +660,15 @@ "@pixi/text": "6.0.4" } }, + "node_modules/@pixi/color": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.2.4.tgz", + "integrity": "sha512-B/+9JRcXe2uE8wQfsueFRPZVayF2VEMRB7XGeRAsWCryOX19nmWhv0Nt3nOU2rvzI0niz9XgugJXsB6vVmDFSg==", + "peer": true, + "dependencies": { + "colord": "^2.9.3" + } + }, "node_modules/@pixi/compressed-textures": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-6.0.4.tgz", @@ -331,6 +712,12 @@ "@pixi/utils": "6.0.4" } }, + "node_modules/@pixi/extensions": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.2.4.tgz", + "integrity": "sha512-Mnqv9scbL1ARD3QFKfOWs2aSVJJfP1dL8g5UiqGImYO3rZbz/9QCzXOeMVIZ5n3iaRyKMNhFFr84/zUja2H7Dw==", + "peer": true + }, "node_modules/@pixi/extract": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-6.0.4.tgz", @@ -639,11 +1026,220 @@ "url": "^0.11.0" } }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "peer": true + }, "node_modules/@types/earcut": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz", "integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==" }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==", + "peer": true + }, "node_modules/a11y-dialog": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.5.0.tgz", @@ -674,16 +1270,9 @@ } }, "node_modules/adler-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", - "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "adler32": "bin/adler32.njs" - }, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", "engines": { "node": ">=0.8" } @@ -799,13 +1388,12 @@ } }, "node_modules/cfb": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz", - "integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", "dependencies": { - "adler-32": "~1.2.0", - "crc-32": "~1.2.0", - "printj": "~1.1.2" + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" }, "engines": { "node": ">=0.8" @@ -835,25 +1423,13 @@ } }, "node_modules/codepage": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", - "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=", - "dependencies": { - "commander": "~2.14.1", - "exit-on-epipe": "~1.0.1" - }, - "bin": { - "codepage": "bin/codepage.njs" - }, + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", "engines": { "node": ">=0.8" } }, - "node_modules/codepage/node_modules/commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -869,6 +1445,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -876,13 +1458,9 @@ "dev": true }, "node_modules/crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "bin": { "crc32": "bin/crc32.njs" }, @@ -957,9 +1535,9 @@ } }, "node_modules/earcut": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", - "integrity": "sha512-eZoZPPJcUHnfRZ0PjLvx2qBordSiO8ofC3vt+qACLM95u+4DovnbYNpQtJh0DNsWj8RnxrQytD4WA8gj5cRIaQ==" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -1405,6 +1983,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1419,14 +2003,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1445,11 +2021,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "node_modules/fflate": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", - "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==" - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1500,12 +2071,34 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "dependencies": { + "js-binary-schema-parser": "^2.0.3" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1624,6 +2217,11 @@ "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==" }, + "node_modules/js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1856,6 +2454,24 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -1954,6 +2570,427 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixi-filters": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-5.0.0.tgz", + "integrity": "sha512-j90nvbiRpDozxalSUaQ2kTIyFNGAUKxJ2qhPs4ThmVLiR9lam5x+GpP+c1Yx5N+qc+u0tH5G3VRY1usB69atrw==", + "dependencies": { + "@pixi/filter-adjustment": "5.0.0", + "@pixi/filter-advanced-bloom": "5.0.0", + "@pixi/filter-ascii": "5.0.0", + "@pixi/filter-bevel": "5.0.0", + "@pixi/filter-bloom": "5.0.0", + "@pixi/filter-bulge-pinch": "5.0.0", + "@pixi/filter-color-map": "5.0.0", + "@pixi/filter-color-overlay": "5.0.0", + "@pixi/filter-color-replace": "5.0.0", + "@pixi/filter-convolution": "5.0.0", + "@pixi/filter-cross-hatch": "5.0.0", + "@pixi/filter-crt": "5.0.0", + "@pixi/filter-dot": "5.0.0", + "@pixi/filter-drop-shadow": "5.0.0", + "@pixi/filter-emboss": "5.0.0", + "@pixi/filter-glitch": "5.0.0", + "@pixi/filter-glow": "5.0.0", + "@pixi/filter-godray": "5.0.0", + "@pixi/filter-kawase-blur": "5.0.0", + "@pixi/filter-motion-blur": "5.0.0", + "@pixi/filter-multi-color-replace": "5.0.0", + "@pixi/filter-old-film": "5.0.0", + "@pixi/filter-outline": "5.0.0", + "@pixi/filter-pixelate": "5.0.0", + "@pixi/filter-radial-blur": "5.0.0", + "@pixi/filter-reflection": "5.0.0", + "@pixi/filter-rgb-split": "5.0.0", + "@pixi/filter-shockwave": "5.0.0", + "@pixi/filter-simple-lightmap": "5.0.0", + "@pixi/filter-tilt-shift": "5.0.0", + "@pixi/filter-twist": "5.0.0", + "@pixi/filter-zoom-blur": "5.0.0" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/constants": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.2.4.tgz", + "integrity": "sha512-hKuHBWR6N4Q0Sf5MGF3/9l+POg/G5rqhueHfzofiuelnKg7aBs3BVjjZ+6hZbd6M++vOUmxYelEX/NEFBxrheA==", + "peer": true + }, + "node_modules/pixi-filters/node_modules/@pixi/core": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.2.4.tgz", + "integrity": "sha512-0XtvrfxHlS2T+beBBSpo7GI8+QLyyTqMVQpNmPqB4woYxzrOEJ9JaUFBaBfCvycLeUkfVih1u6HAbtF+2d1EjQ==", + "peer": true, + "dependencies": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/extensions": "7.2.4", + "@pixi/math": "7.2.4", + "@pixi/runner": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/ticker": "7.2.4", + "@pixi/utils": "7.2.4", + "@types/offscreencanvas": "^2019.6.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-adjustment": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-5.0.0.tgz", + "integrity": "sha512-Epci8zSWCNWhFtnarvQqOcnmOqLfhXIJ7NNENEi2E1rom1Ar13RLM76CBGBbuDRK7flweqcWmZb0QZLxqwxTDg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-advanced-bloom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-advanced-bloom/-/filter-advanced-bloom-5.0.0.tgz", + "integrity": "sha512-P5Xt65GLBEqjZVUkLe4ZZk4D1/j9UEXYnYFG3JrLPYkdcniwD4Y+NIyNCJ+eP91ivgoCmK/+SyBRv0P0AEQkTw==", + "dependencies": { + "@pixi/filter-kawase-blur": "5.0.0" + }, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-alpha": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.2.4.tgz", + "integrity": "sha512-UTUMSGyktUr+I9vmigqJo9iUhb0nwGyqTTME2xBWZvVGCnl5z+/wHxvIBBCe5pNZ66IM15pGXQ4cDcfqCuP2kA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-ascii": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-ascii/-/filter-ascii-5.0.0.tgz", + "integrity": "sha512-A49yNhiye/aFDOnI11zwEm/td2xho0td/Cvzvru8FUgi1MzJvZE03W/JoLl04ToZczw143wFPxutl6V/Ohw5bQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-bevel": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bevel/-/filter-bevel-5.0.0.tgz", + "integrity": "sha512-0Odat0tW/uoS/uyp0rigm07Q3YPgwKLTgkZZZSzIUVsPnwcJjiocSzWel73JkiY3m2ZjTrj+JZjkyGjkYH+2gQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-bloom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bloom/-/filter-bloom-5.0.0.tgz", + "integrity": "sha512-vOSNJNV5y+ifwQWfzEmml3owcgoJAQIQtMR17SELBUwfYP60qxy5bNWBdYBlipSJVwX2AuGi8Xk5Ia9dijcqZQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X", + "@pixi/filter-alpha": "^7.0.0-X", + "@pixi/filter-blur": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-blur": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.2.4.tgz", + "integrity": "sha512-aLyXIoxy14bTansCPtbY8x7Sdn2OrrqkF/pcKiRXHJGGhi7wPacvB/NcmYJdnI/n2ExQ6V5Njuj/nfrsejVwcA==", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-bulge-pinch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bulge-pinch/-/filter-bulge-pinch-5.0.0.tgz", + "integrity": "sha512-j1feWsCpyTZk4aHbYNjax52lt0OtyYDbHvYaePYzGO/SBb1t/spDnHQEkAP7R3bZ7Ud/GI4RgefAFnvsYeSetQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-color-map": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-map/-/filter-color-map-5.0.0.tgz", + "integrity": "sha512-w77mRi89sLUMwjhl7qL/q1YrhEKyOk2MJZQdKBksvGEV/Mf5mV2h3+EOC62wB18Q4iUVQy1MS4sANyVaCctu2w==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-color-overlay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-overlay/-/filter-color-overlay-5.0.0.tgz", + "integrity": "sha512-AjxVN6gnZ+xCryQUmI+TVy3yVF+CcLgDPv+nSVPDlQowuqYhZjD6qSzgRCl3Kezdi3AxrL1vi1fnBudEnzdDJg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-color-replace": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-replace/-/filter-color-replace-5.0.0.tgz", + "integrity": "sha512-u4VOtKbY6SSr2P9v5AL8/2MVsUcAH9z92c1eaqeE3PXCPNyCgZKuNHWl8+FjBIDl/1UMQVhXH2zNrC3Vuqo3JA==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-convolution": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-convolution/-/filter-convolution-5.0.0.tgz", + "integrity": "sha512-SYjyKXODdHbjzBP9c5QGMOfowNwkNFi7zW1XzGwEadmv6mLHNanO3nm0PtRu/3B9B6AW1fvOaUecYmhjAZfQjg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-cross-hatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-cross-hatch/-/filter-cross-hatch-5.0.0.tgz", + "integrity": "sha512-J4bcI3MUc/Ol3nQIsXZldYEtiLAl3ktU28zlidwffkANyl/XjP76bLEgFBoc4RE2iP/FQ+9ZeEqpsN8DIg6vVg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-crt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-crt/-/filter-crt-5.0.0.tgz", + "integrity": "sha512-/kgjNW+BCCVtUa0s8Usk3WyxgBX8kelAiqkyVnM1g8xM19Dh2689gK2wjx0ibS0p74EHs42QpkJj7jTL+1MS7A==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-dot": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-dot/-/filter-dot-5.0.0.tgz", + "integrity": "sha512-kytardK58Ifl5D8Ss3kkfI29FMzV3+npJYr5GAKnA80R7XGOPOMoxrknhou8y+Dw9LUcOv8y643wryvL43P2vw==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-drop-shadow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-drop-shadow/-/filter-drop-shadow-5.0.0.tgz", + "integrity": "sha512-kz2eL+ikCLL7/2RICyIkw3pZXkyMY0Ji6skhnPj7JaZSjH4V+7TiKqYXp532gTbwSRj/mzLCvFfOL3WwTDgZ1w==", + "dependencies": { + "@pixi/filter-kawase-blur": "5.0.0" + }, + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-emboss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-emboss/-/filter-emboss-5.0.0.tgz", + "integrity": "sha512-wvrk9zB62lGaPcCWbTwoaO48FrLIE4+hi02BVS+exx5RvIniNUJD/ledGxdmUjcHX/2mDIIs7PH0kAs1L/ziZw==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-glitch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-glitch/-/filter-glitch-5.0.0.tgz", + "integrity": "sha512-yK3plqExyQp9eo3dwV03dnSHpQgh0xeD112ieAsqefrAOLc5AXSfTelPvEQaZ07ZkcxSDE5eqKcRvcIVi2IgLQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-glow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-5.0.0.tgz", + "integrity": "sha512-D+YE9DGSJXtmZa6aoWJfuNu+6MnSw90GP7oRRzr7S1/4moeFZ7EWbvQehl9Y9j98idHG87Cvuh6mmsRqpgS6ow==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-godray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-godray/-/filter-godray-5.0.0.tgz", + "integrity": "sha512-L4PD3cysUMjTSDYk5q5xUtal9q6kfH8NVIdNT3aTDJpR0VW4b/ClanmOTFpJVzN6Ld/JlJbdg8ogUpXBe1gVuw==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-kawase-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-kawase-blur/-/filter-kawase-blur-5.0.0.tgz", + "integrity": "sha512-dKSTaPUOvdVkfx9x+kp0TzYjGAl8CLxIRGz6Wh43NKx96nVqd/lWqvlda+zloHVgZyQoJNHZZ4Spjcw2mYoaWg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-motion-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-motion-blur/-/filter-motion-blur-5.0.0.tgz", + "integrity": "sha512-2av4dnVL1uyyCKF8RlZaMfeO8YnQwA893j24S15ubWHZaz4WlWH3lFIYmCMqlEqHPlFDBER4vLxpR1WjsUsX/Q==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-multi-color-replace": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-multi-color-replace/-/filter-multi-color-replace-5.0.0.tgz", + "integrity": "sha512-hcmCKFFQ1baGDrZc/blK9zWpe3f02rqWGsPx5VRRgc1sk44UYXHCKZnDjF80/g0ls8U4Lj+/5Xb7HOQq2LyyDg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-old-film": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-old-film/-/filter-old-film-5.0.0.tgz", + "integrity": "sha512-XSHBz4JDbvYtUrf/NP5eKCw/wvaKTAKXQENDxk480tKYtDuteSCMg87ZjLrPlyKtGySW8KTmdzl58bZjSYpiyA==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-outline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-outline/-/filter-outline-5.0.0.tgz", + "integrity": "sha512-efS3Or7VQFXo2ZyPFR2M/JlZrcLAxeVbOTPYvgKe574yUghSQbQ/pyqDWE16tRB/W7+osMrTV0+C4/N/9wIxhQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-pixelate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-pixelate/-/filter-pixelate-5.0.0.tgz", + "integrity": "sha512-3g1ajOLsYy+x0FCC67WhDcjixrcBlhK3Zo+JP9zlHSxh0W4yNzfhsw9EsIb9XP4WnMtMAUMg5T0MLTnjbsrK4g==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-radial-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-radial-blur/-/filter-radial-blur-5.0.0.tgz", + "integrity": "sha512-zafBJCAiqRtsTNGKiQ8iMt00KbG20qtBi71h286wWbr0na37iXsRcg4EN76eyNbpfAOX+1ylBgIuSd9hLyQBFA==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-reflection": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-reflection/-/filter-reflection-5.0.0.tgz", + "integrity": "sha512-PuZe19XUq0gTdmAStu3hcyGKkNlKGrpblN4s6vJmV+vAKVcFv2OpfjtuGUXcP/oi2LmLakC/vKfEx4bDgZzz+w==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-rgb-split": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-rgb-split/-/filter-rgb-split-5.0.0.tgz", + "integrity": "sha512-zsWBrDkj9EdjJRPjGCt/0O2Vx/8Gt+8VTmjRA0ONoegcMD9slJdJMgL9EbH/1y5WHgmzGbgZIPvWULIqepVxBQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-shockwave": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-shockwave/-/filter-shockwave-5.0.0.tgz", + "integrity": "sha512-aL0ExAkJGcUo463Ktq4HXjZGlJDpoYcyZhwd87maJrFsBjQZl2gopse6bEsy7IJxbAKzlpUKFmAP9rxwZWqMVQ==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-simple-lightmap": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-simple-lightmap/-/filter-simple-lightmap-5.0.0.tgz", + "integrity": "sha512-0WIKQIGZ3aNafe2VZIbGQJWxSlBMbmjM9J+Tswjaeg8Z1dz6Qux5lYIC16wZOaIqVlWL5GTpfn8HU0BHCOvESA==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-tilt-shift": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-tilt-shift/-/filter-tilt-shift-5.0.0.tgz", + "integrity": "sha512-nIxYoTU9kFDx3EE1fyoIEOfAia9Tvoj+sakTKCJZUvTk+5tjpZdAm+Ump42cnb6UxTR8AMTQiwH54C7I0pbA4Q==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-twist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-twist/-/filter-twist-5.0.0.tgz", + "integrity": "sha512-YVtz3ZPfvaz22gZRZo+cOC0/L6SgSZmr/HEa6Ir+BRNVqLff6CpPx6YBVJqPREh+HFZjDomSP0kf5JasQYhzSg==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/filter-zoom-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-zoom-blur/-/filter-zoom-blur-5.0.0.tgz", + "integrity": "sha512-Q1ftuY/KPgbVtJHCvl0p4hrwVWRMWZ/yX1YRjdLGSyOwMEN8u16MEEXFQUtixEHY7+MBRBWaPOaXBaQrd+Xq7A==", + "peerDependencies": { + "@pixi/core": "^7.0.0-X" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/math": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.2.4.tgz", + "integrity": "sha512-LJB+mozyEPllxa0EssFZrKNfVwysfaBun4b2dJKQQInp0DafgbA0j7A+WVg0oe51KhFULTJMpDqbLn/ITFc41A==", + "peer": true + }, + "node_modules/pixi-filters/node_modules/@pixi/runner": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.2.4.tgz", + "integrity": "sha512-YtyqPk1LA+0guEFKSFx6t/YSvbEQwajFwi4Ft8iDhioa6VK2MmTir1GjWwy7JQYLcDmYSAcQjnmFtVTZohyYSw==", + "peer": true + }, + "node_modules/pixi-filters/node_modules/@pixi/settings": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.2.4.tgz", + "integrity": "sha512-ZPKRar9EwibijGmH8EViu4Greq1I/O7V/xQx2rNqN23XA7g09Qo6yfaeQpufu5xl8+/lZrjuHtQSnuY7OgG1CA==", + "peer": true, + "dependencies": { + "@pixi/constants": "7.2.4", + "@types/css-font-loading-module": "^0.0.7", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/ticker": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.2.4.tgz", + "integrity": "sha512-hQQHIHvGeFsP4GNezZqjzuhUgNQEVgCH9+qU05UX1Mc5UHC9l6OJnY4VTVhhcHxZjA6RnyaY+1zBxCnoXuazpg==", + "peer": true, + "dependencies": { + "@pixi/extensions": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/utils": "7.2.4" + } + }, + "node_modules/pixi-filters/node_modules/@pixi/utils": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.2.4.tgz", + "integrity": "sha512-VUGQHBOINIS4ePzoqafwxaGPVRTa3oM/mEutIIHbNGI3b+QvSO+1Dnk40M0zcH6Bo+MxQZbOZK5X/wO9oU5+LQ==", + "peer": true, + "dependencies": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/settings": "7.2.4", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, + "node_modules/pixi-filters/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "peer": true + }, "node_modules/pixi.js": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-6.0.4.tgz", @@ -2022,6 +3059,34 @@ "url": "https://opencollective.com/pixijs" } }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2031,17 +3096,6 @@ "node": ">= 0.8.0" } }, - "node_modules/printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -2132,6 +3186,38 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", + "fsevents": "~2.3.2" + } + }, "node_modules/seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -2187,6 +3273,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2382,6 +3477,115 @@ "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, + "node_modules/vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-glsl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.2.1.tgz", + "integrity": "sha512-yBpBHWfdiRVMxN3yIKx4qmwuqMwoMAnEMipVI0NbdIieyRFO8hpW8VTFHYi3W75h7CkvsotteP9C4pln51OE0A==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.2" + }, + "engines": { + "node": ">= 16.15.1", + "npm": ">= 8.11.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, "node_modules/wmf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", @@ -2414,17 +3618,14 @@ "dev": true }, "node_modules/xlsx": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.0.tgz", - "integrity": "sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A==", + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", "dependencies": { - "adler-32": "~1.2.0", - "cfb": "^1.1.4", - "codepage": "~1.14.0", - "commander": "~2.17.1", - "crc-32": "~1.2.0", - "exit-on-epipe": "~1.0.1", - "fflate": "^0.3.8", + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" @@ -2436,11 +3637,6 @@ "node": ">=0.8" } }, - "node_modules/xlsx/node_modules/commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" - }, "node_modules/xmlcreate": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", @@ -2495,6 +3691,167 @@ "regenerator-runtime": "^0.13.4" } }, + "@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", @@ -2682,6 +4039,15 @@ "@pixi/text": "6.0.4" } }, + "@pixi/color": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.2.4.tgz", + "integrity": "sha512-B/+9JRcXe2uE8wQfsueFRPZVayF2VEMRB7XGeRAsWCryOX19nmWhv0Nt3nOU2rvzI0niz9XgugJXsB6vVmDFSg==", + "peer": true, + "requires": { + "colord": "^2.9.3" + } + }, "@pixi/compressed-textures": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-6.0.4.tgz", @@ -2721,6 +4087,12 @@ "@pixi/utils": "6.0.4" } }, + "@pixi/extensions": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.2.4.tgz", + "integrity": "sha512-Mnqv9scbL1ARD3QFKfOWs2aSVJJfP1dL8g5UiqGImYO3rZbz/9QCzXOeMVIZ5n3iaRyKMNhFFr84/zUja2H7Dw==", + "peer": true + }, "@pixi/extract": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-6.0.4.tgz", @@ -3026,11 +4398,131 @@ "url": "^0.11.0" } }, + "@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "dev": true, + "optional": true + }, + "@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "peer": true + }, "@types/earcut": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz", "integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==" }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==", + "peer": true + }, "a11y-dialog": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.5.0.tgz", @@ -3053,13 +4545,9 @@ "requires": {} }, "adler-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", - "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==" }, "ajv": { "version": "6.12.6", @@ -3150,13 +4638,12 @@ } }, "cfb": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz", - "integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", "requires": { - "adler-32": "~1.2.0", - "crc-32": "~1.2.0", - "printj": "~1.1.2" + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" } }, "chalk": { @@ -3177,20 +4664,9 @@ "dev": true }, "codepage": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", - "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=", - "requires": { - "commander": "~2.14.1", - "exit-on-epipe": "~1.0.1" - }, - "dependencies": { - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - } - } + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==" }, "color-convert": { "version": "1.9.3", @@ -3207,6 +4683,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "peer": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3214,13 +4696,9 @@ "dev": true }, "crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" }, "csslint": { "version": "1.0.5", @@ -3268,9 +4746,9 @@ "dev": true }, "earcut": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", - "integrity": "sha512-eZoZPPJcUHnfRZ0PjLvx2qBordSiO8ofC3vt+qACLM95u+4DovnbYNpQtJh0DNsWj8RnxrQytD4WA8gj5cRIaQ==" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" }, "emoji-regex": { "version": "8.0.0", @@ -3587,6 +5065,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3598,11 +5082,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3621,11 +5100,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "fflate": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", - "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==" - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3667,12 +5141,27 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "requires": { + "js-binary-schema-parser": "^2.0.3" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -3767,6 +5256,11 @@ "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==" }, + "js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3965,6 +5459,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4044,6 +5544,347 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pixi-filters": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-5.0.0.tgz", + "integrity": "sha512-j90nvbiRpDozxalSUaQ2kTIyFNGAUKxJ2qhPs4ThmVLiR9lam5x+GpP+c1Yx5N+qc+u0tH5G3VRY1usB69atrw==", + "requires": { + "@pixi/filter-adjustment": "5.0.0", + "@pixi/filter-advanced-bloom": "5.0.0", + "@pixi/filter-ascii": "5.0.0", + "@pixi/filter-bevel": "5.0.0", + "@pixi/filter-bloom": "5.0.0", + "@pixi/filter-bulge-pinch": "5.0.0", + "@pixi/filter-color-map": "5.0.0", + "@pixi/filter-color-overlay": "5.0.0", + "@pixi/filter-color-replace": "5.0.0", + "@pixi/filter-convolution": "5.0.0", + "@pixi/filter-cross-hatch": "5.0.0", + "@pixi/filter-crt": "5.0.0", + "@pixi/filter-dot": "5.0.0", + "@pixi/filter-drop-shadow": "5.0.0", + "@pixi/filter-emboss": "5.0.0", + "@pixi/filter-glitch": "5.0.0", + "@pixi/filter-glow": "5.0.0", + "@pixi/filter-godray": "5.0.0", + "@pixi/filter-kawase-blur": "5.0.0", + "@pixi/filter-motion-blur": "5.0.0", + "@pixi/filter-multi-color-replace": "5.0.0", + "@pixi/filter-old-film": "5.0.0", + "@pixi/filter-outline": "5.0.0", + "@pixi/filter-pixelate": "5.0.0", + "@pixi/filter-radial-blur": "5.0.0", + "@pixi/filter-reflection": "5.0.0", + "@pixi/filter-rgb-split": "5.0.0", + "@pixi/filter-shockwave": "5.0.0", + "@pixi/filter-simple-lightmap": "5.0.0", + "@pixi/filter-tilt-shift": "5.0.0", + "@pixi/filter-twist": "5.0.0", + "@pixi/filter-zoom-blur": "5.0.0" + }, + "dependencies": { + "@pixi/constants": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.2.4.tgz", + "integrity": "sha512-hKuHBWR6N4Q0Sf5MGF3/9l+POg/G5rqhueHfzofiuelnKg7aBs3BVjjZ+6hZbd6M++vOUmxYelEX/NEFBxrheA==", + "peer": true + }, + "@pixi/core": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.2.4.tgz", + "integrity": "sha512-0XtvrfxHlS2T+beBBSpo7GI8+QLyyTqMVQpNmPqB4woYxzrOEJ9JaUFBaBfCvycLeUkfVih1u6HAbtF+2d1EjQ==", + "peer": true, + "requires": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/extensions": "7.2.4", + "@pixi/math": "7.2.4", + "@pixi/runner": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/ticker": "7.2.4", + "@pixi/utils": "7.2.4", + "@types/offscreencanvas": "^2019.6.4" + } + }, + "@pixi/filter-adjustment": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-5.0.0.tgz", + "integrity": "sha512-Epci8zSWCNWhFtnarvQqOcnmOqLfhXIJ7NNENEi2E1rom1Ar13RLM76CBGBbuDRK7flweqcWmZb0QZLxqwxTDg==", + "requires": {} + }, + "@pixi/filter-advanced-bloom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-advanced-bloom/-/filter-advanced-bloom-5.0.0.tgz", + "integrity": "sha512-P5Xt65GLBEqjZVUkLe4ZZk4D1/j9UEXYnYFG3JrLPYkdcniwD4Y+NIyNCJ+eP91ivgoCmK/+SyBRv0P0AEQkTw==", + "requires": { + "@pixi/filter-kawase-blur": "5.0.0" + } + }, + "@pixi/filter-alpha": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.2.4.tgz", + "integrity": "sha512-UTUMSGyktUr+I9vmigqJo9iUhb0nwGyqTTME2xBWZvVGCnl5z+/wHxvIBBCe5pNZ66IM15pGXQ4cDcfqCuP2kA==", + "peer": true, + "requires": {} + }, + "@pixi/filter-ascii": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-ascii/-/filter-ascii-5.0.0.tgz", + "integrity": "sha512-A49yNhiye/aFDOnI11zwEm/td2xho0td/Cvzvru8FUgi1MzJvZE03W/JoLl04ToZczw143wFPxutl6V/Ohw5bQ==", + "requires": {} + }, + "@pixi/filter-bevel": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bevel/-/filter-bevel-5.0.0.tgz", + "integrity": "sha512-0Odat0tW/uoS/uyp0rigm07Q3YPgwKLTgkZZZSzIUVsPnwcJjiocSzWel73JkiY3m2ZjTrj+JZjkyGjkYH+2gQ==", + "requires": {} + }, + "@pixi/filter-bloom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bloom/-/filter-bloom-5.0.0.tgz", + "integrity": "sha512-vOSNJNV5y+ifwQWfzEmml3owcgoJAQIQtMR17SELBUwfYP60qxy5bNWBdYBlipSJVwX2AuGi8Xk5Ia9dijcqZQ==", + "requires": {} + }, + "@pixi/filter-blur": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.2.4.tgz", + "integrity": "sha512-aLyXIoxy14bTansCPtbY8x7Sdn2OrrqkF/pcKiRXHJGGhi7wPacvB/NcmYJdnI/n2ExQ6V5Njuj/nfrsejVwcA==", + "peer": true, + "requires": {} + }, + "@pixi/filter-bulge-pinch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-bulge-pinch/-/filter-bulge-pinch-5.0.0.tgz", + "integrity": "sha512-j1feWsCpyTZk4aHbYNjax52lt0OtyYDbHvYaePYzGO/SBb1t/spDnHQEkAP7R3bZ7Ud/GI4RgefAFnvsYeSetQ==", + "requires": {} + }, + "@pixi/filter-color-map": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-map/-/filter-color-map-5.0.0.tgz", + "integrity": "sha512-w77mRi89sLUMwjhl7qL/q1YrhEKyOk2MJZQdKBksvGEV/Mf5mV2h3+EOC62wB18Q4iUVQy1MS4sANyVaCctu2w==", + "requires": {} + }, + "@pixi/filter-color-overlay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-overlay/-/filter-color-overlay-5.0.0.tgz", + "integrity": "sha512-AjxVN6gnZ+xCryQUmI+TVy3yVF+CcLgDPv+nSVPDlQowuqYhZjD6qSzgRCl3Kezdi3AxrL1vi1fnBudEnzdDJg==", + "requires": {} + }, + "@pixi/filter-color-replace": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-replace/-/filter-color-replace-5.0.0.tgz", + "integrity": "sha512-u4VOtKbY6SSr2P9v5AL8/2MVsUcAH9z92c1eaqeE3PXCPNyCgZKuNHWl8+FjBIDl/1UMQVhXH2zNrC3Vuqo3JA==", + "requires": {} + }, + "@pixi/filter-convolution": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-convolution/-/filter-convolution-5.0.0.tgz", + "integrity": "sha512-SYjyKXODdHbjzBP9c5QGMOfowNwkNFi7zW1XzGwEadmv6mLHNanO3nm0PtRu/3B9B6AW1fvOaUecYmhjAZfQjg==", + "requires": {} + }, + "@pixi/filter-cross-hatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-cross-hatch/-/filter-cross-hatch-5.0.0.tgz", + "integrity": "sha512-J4bcI3MUc/Ol3nQIsXZldYEtiLAl3ktU28zlidwffkANyl/XjP76bLEgFBoc4RE2iP/FQ+9ZeEqpsN8DIg6vVg==", + "requires": {} + }, + "@pixi/filter-crt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-crt/-/filter-crt-5.0.0.tgz", + "integrity": "sha512-/kgjNW+BCCVtUa0s8Usk3WyxgBX8kelAiqkyVnM1g8xM19Dh2689gK2wjx0ibS0p74EHs42QpkJj7jTL+1MS7A==", + "requires": {} + }, + "@pixi/filter-dot": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-dot/-/filter-dot-5.0.0.tgz", + "integrity": "sha512-kytardK58Ifl5D8Ss3kkfI29FMzV3+npJYr5GAKnA80R7XGOPOMoxrknhou8y+Dw9LUcOv8y643wryvL43P2vw==", + "requires": {} + }, + "@pixi/filter-drop-shadow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-drop-shadow/-/filter-drop-shadow-5.0.0.tgz", + "integrity": "sha512-kz2eL+ikCLL7/2RICyIkw3pZXkyMY0Ji6skhnPj7JaZSjH4V+7TiKqYXp532gTbwSRj/mzLCvFfOL3WwTDgZ1w==", + "requires": { + "@pixi/filter-kawase-blur": "5.0.0" + } + }, + "@pixi/filter-emboss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-emboss/-/filter-emboss-5.0.0.tgz", + "integrity": "sha512-wvrk9zB62lGaPcCWbTwoaO48FrLIE4+hi02BVS+exx5RvIniNUJD/ledGxdmUjcHX/2mDIIs7PH0kAs1L/ziZw==", + "requires": {} + }, + "@pixi/filter-glitch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-glitch/-/filter-glitch-5.0.0.tgz", + "integrity": "sha512-yK3plqExyQp9eo3dwV03dnSHpQgh0xeD112ieAsqefrAOLc5AXSfTelPvEQaZ07ZkcxSDE5eqKcRvcIVi2IgLQ==", + "requires": {} + }, + "@pixi/filter-glow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-5.0.0.tgz", + "integrity": "sha512-D+YE9DGSJXtmZa6aoWJfuNu+6MnSw90GP7oRRzr7S1/4moeFZ7EWbvQehl9Y9j98idHG87Cvuh6mmsRqpgS6ow==", + "requires": {} + }, + "@pixi/filter-godray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-godray/-/filter-godray-5.0.0.tgz", + "integrity": "sha512-L4PD3cysUMjTSDYk5q5xUtal9q6kfH8NVIdNT3aTDJpR0VW4b/ClanmOTFpJVzN6Ld/JlJbdg8ogUpXBe1gVuw==", + "requires": {} + }, + "@pixi/filter-kawase-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-kawase-blur/-/filter-kawase-blur-5.0.0.tgz", + "integrity": "sha512-dKSTaPUOvdVkfx9x+kp0TzYjGAl8CLxIRGz6Wh43NKx96nVqd/lWqvlda+zloHVgZyQoJNHZZ4Spjcw2mYoaWg==", + "requires": {} + }, + "@pixi/filter-motion-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-motion-blur/-/filter-motion-blur-5.0.0.tgz", + "integrity": "sha512-2av4dnVL1uyyCKF8RlZaMfeO8YnQwA893j24S15ubWHZaz4WlWH3lFIYmCMqlEqHPlFDBER4vLxpR1WjsUsX/Q==", + "requires": {} + }, + "@pixi/filter-multi-color-replace": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-multi-color-replace/-/filter-multi-color-replace-5.0.0.tgz", + "integrity": "sha512-hcmCKFFQ1baGDrZc/blK9zWpe3f02rqWGsPx5VRRgc1sk44UYXHCKZnDjF80/g0ls8U4Lj+/5Xb7HOQq2LyyDg==", + "requires": {} + }, + "@pixi/filter-old-film": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-old-film/-/filter-old-film-5.0.0.tgz", + "integrity": "sha512-XSHBz4JDbvYtUrf/NP5eKCw/wvaKTAKXQENDxk480tKYtDuteSCMg87ZjLrPlyKtGySW8KTmdzl58bZjSYpiyA==", + "requires": {} + }, + "@pixi/filter-outline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-outline/-/filter-outline-5.0.0.tgz", + "integrity": "sha512-efS3Or7VQFXo2ZyPFR2M/JlZrcLAxeVbOTPYvgKe574yUghSQbQ/pyqDWE16tRB/W7+osMrTV0+C4/N/9wIxhQ==", + "requires": {} + }, + "@pixi/filter-pixelate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-pixelate/-/filter-pixelate-5.0.0.tgz", + "integrity": "sha512-3g1ajOLsYy+x0FCC67WhDcjixrcBlhK3Zo+JP9zlHSxh0W4yNzfhsw9EsIb9XP4WnMtMAUMg5T0MLTnjbsrK4g==", + "requires": {} + }, + "@pixi/filter-radial-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-radial-blur/-/filter-radial-blur-5.0.0.tgz", + "integrity": "sha512-zafBJCAiqRtsTNGKiQ8iMt00KbG20qtBi71h286wWbr0na37iXsRcg4EN76eyNbpfAOX+1ylBgIuSd9hLyQBFA==", + "requires": {} + }, + "@pixi/filter-reflection": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-reflection/-/filter-reflection-5.0.0.tgz", + "integrity": "sha512-PuZe19XUq0gTdmAStu3hcyGKkNlKGrpblN4s6vJmV+vAKVcFv2OpfjtuGUXcP/oi2LmLakC/vKfEx4bDgZzz+w==", + "requires": {} + }, + "@pixi/filter-rgb-split": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-rgb-split/-/filter-rgb-split-5.0.0.tgz", + "integrity": "sha512-zsWBrDkj9EdjJRPjGCt/0O2Vx/8Gt+8VTmjRA0ONoegcMD9slJdJMgL9EbH/1y5WHgmzGbgZIPvWULIqepVxBQ==", + "requires": {} + }, + "@pixi/filter-shockwave": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-shockwave/-/filter-shockwave-5.0.0.tgz", + "integrity": "sha512-aL0ExAkJGcUo463Ktq4HXjZGlJDpoYcyZhwd87maJrFsBjQZl2gopse6bEsy7IJxbAKzlpUKFmAP9rxwZWqMVQ==", + "requires": {} + }, + "@pixi/filter-simple-lightmap": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-simple-lightmap/-/filter-simple-lightmap-5.0.0.tgz", + "integrity": "sha512-0WIKQIGZ3aNafe2VZIbGQJWxSlBMbmjM9J+Tswjaeg8Z1dz6Qux5lYIC16wZOaIqVlWL5GTpfn8HU0BHCOvESA==", + "requires": {} + }, + "@pixi/filter-tilt-shift": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-tilt-shift/-/filter-tilt-shift-5.0.0.tgz", + "integrity": "sha512-nIxYoTU9kFDx3EE1fyoIEOfAia9Tvoj+sakTKCJZUvTk+5tjpZdAm+Ump42cnb6UxTR8AMTQiwH54C7I0pbA4Q==", + "requires": {} + }, + "@pixi/filter-twist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-twist/-/filter-twist-5.0.0.tgz", + "integrity": "sha512-YVtz3ZPfvaz22gZRZo+cOC0/L6SgSZmr/HEa6Ir+BRNVqLff6CpPx6YBVJqPREh+HFZjDomSP0kf5JasQYhzSg==", + "requires": {} + }, + "@pixi/filter-zoom-blur": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-zoom-blur/-/filter-zoom-blur-5.0.0.tgz", + "integrity": "sha512-Q1ftuY/KPgbVtJHCvl0p4hrwVWRMWZ/yX1YRjdLGSyOwMEN8u16MEEXFQUtixEHY7+MBRBWaPOaXBaQrd+Xq7A==", + "requires": {} + }, + "@pixi/math": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.2.4.tgz", + "integrity": "sha512-LJB+mozyEPllxa0EssFZrKNfVwysfaBun4b2dJKQQInp0DafgbA0j7A+WVg0oe51KhFULTJMpDqbLn/ITFc41A==", + "peer": true + }, + "@pixi/runner": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.2.4.tgz", + "integrity": "sha512-YtyqPk1LA+0guEFKSFx6t/YSvbEQwajFwi4Ft8iDhioa6VK2MmTir1GjWwy7JQYLcDmYSAcQjnmFtVTZohyYSw==", + "peer": true + }, + "@pixi/settings": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.2.4.tgz", + "integrity": "sha512-ZPKRar9EwibijGmH8EViu4Greq1I/O7V/xQx2rNqN23XA7g09Qo6yfaeQpufu5xl8+/lZrjuHtQSnuY7OgG1CA==", + "peer": true, + "requires": { + "@pixi/constants": "7.2.4", + "@types/css-font-loading-module": "^0.0.7", + "ismobilejs": "^1.1.0" + } + }, + "@pixi/ticker": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.2.4.tgz", + "integrity": "sha512-hQQHIHvGeFsP4GNezZqjzuhUgNQEVgCH9+qU05UX1Mc5UHC9l6OJnY4VTVhhcHxZjA6RnyaY+1zBxCnoXuazpg==", + "peer": true, + "requires": { + "@pixi/extensions": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/utils": "7.2.4" + } + }, + "@pixi/utils": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.2.4.tgz", + "integrity": "sha512-VUGQHBOINIS4ePzoqafwxaGPVRTa3oM/mEutIIHbNGI3b+QvSO+1Dnk40M0zcH6Bo+MxQZbOZK5X/wO9oU5+LQ==", + "peer": true, + "requires": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/settings": "7.2.4", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "peer": true + } + } + }, "pixi.js": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-6.0.4.tgz", @@ -4104,17 +5945,23 @@ "pixi.js": "6.0.4" } }, + "postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -4181,6 +6028,29 @@ "glob": "^7.1.3" } }, + "rollup": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@types/estree": "1.0.5", + "fsevents": "~2.3.2" + } + }, "seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -4223,6 +6093,12 @@ } } }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4394,6 +6270,60 @@ "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, + "vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "dev": true, + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "dependencies": { + "esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + } + } + }, + "vite-plugin-glsl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.2.1.tgz", + "integrity": "sha512-yBpBHWfdiRVMxN3yIKx4qmwuqMwoMAnEMipVI0NbdIieyRFO8hpW8VTFHYi3W75h7CkvsotteP9C4pln51OE0A==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.2" + } + }, "wmf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", @@ -4417,27 +6347,17 @@ "dev": true }, "xlsx": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.0.tgz", - "integrity": "sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A==", + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", "requires": { - "adler-32": "~1.2.0", - "cfb": "^1.1.4", - "codepage": "~1.14.0", - "commander": "~2.17.1", - "crc-32": "~1.2.0", - "exit-on-epipe": "~1.0.1", - "fflate": "^0.3.8", + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" - }, - "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" - } } }, "xmlcreate": { diff --git a/package.json b/package.json index 8c7e2cb..f1476bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psychojs", - "version": "2022.3.1", + "version": "2024.1.0", "private": true, "description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments", "license": "MIT", @@ -15,6 +15,9 @@ }, "main": "./src/index.js", "scripts": { + "dev": "vite", + "vitebuild": "vite build", + "preview": "vite preview", "build": "npm run build:js && npm run build:css && npm run build:docs", "build:css": "node ./scripts/build.css.cjs", "build:docs": "jsdoc src -c jsdoc.json & cp jsdoc.css docs/styles/", @@ -31,15 +34,19 @@ "a11y-dialog": "^7.5.0", "docdash": "^1.2.0", "esbuild-plugin-glsl": "^1.0.5", + "gifuct-js": "^2.1.2", "howler": "^2.2.1", "log4javascript": "github:Ritzlgrmft/log4javascript", "pako": "^1.0.10", + "pixi-filters": "^5.0.0", "pixi.js-legacy": "^6.0.4", "seedrandom": "^3.0.5", "tone": "^14.7.77", - "xlsx": "^0.17.0" + "xlsx": "^0.18.5" }, "devDependencies": { + "vite": "^5.1.6", + "vite-plugin-glsl": "^1.2.1", "csslint": "^1.0.5", "dprint": "^0.15.3", "esbuild": "^0.12.1", diff --git a/src/core/EventManager.js b/src/core/EventManager.js index 04f6d48..49e6b8f 100644 --- a/src/core/EventManager.js +++ b/src/core/EventManager.js @@ -351,7 +351,13 @@ export class EventManager { const timestamp = MonotonicClock.getReferenceTime(); - let code = event.code; + // Note: we are using event.key since we are interested in the input character rather than + // the physical key position on the keyboard, i.e. we need to take into account the keyboard + // layout + // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for a comment regarding + // event.code's lack of suitability + let code = EventManager._pygletMap[event.key]; + // let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): if (typeof code === "undefined") diff --git a/src/core/GUI.js b/src/core/GUI.js index 90bd3d1..e3cc571 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -50,6 +50,9 @@ export class GUI { this._psychoJS = psychoJS; + // info fields excluded from the GUI: + this._excludedInfo = {}; + // gui listens to RESOURCE events from the server manager: psychoJS.serverManager.on(ServerManager.Event.RESOURCE, (signal) => { @@ -87,9 +90,6 @@ export class GUI requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick }) { - // get info from URL: - const infoFromUrl = util.getUrlParameters(); - this._progressBarMax = 0; this._allResourcesDownloaded = false; this._requiredKeys = []; @@ -113,6 +113,19 @@ export class GUI self._dialogComponent.tStart = t; self._dialogComponent.status = PsychoJS.Status.STARTED; + // prepare the info fields excluded from the GUI, including those from the URL: + const excludedInfo = {}; + for (let key in self._excludedInfo) + { + excludedInfo[key.trim().toLowerCase()] = self._excludedInfo[key]; + } + const infoFromUrl = util.getUrlParameters(); + infoFromUrl.forEach((value, key) => + { + excludedInfo[key.trim().toLowerCase()] = value; + }); + + // if the experiment is licensed, and running on the license rather than on credit, // we use the license logo: if (self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER @@ -130,7 +143,13 @@ export class GUI markup += "
"; // alert title and close button: - markup += `

${title}

`; + markup += "
"; + markup += `

${title}

`; + markup += ""; + markup += "
"; + + // everything above the buttons is in a scrollable container: + markup += "
"; // logo, if need be: if (typeof logoUrl === "string") @@ -139,14 +158,16 @@ export class GUI } // add a combobox or text areas for each entry in the dictionary: + let atLeastOneIncludedKey = false; Object.keys(dictionary).forEach((key, keyIdx) => { const value = dictionary[key]; const keyId = "form-input-" + keyIdx; // only create an input if the key is not in the URL: - let inUrl = false; const cleanedDictKey = key.trim().toLowerCase(); + const isIncluded = !(cleanedDictKey in excludedInfo); + /*let inUrl = false; infoFromUrl.forEach((urlValue, urlKey) => { const cleanedUrlKey = urlKey.trim().toLowerCase(); @@ -155,10 +176,13 @@ export class GUI inUrl = true; // break; } - }); + });*/ - if (!inUrl) + if (isIncluded) + // if (!inUrl) { + atLeastOneIncludedKey = true; + markup += ``; // if the field is required: @@ -185,7 +209,7 @@ export class GUI markup += ""; } - // otherwise we use a single string input: + // otherwise we use a single string input: //if (typeof value === 'string') else { @@ -199,17 +223,27 @@ export class GUI markup += "

Fields marked with an asterisk (*) are required.

"; } + markup += "
"; // scrollable-container + + // separator, if need be: + if (atLeastOneIncludedKey) + { + markup += "
"; + } + // progress bar: - markup += `
${self._progressMessage}
`; + markup += `
${self._progressMessage}
`; markup += "
"; // buttons: markup += "
"; + markup += "
"; markup += ""; if (self._requireParticipantClick) { markup += ""; } + markup += "
"; // button-group markup += "
"; @@ -346,14 +380,18 @@ export class GUI { const error = this._userFriendlyError(errorCode); markup += `

${error.title}

`; + markup += "
"; markup += `

${error.text}

`; + markup += "
"; } else { markup += `

Error

`; + markup += "
"; markup += `

Unfortunately we encountered the following error:

`; markup += stackCode; markup += "

Try to run the experiment again. If the error persists, contact the experiment designer.

"; + markup += "
"; } } @@ -361,27 +399,36 @@ export class GUI else if (typeof warning !== "undefined") { markup += `

Warning

`; + markup += "
"; markup += `

${warning}

`; + markup += "
"; } // we are displaying a message: else if (typeof message !== "undefined") { - markup += `

Message

`; + markup += "

Message

"; + markup += "
"; markup += `

${message}

`; + markup += "
"; } if (showOK || showCancel) { markup += "
"; } - if (showCancel) + if (showCancel || showOK) { - markup += ""; - } - if (showOK) - { - markup += ""; + markup += "
"; + if (showCancel) + { + markup += ""; + } + if (showOK) + { + markup += ""; + } + markup += "
"; // button-group } markup += ""; diff --git a/src/core/Keyboard.js b/src/core/Keyboard.js index dd2427d..56df760 100644 --- a/src/core/Keyboard.js +++ b/src/core/Keyboard.js @@ -354,7 +354,13 @@ export class Keyboard extends PsychObject */ self._previousKeydownKey = event.key; - let code = event.code; + // Note: we are using event.key since we are interested in the input character rather than + // the physical key position on the keyboard, i.e. we need to take into account the keyboard + // layout + // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for a comment regarding + // event.code's lack of suitability + let code = EventManager._pygletMap[event.key]; + // let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): if (typeof code === "undefined") @@ -394,7 +400,9 @@ export class Keyboard extends PsychObject self._previousKeydownKey = undefined; - let code = event.code; + // Note: see above for explanation regarding the use of event.key in lieu of event.code + let code = EventManager._pygletMap[event.key]; + // let code = event.code; // take care of legacy Microsoft Edge: if (typeof code === "undefined") diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index 21d9f35..c8ca9d1 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -530,6 +530,7 @@ export class PsychoJS const response = { origin: "PsychoJS.quit", context: "when terminating the experiment" }; this._experiment.experimentEnded = true; + this._experiment.isCompleted = isCompleted; this.status = PsychoJS.Status.STOPPED; const isServerEnv = (this.getEnvironment() === ExperimentHandler.Environment.SERVER); @@ -601,7 +602,7 @@ export class PsychoJS if (showOK) { - let text = "Thank you for your patience.

"; + let text = "Thank you for your patience."; text += (typeof message !== "undefined") ? message : "Goodbye!"; this._gui.dialog({ message: text, diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 2415c50..2c3e670 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -314,6 +314,46 @@ export class ServerManager extends PsychObject return pathStatusData.data; } + /** + * Get full data of a resource. + * + * @name module:core.ServerManager#getFullResourceData + * @function + * @public + * @param {string} name - name of the requested resource + * @param {boolean} [errorIfNotDownloaded = false] whether or not to throw an exception if the + * resource status is not DOWNLOADED + * @return {Object} full available data for resource, or undefined if the resource has been registered + * but not downloaded yet. + * @throws {Object.} exception if no resource with that name has previously been registered + */ + getFullResourceData (name, errorIfNotDownloaded = false) + { + const response = { + origin: "ServerManager.getResource", + context: "when getting the value of resource: " + name, + }; + + const pathStatusData = this._resources.get(name); + + if (typeof pathStatusData === "undefined") + { + + // throw { ...response, error: 'unknown resource' }; + throw Object.assign(response, { error: "unknown resource" }); + } + + if (errorIfNotDownloaded && pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) + { + throw Object.assign(response, { + error: name + " is not available for use (yet), its current status is: " + + util.toString(pathStatusData.status), + }); + } + + return pathStatusData; + } + /** * Release a resource. * @@ -662,6 +702,19 @@ export class ServerManager extends PsychObject } } + cacheResourceData (name, dataToCache) + { + const pathStatusData = this._resources.get(name); + + if (typeof pathStatusData === "undefined") + { + // throw { ...response, error: 'unknown resource' }; + throw Object.assign(response, { error: "unknown resource" }); + } + + pathStatusData.cachedData = dataToCache; + } + /** * Block the experiment until the specified resources have been downloaded. * @@ -1265,7 +1318,7 @@ export class ServerManager extends PsychObject const pathExtension = (pathParts.length > 1) ? pathParts.pop() : undefined; // preload.js with forced binary: - if (["csv", "odp", "xls", "xlsx", "json"].indexOf(extension) > -1) + if (["csv", "odp", "xls", "xlsx", "json", "gif"].indexOf(extension) > -1) { preloadManifest.push(/*new createjs.LoadItem().set(*/ { id: name, @@ -1293,7 +1346,7 @@ export class ServerManager extends PsychObject } // font files: - else if (["ttf", "otf", "woff", "woff2"].indexOf(pathExtension) > -1) + else if (["ttf", "otf", "woff", "woff2","eot"].indexOf(pathExtension) > -1) { fontResources.push(name); } @@ -1310,7 +1363,7 @@ export class ServerManager extends PsychObject preloadManifest.push(/*new createjs.LoadItem().set(*/ { id: name, src: pathStatusData.path, - crossOrigin: "Anonymous", + crossOrigin: "Anonymous" } /*)*/); } } diff --git a/src/core/Window.js b/src/core/Window.js index da8f45f..3105c37 100644 --- a/src/core/Window.js +++ b/src/core/Window.js @@ -13,6 +13,7 @@ import { MonotonicClock } from "../util/Clock.js"; import { Color } from "../util/Color.js"; import { PsychObject } from "../util/PsychObject.js"; import { Logger } from "./Logger.js"; +import { hasTouchScreen } from "../util/Util.js"; /** *

Window displays the various stimuli of the experiment.

@@ -151,7 +152,7 @@ export class Window extends PsychObject } this._rootContainer.destroy(); - + if (document.body.contains(this._renderer.view)) { document.body.removeChild(this._renderer.view); @@ -180,7 +181,7 @@ export class Window extends PsychObject { // gets updated frame by frame const lastDelta = this.psychoJS.scheduler._lastDelta; - const fps = lastDelta === 0 ? 60.0 : 1000 / lastDelta; + const fps = (lastDelta === 0) ? 60.0 : (1000.0 / lastDelta); return fps; } @@ -314,7 +315,7 @@ export class Window extends PsychObject */ removePixiObject(pixiObject) { - this._stimsContainer.removeChild(pixiObject); + this._stimsContainer.removeChild(pixiObject); } /** @@ -475,11 +476,11 @@ export class Window extends PsychObject // create a top-level PIXI container: this._rootContainer = new PIXI.Container(); this._rootContainer.addChild(this._backgroundSprite, this._stimsContainer); - + // sorts children according to their zIndex value. Higher zIndex means it will be moved towards the end of the array, // and thus rendered on top of previous one. this._rootContainer.sortableChildren = true; - + this._rootContainer.interactive = true; this._rootContainer.filters = [this._adjustmentFilter]; @@ -575,6 +576,17 @@ export class Window extends PsychObject // update the renderer size and the Window's stimuli whenever the browser's size or orientation change: this._resizeCallback = (e) => { + // if the user device is a mobile phone or tablet (we use the presence of a touch screen as a + // proxy), we need to detect whether the change in size is due to the appearance of a virtual keyboard + // in which case we do not want to resize the canvas. This is rather tricky and so we resort to + // the below trick. It would be better to use the VirtualKeyboard API, but it is not widely + // available just yet, as of 2023-06. + const keyboardHeight = 300; + if (hasTouchScreen() && (window.screen.height - window.visualViewport.height) > keyboardHeight) + { + return; + } + Window._resizePixiRenderer(this, e); this._backgroundSprite.width = this._size[0]; this._backgroundSprite.height = this._size[1]; diff --git a/src/data/ExperimentHandler.js b/src/data/ExperimentHandler.js index 7a30578..97692b5 100644 --- a/src/data/ExperimentHandler.js +++ b/src/data/ExperimentHandler.js @@ -276,6 +276,7 @@ export class ExperimentHandler extends PsychObject } let data = this._trialsData; + // if the experiment data have to be cleared, we first make a copy of them: if (clear) { @@ -351,6 +352,19 @@ export class ExperimentHandler extends PsychObject } } + /** + * Get the results of the experiment as a .csv string, ready to be uploaded or stored. + * + * @return {string} a .csv representation of the experiment results. + */ + getResultAsCsv() + { + // note: we use the XLSX library as it automatically deals with header, takes care of quotes, + // newlines, etc. + const worksheet = XLSX.utils.json_to_sheet(this._trialsData); + return "\ufeff" + XLSX.utils.sheet_to_csv(worksheet); + } + /** * Get the attribute names and values for the current trial of a given loop. *

Only info relating to the trial execution are returned.

diff --git a/src/index.css b/src/index.css index 301aaa1..8194d84 100644 --- a/src/index.css +++ b/src/index.css @@ -26,13 +26,12 @@ body { /* Project and resource dialogs */ - .dialog-container label, .dialog-container input, .dialog-container select { - box-sizing: border-box; - display: block; - padding-bottom: 0.5em; + box-sizing: border-box; + display: block; + padding-bottom: 0.5em; } .dialog-container input.text, @@ -40,6 +39,13 @@ body { margin-bottom: 1em; padding: 0.5em; width: 100%; + + height: 34px; + border: 1px solid #767676; + border-radius: 2px; + background: #ffffff; + color: #333; + font-size: 14px; } .dialog-container fieldset { @@ -71,12 +77,19 @@ body { } .dialog-content { + display: flex; + flex-direction: column; + row-gap: 0; + margin: auto; z-index: 2; position: relative; width: 500px; max-width: 88vw; + /*max-height: 90vh;*/ + max-height: 93%; + padding: 0.5em; border-radius: 2px; @@ -88,11 +101,24 @@ body { box-shadow: 1px 1px 3px #555555; } +.dialog-content .scrollable-container { + height: 100%; + padding: 0 0.5em; + + overflow-x: hidden; + overflow-y: auto; +} + +.dialog-content hr { + width: 100%; +} + .dialog-title { padding: 0.5em; margin-bottom: 1em; - background-color: #009900; + background-color: #00dd00; + /*background-color: #009900;*/ border-radius: 2px; } @@ -111,6 +137,11 @@ body { } .dialog-close { + display: flex; + justify-content: center; + align-items: center; + line-height: 1.1em; + position: absolute; top: 0.7em; right: 0.7em; @@ -153,7 +184,7 @@ body { .dialog-button { padding: 0.5em 1em 0.5em 1em; - margin: 0.5em 0.5em 0.5em 0; + /*margin: 0.5em 0.5em 0.5em 0;*/ border: 1px solid #555555; border-radius: 2px; @@ -176,6 +207,14 @@ body { border: 1px solid #000000; } +.dialog-button-group { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + column-gap: 0.5em; +} + .disabled { border: 1px solid #AAAAAA; color: #AAAAAA; @@ -186,10 +225,15 @@ body { } .logo { - display: block; + display: flex; + flex: 0 1 auto; + height: 100%; + width: auto; + + /*display: block; margin: 0 auto 1em; max-height: 20vh; - max-width: 100%; + max-width: 100%;*/ } a, diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..b5170ad --- /dev/null +++ b/src/index.html @@ -0,0 +1,14 @@ + + + + + + Test Experiment + + + + + +
+ + diff --git a/src/test_experiment.js b/src/test_experiment.js new file mode 100644 index 0000000..28b212b --- /dev/null +++ b/src/test_experiment.js @@ -0,0 +1,502 @@ +/************** + * Gabor Test * + **************/ + +// import { core, data, sound, util, visual } from '../../psychojs_experimental/psychojsPR519.js'; +// import { core, data, sound, util, visual } from "../out/psychojs-2024.1.0.js"; +import { core, data, sound, util, visual } from "./index.js"; + +// import {StimInspector} from 'https://run.pavlovia.org/lgtst/stiminspector/StimInspector.js'; +// import {StimInspector} from '../stiminspector/StimInspector.js'; +// import {PsyexpReader} from '../psyexpreader/PsyexpReader.js'; +const { PsychoJS } = core; +const { TrialHandler } = data; +const { Scheduler } = util; +//some handy aliases as in the psychopy scripts; +const { abs, sin, cos, PI: pi, sqrt } = Math; +const { round } = util; + +// store info about the experiment session: +let expName = 'gabor'; // from the Builder filename that created this script +let expInfo = {}; + +// Start code blocks for 'Before Experiment' +// init psychoJS: +const psychoJS = new PsychoJS({ + debug: true +}); +window.psychoJS = psychoJS; +window.util = util; + +// open window: +psychoJS.openWindow({ + fullscr: false, + color: new util.Color("gray"), + units: 'height', + waitBlanking: true +}); + +// new StimInspector(psychoJS.window, { core, data, sound, util, visual }); + +// schedule the experiment: +psychoJS.schedule(psychoJS.gui.DlgFromDict({ + dictionary: expInfo, + title: expName +})); + +const flowScheduler = new Scheduler(psychoJS); +const dialogCancelScheduler = new Scheduler(psychoJS); +psychoJS.scheduleCondition(function() { return (psychoJS.gui.dialogComponent.button === 'OK'); }, flowScheduler, dialogCancelScheduler); + +// flowScheduler gets run if the participants presses OK +flowScheduler.add(updateInfo); // add timeStamp +flowScheduler.add(experimentInit); +// flowScheduler.add(instructRoutineBegin()); +// flowScheduler.add(instructRoutineEachFrame()); +// flowScheduler.add(instructRoutineEnd()); +flowScheduler.add(gaborRoutineBegin()); +flowScheduler.add(gaborRoutineEachFrame()); +flowScheduler.add(gaborRoutineEnd()); +flowScheduler.add(quitPsychoJS, '', true); + +// quit if user presses Cancel in dialog box: +dialogCancelScheduler.add(quitPsychoJS, '', false); + +psychoJS.start({ + expName: expName, + expInfo: expInfo, + configURL: "../config.json", + resources: [ + { + name: "cool.gif", + path: "./test_resources/cool.gif" + }, + { + name: "delorean.gif", + path: "./test_resources/delorean.gif" + } + // { + // name: "007", + // path: "007.jpg" + // }, + ] +}); + +psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.WARNING); + +var frameDur; +async function updateInfo() { + expInfo['date'] = util.MonotonicClock.getDateStr(); // add a simple timestamp + expInfo['expName'] = expName; + expInfo['psychopyVersion'] = '2021.3.0'; + expInfo['OS'] = window.navigator.platform; + + // store frame rate of monitor if we can measure it successfully + expInfo['frameRate'] = psychoJS.window.getActualFrameRate(); + if (typeof expInfo['frameRate'] !== 'undefined') + frameDur = 1.0 / Math.round(expInfo['frameRate']); + else + frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess + + // add info from the URL: + util.addInfoFromUrl(expInfo); + + return Scheduler.Event.NEXT; +} + +var instructClock; +var ready; +var gaborClock; +var gabor; +var stims = []; +window.grating2BlendMode = 'add'; +var globalClock; +var routineTimer; + +function addWheelListener () { + let v = 1.; + window.addEventListener('wheel', (e) => { + if (!psychoJS) { + return; + } + psychoJS._window._stimsContainer.position.y += e.deltaY * v; + }) +} + +// var video; +async function experimentInit() { + // Initialize components for Routine "instruct" + instructClock = new util.Clock(); + ready = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true}); + psychoJS.window.backgroundImage = "toxen"; + + // Initialize components for Routine "gabor" + gaborClock = new util.Clock(); + + stims.push( + // new visual.GratingStim({ + // win : psychoJS.window, + // name: 'morph', + // tex: 'sin', + // mask: undefined, + // ori: 0, + // size: [256, 512], + // pos: [0, 0], + // units: "pix", + // depth: 0 + // }) + new visual.GifStim({ + win : psychoJS.window, + name: 'morph', + image: "cool.gif", + mask: undefined, + ori: 0, + size: [512, 512], + pos: [0, 0], + units: "pix", + depth: 0 + }) + ); + + window.stims = stims; + // Create some handy timers + globalClock = new util.Clock(); // to track the time since experiment started + routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine + addWheelListener(); + + return Scheduler.Event.NEXT; +} + + +var t; +var frameN; +var continueRoutine; +var gotValidClick; +var _ready_allKeys; +var instructComponents; +function instructRoutineBegin(snapshot) { + return async function () { + TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date + + //------Prepare to start Routine 'instruct'------- + t = 0; + instructClock.reset(); // clock + frameN = -1; + continueRoutine = true; // until we're told otherwise + // update component parameters for each repeat + ready.keys = undefined; + ready.rt = undefined; + _ready_allKeys = []; + // keep track of which components have finished + instructComponents = []; + instructComponents.push(ready); + + for (const thisComponent of instructComponents) + if ('status' in thisComponent) + thisComponent.status = PsychoJS.Status.NOT_STARTED; + return Scheduler.Event.NEXT; + } +} + + +function instructRoutineEachFrame() { + return async function () { + //------Loop for each frame of Routine 'instruct'------- + // get current time + t = instructClock.getTime(); + frameN = frameN + 1;// number of completed frames (so 0 is the first frame) + // update/draw components on each frame + + // *ready* updates + if (t >= 0 && ready.status === PsychoJS.Status.NOT_STARTED) { + // keep track of start time/frame for later + ready.tStart = t; // (not accounting for frame time here) + ready.frameNStart = frameN; // exact frame index + + // keyboard checking is just starting + psychoJS.window.callOnFlip(function() { ready.clock.reset(); }); // t=0 on next screen flip + psychoJS.window.callOnFlip(function() { ready.start(); }); // start on screen flip + psychoJS.window.callOnFlip(function() { ready.clearEvents(); }); + } + + if (ready.status === PsychoJS.Status.STARTED) { + let theseKeys = ready.getKeys({keyList: [], waitRelease: false}); + _ready_allKeys = _ready_allKeys.concat(theseKeys); + if (_ready_allKeys.length > 0) { + ready.keys = _ready_allKeys[_ready_allKeys.length - 1].name; // just the last key pressed + ready.rt = _ready_allKeys[_ready_allKeys.length - 1].rt; + // a response ends the routine + continueRoutine = false; + } + } + + // check for quit (typically the Esc key) + if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { + return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); + } + + // check if the Routine should terminate + if (!continueRoutine) { // a component has requested a forced-end of Routine + return Scheduler.Event.NEXT; + } + + continueRoutine = false; // reverts to True if at least one component still running + for (const thisComponent of instructComponents) + if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { + continueRoutine = true; + break; + } + + // refresh the screen if continuing + if (continueRoutine) { + return Scheduler.Event.FLIP_REPEAT; + } else { + return Scheduler.Event.NEXT; + } + }; +} + + +function instructRoutineEnd() { + return async function () { + //------Ending Routine 'instruct'------- + for (const thisComponent of instructComponents) { + if (typeof thisComponent.setAutoDraw === 'function') { + thisComponent.setAutoDraw(false); + } + } + ready.stop(); + // the Routine "instruct" was not non-slip safe, so reset the non-slip timer + routineTimer.reset(); + + return Scheduler.Event.NEXT; + }; +} + + +var gaborComponents; +function gaborRoutineBegin(snapshot) { + return async function () { + TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date + + //------Prepare to start Routine 'instruct'------- + t = 0; + gaborClock.reset(); // clock + frameN = -1; + continueRoutine = true; // until we're told otherwise + // update component parameters for each repeat + ready.keys = undefined; + ready.rt = undefined; + _ready_allKeys = []; + // keep track of which components have finished + gaborComponents = []; + gaborComponents.push(ready); + gaborComponents = [...gaborComponents, ...stims]; + + + for (const thisComponent of gaborComponents) + if ('status' in thisComponent) + thisComponent.status = PsychoJS.Status.NOT_STARTED; + + return Scheduler.Event.NEXT; + } +} + +var secTimer = 0; +var prevTime = performance.now(); +var dynamicDimension = 0; +var newSize = [512, 512]; +var newPos = [0, 0]; +var sizeTests = [-512, -256.1, -128, 128, 256.6, 512]; +var positionTests = [-256, -256.1, 256, 256.1, 0]; +var anchorTests = ["left", "topleft", "top", "topright", "right", "bottomright", "bottom", "bottomleft", "center"]; +var sizeTestsProgress = 0; +var positionTestsProgress = 0; +var anchorTestsProgress = 0; +var continueAutoTest = true; +window.stopTest = function () { + continueAutoTest = false; +}; +window.startTest = function () { + continueAutoTest = true; +}; +function gaborRoutineEachFrame() { + return async function () { + //------Loop for each frame of Routine 'gabor'------- + // get current time + t = gaborClock.getTime(); + frameN = frameN + 1;// number of completed frames (so 0 is the first frame) + + let i; + for (i = 0; i < stims.length; i++) { + if (t >= 0. && stims[i].status === PsychoJS.Status.NOT_STARTED) { + stims[i].tStart = t; + stims[i].frameNStart = frameN; + stims[i].setAutoDraw(true); + } + } + + + // testing code + secTimer += performance.now() - prevTime; + prevTime = performance.now(); + if (secTimer >= 1000 && continueAutoTest) + { + secTimer = 0; + + if (sizeTestsProgress < sizeTests.length * 2) + { + i = sizeTestsProgress % sizeTests.length; + newSize[dynamicDimension] = sizeTests[i]; + stims[0].setSize(newSize); + sizeTestsProgress++; + console.log("stim size set to", stims[0].getSize()); + if (sizeTestsProgress % sizeTests.length === 0) + { + dynamicDimension = (dynamicDimension + 1) % 2; + } + } + else if (sizeTestsProgress < sizeTests.length * 3) + { + i = sizeTestsProgress % sizeTests.length; + newSize[0] = sizeTests[i]; + newSize[1] = sizeTests[i]; + stims[0].setSize(newSize); + sizeTestsProgress++; + console.log("stim size set to", stims[0].getSize()); + } + else if ( + sizeTestsProgress >= sizeTests.length * 3 && + positionTestsProgress < positionTests.length * 2) + { + i = positionTestsProgress % positionTests.length; + newPos[dynamicDimension] = positionTests[i]; + stims[0].setPos(newPos); + positionTestsProgress++; + console.log("stim pos set to", stims[0].getPos()); + if (positionTestsProgress % positionTests.length === 0) + { + newPos[dynamicDimension] = 0; + dynamicDimension = (dynamicDimension + 1) % 2; + } + } + else if( + sizeTestsProgress >= sizeTests.length * 3 && + positionTestsProgress >= positionTests.length * 2 && + anchorTestsProgress < anchorTests.length) + { + i = anchorTestsProgress % anchorTests.length; + stims[0].setAnchor(anchorTests[i]); + anchorTestsProgress++; + console.log("anchor set to", anchorTests[i]); + } + + if ( + sizeTestsProgress >= sizeTests.length * 3 && + positionTestsProgress >= positionTests.length * 2 && + anchorTestsProgress >= anchorTests.length) + { + sizeTestsProgress = 0; + positionTestsProgress = 0; + anchorTestsProgress = 0; + dynamicDimension = 0; + newPos[0] = 0; + newPos[1] = 0; + newSize[0] = 512; + newSize[1] = 512; + console.log("============== full reset =============="); + stims[0].setPos(newPos); + stims[0].setSize(newSize); + stims[0].setAnchor("center"); + } + } + + + // check for quit (typically the Esc key) + if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) + { + continueRoutine = false; + } + + // check if the Routine should terminate + if (!continueRoutine) { // a component has requested a forced-end of Routine + return Scheduler.Event.NEXT; + } + + continueRoutine = false; // reverts to True if at least one component still running + for (const thisComponent of gaborComponents) + if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { + continueRoutine = true; + break; + } + + // refresh the screen if continuing + if (continueRoutine) { + return Scheduler.Event.FLIP_REPEAT; + } else { + return Scheduler.Event.NEXT; + } + }; +} + + +function gaborRoutineEnd() { + return async function () { + //------Ending Routine 'gabor'------- + for (const thisComponent of gaborComponents) { + if (typeof thisComponent.setAutoDraw === 'function') { + thisComponent.setAutoDraw(false); + } + } + + + + // the Routine "gabor" was not non-slip safe, so reset the non-slip timer + routineTimer.reset(); + + return Scheduler.Event.NEXT; + }; +} + + +function endLoopIteration(scheduler, snapshot) { + // ------Prepare for next entry------ + return async function () { + if (typeof snapshot !== 'undefined') { + // ------Check if user ended loop early------ + if (snapshot.finished) { + // Check for and save orphaned data + if (psychoJS.experiment.isEntryEmpty()) { + psychoJS.experiment.nextEntry(snapshot); + } + scheduler.stop(); + } else { + const thisTrial = snapshot.getCurrentTrial(); + if (typeof thisTrial === 'undefined' || !('isTrials' in thisTrial) || thisTrial.isTrials) { + psychoJS.experiment.nextEntry(snapshot); + } + } + return Scheduler.Event.NEXT; + } + }; +} + + +function importConditions(currentLoop) { + return async function () { + psychoJS.importAttributes(currentLoop.getCurrentTrial()); + return Scheduler.Event.NEXT; + }; +} + + +async function quitPsychoJS(message, isCompleted) { + // Check for and save orphaned data + if (psychoJS.experiment.isEntryEmpty()) { + psychoJS.experiment.nextEntry(); + } + psychoJS.window.close(); + psychoJS.quit({message: message, isCompleted: isCompleted}); + + return Scheduler.Event.QUIT; +} diff --git a/src/test_resources/cool.gif b/src/test_resources/cool.gif new file mode 100644 index 0000000..47ae578 Binary files /dev/null and b/src/test_resources/cool.gif differ diff --git a/src/test_resources/delorean.gif b/src/test_resources/delorean.gif new file mode 100644 index 0000000..cd38909 Binary files /dev/null and b/src/test_resources/delorean.gif differ diff --git a/src/util/GifParser.js b/src/util/GifParser.js new file mode 100644 index 0000000..535201b --- /dev/null +++ b/src/util/GifParser.js @@ -0,0 +1,278 @@ +/** + * Tool for parsing gif files and decoding it's data to frames. + * + * @author "Matt Way" (https://github.com/matt-way), Nikita Agafonov (https://github.com/lightest) + * @copyright (c) 2015 Matt Way, (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + * + * @note Based on https://github.com/matt-way/gifuct-js + * + */ + +import GIF from 'js-binary-schema-parser/lib/schemas/gif' +import { parse } from 'js-binary-schema-parser' +import { buildStream } from 'js-binary-schema-parser/lib/parsers/uint8' + +/** + * Deinterlace function from https://github.com/shachaf/jsgif + */ + +export const deinterlace = (pixels, width) => { + const newPixels = new Array(pixels.length) + const rows = pixels.length / width + const cpRow = function(toRow, fromRow) { + const fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width) + newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels)) + } + + // See appendix E. + const offsets = [0, 4, 2, 1] + const steps = [8, 8, 4, 2] + + var fromRow = 0 + for (var pass = 0; pass < 4; pass++) { + for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) { + cpRow(toRow, fromRow) + fromRow++ + } + } + + return newPixels +} + + +/** + * javascript port of java LZW decompression + * Original java author url: https://gist.github.com/devunwired/4479231 + */ + +export const lzw = (minCodeSize, data, pixelCount, memoryBuffer, bufferOffset) => { + const MAX_STACK_SIZE = 4096 + const nullCode = -1 + const npix = pixelCount + var available, + clear, + code_mask, + code_size, + end_of_information, + in_code, + old_code, + bits, + code, + i, + datum, + data_size, + first, + top, + bi, + pi + + // const dstPixels = new Array(pixelCount) + // const prefix = new Array(MAX_STACK_SIZE) + // const suffix = new Array(MAX_STACK_SIZE) + // const pixelStack = new Array(MAX_STACK_SIZE + 1) + + const dstPixels = new Uint8Array(memoryBuffer, bufferOffset, pixelCount) + const prefix = new Uint16Array(MAX_STACK_SIZE) + const suffix = new Uint16Array(MAX_STACK_SIZE) + const pixelStack = new Uint8Array(MAX_STACK_SIZE + 1) + + // Initialize GIF data stream decoder. + data_size = minCodeSize + clear = 1 << data_size + end_of_information = clear + 1 + available = clear + 2 + old_code = nullCode + code_size = data_size + 1 + code_mask = (1 << code_size) - 1 + for (code = 0; code < clear; code++) { + // prefix[code] = 0 + suffix[code] = code + } + + // Decode GIF pixel stream. + var datum, bits, count, first, top, pi, bi + datum = bits = count = first = top = pi = bi = 0 + for (i = 0; i < npix; ) { + if (top === 0) { + if (bits < code_size) { + // get the next byte + datum += data[bi] << bits + + bits += 8 + bi++ + continue + } + // Get the next code. + code = datum & code_mask + datum >>= code_size + bits -= code_size + // Interpret the code + if (code > available || code == end_of_information) { + break + } + if (code == clear) { + // Reset decoder. + code_size = data_size + 1 + code_mask = (1 << code_size) - 1 + available = clear + 2 + old_code = nullCode + continue + } + if (old_code == nullCode) { + pixelStack[top++] = suffix[code] + old_code = code + first = code + continue + } + in_code = code + if (code == available) { + pixelStack[top++] = first + code = old_code + } + while (code > clear) { + pixelStack[top++] = suffix[code] + code = prefix[code] + } + + first = suffix[code] & 0xff + pixelStack[top++] = first + + // add a new string to the table, but only if space is available + // if not, just continue with current table until a clear code is found + // (deferred clear code implementation as per GIF spec) + if (available < MAX_STACK_SIZE) { + prefix[available] = old_code + suffix[available] = first + available++ + if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) { + code_size++ + code_mask += available + } + } + old_code = in_code + } + // Pop a pixel off the pixel stack. + top-- + dstPixels[pi++] = pixelStack[top] + i++ + } + + // for (i = pi; i < npix; i++) { + // dstPixels[i] = 0 // clear missing pixels + // } + + return dstPixels +} + +export const parseGIF = arrayBuffer => { + const byteData = new Uint8Array(arrayBuffer) + return parse(buildStream(byteData), GIF) +} + +const generatePatch = image => { + const totalPixels = image.pixels.length + const patchData = new Uint8ClampedArray(totalPixels * 4) + for (var i = 0; i < totalPixels; i++) { + const pos = i * 4 + const colorIndex = image.pixels[i] + const color = image.colorTable[colorIndex] || [0, 0, 0] + patchData[pos] = color[0] + patchData[pos + 1] = color[1] + patchData[pos + 2] = color[2] + patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0 + } + + return patchData +} + +export const decompressFrame = (frame, gct, buildImagePatch, memoryBuffer, memoryOffset) => { + if (!frame.image) { + console.warn('gif frame does not have associated image.') + return + } + + const { image } = frame + + // get the number of pixels + const totalPixels = image.descriptor.width * image.descriptor.height + // do lzw decompression + var pixels = lzw(image.data.minCodeSize, image.data.blocks, totalPixels, memoryBuffer, memoryOffset) + + // deal with interlacing if necessary + if (image.descriptor.lct.interlaced) { + pixels = deinterlace(pixels, image.descriptor.width) + } + + const resultImage = { + pixels: pixels, + dims: { + top: frame.image.descriptor.top, + left: frame.image.descriptor.left, + width: frame.image.descriptor.width, + height: frame.image.descriptor.height + } + } + + // color table + if (image.descriptor.lct && image.descriptor.lct.exists) { + resultImage.colorTable = image.lct + } else { + resultImage.colorTable = gct + } + + // add per frame relevant gce information + if (frame.gce) { + resultImage.delay = (frame.gce.delay || 10) * 10 // convert to ms + resultImage.disposalType = frame.gce.extras.disposal + // transparency + if (frame.gce.extras.transparentColorGiven) { + resultImage.transparentIndex = frame.gce.transparentColorIndex + } + } + + // create canvas usable imagedata if desired + if (buildImagePatch) { + resultImage.patch = generatePatch(resultImage) + } + + return resultImage +} + +export const decompressFrames = (parsedGif, buildImagePatches) => { + // return parsedGif.frames + // .filter(f => f.image) + // .map(f => decompressFrame(f, parsedGif.gct, buildImagePatches)) + let totalPixels = 0; + let framesWithData = 0; + let out ; + let i, j = 0; + + for (i = 0; i < parsedGif.frames.length; i++) { + if (parsedGif.frames[i].image) + { + totalPixels += parsedGif.frames[i].image.descriptor.width * parsedGif.frames[i].image.descriptor.height; + framesWithData++; + } + } + + // const dstPixels = new Uint16Array(totalPixels); + // let frameStart = 0; + // let frameEnd = 0; + + const buf = new ArrayBuffer(totalPixels); + let bufOffset = 0; + out = new Array(framesWithData); + + for (i = 0; i < parsedGif.frames.length; i++) { + if (parsedGif.frames[i].image) + { + out[j] = decompressFrame(parsedGif.frames[i], parsedGif.gct, buildImagePatches, buf, bufOffset); + bufOffset += parsedGif.frames[i].image.descriptor.width * parsedGif.frames[i].image.descriptor.height; + // out[j] = decompressFrame(parsedGif.frames[i], parsedGif.gct, buildImagePatches, prefix, suffix, pixelStack, dstPixels, frameStart, frameEnd); + j++; + } + } + + return out; +} diff --git a/src/util/Scheduler.js b/src/util/Scheduler.js index bad709c..4bbaf6e 100644 --- a/src/util/Scheduler.js +++ b/src/util/Scheduler.js @@ -117,9 +117,12 @@ export class Scheduler * Start this scheduler. * *

Note: tasks are run after each animation frame.

+ * + * @return {Promise} a promise resolved when the scheduler stops, e.g. when the experiments finishes */ start() { + let shedulerResolve; const self = this; const update = async (timestamp) => { @@ -127,6 +130,7 @@ export class Scheduler if (self._stopAtNextUpdate) { self._status = Scheduler.Status.STOPPED; + shedulerResolve(); return; } @@ -137,6 +141,7 @@ export class Scheduler if (state === Scheduler.Event.QUIT) { self._status = Scheduler.Status.STOPPED; + shedulerResolve(); return; } @@ -155,6 +160,12 @@ export class Scheduler // start the animation: requestAnimationFrame(update); + + // return a promise resolved when the scheduler is stopped: + return new Promise((resolve, _) => + { + shedulerResolve = resolve; + }); } /** diff --git a/src/util/Util.js b/src/util/Util.js index 0845207..1e4d2a5 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -362,6 +362,24 @@ export function shuffle(array, randomNumberGenerator = undefined, startIndex = u return array; } +/** + * linspace + * + * @name module:util.linspace + * @function + * @public + * @param {Object[]} startValue, stopValue, cardinality + * @return {Object[]} an array from startValue to stopValue with cardinality steps + */ +export function linspace(startValue, stopValue, cardinality) { + var arr = []; + var step = (stopValue - startValue) / (cardinality - 1); + for (var i = 0; i < cardinality; i++) { + arr.push(startValue + (step * i)); + } + return arr; +} + /** * Pick a random value from an array, uses `util.shuffle` to shuffle the array and returns the last value. * @@ -629,6 +647,11 @@ export function toString(object) return object.toString(); } + if (typeof object === "function") + { + return ``; + } + try { const symbolReplacer = (key, value) => @@ -1455,6 +1478,47 @@ export function loadCss(cssId, cssPath) } } +/** + * Whether the user device has a touchscreen, e.g. it is a mobile phone or tablet. + * + * @return {boolean} true if the user device has a touchscreen. + * @note the code below is directly adapted from MDN + */ +export function hasTouchScreen() +{ + let hasTouchScreen = false; + + if ("maxTouchPoints" in navigator) + { + hasTouchScreen = navigator.maxTouchPoints > 0; + } + else if ("msMaxTouchPoints" in navigator) + { + hasTouchScreen = navigator.msMaxTouchPoints > 0; + } + else + { + const mQ = matchMedia?.("(pointer:coarse)"); + if (mQ?.media === "(pointer:coarse)") + { + hasTouchScreen = !!mQ.matches; + } + else if ("orientation" in window) + { + hasTouchScreen = true; + } + else + { + const UA = navigator.userAgent; + hasTouchScreen = + /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || + /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA); + } + } + + return hasTouchScreen; +} + /** * Enum that stores possible text directions. * Note that Arabic is the same as RTL but added here to support PsychoPy's diff --git a/src/visual/AnimatedGIF.js b/src/visual/AnimatedGIF.js new file mode 100644 index 0000000..c324cde --- /dev/null +++ b/src/visual/AnimatedGIF.js @@ -0,0 +1,441 @@ +/** + * Animated gif sprite. + * + * @author Nikita Agafonov (https://github.com/lightest), Matt Karl (https://github.com/bigtimebuddy) + * @copyright (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + * + * @note Based on https://github.com/pixijs/gif and heavily modified. + * + */ + +import * as PIXI from "pixi.js-legacy"; + +/** + * Runtime object to play animated GIFs. This object is similar to an AnimatedSprite. + * It support playback (seek, play, stop) as well as animation speed and looping. + */ +class AnimatedGIF extends PIXI.Sprite +{ + /** + * Default options for all AnimatedGIF objects. + * @property {PIXI.SCALE_MODES} [scaleMode=PIXI.SCALE_MODES.LINEAR] - Scale mode to use for the texture. + * @property {boolean} [loop=true] - To enable looping. + * @property {number} [animationSpeed=1] - Speed of the animation. + * @property {boolean} [autoUpdate=true] - Set to `false` to manage updates yourself. + * @property {boolean} [autoPlay=true] - To start playing right away. + * @property {Function} [onComplete=null] - The completed callback, optional. + * @property {Function} [onLoop=null] - The loop callback, optional. + * @property {Function} [onFrameChange=null] - The frame callback, optional. + * @property {number} [fps=PIXI.Ticker.shared.FPS] - Default FPS. + */ + static defaultOptions = { + scaleMode: PIXI.SCALE_MODES.LINEAR, + fps: PIXI.Ticker.shared.FPS, + loop: true, + animationSpeed: 1, + autoPlay: true, + autoUpdate: true, + onComplete: null, + onFrameChange: null, + onLoop: null + }; + + /** + * @param frames - Data of the GIF image. + * @param options - Options for the AnimatedGIF + */ + constructor(decompressedFrames, options) + { + // Get the options, apply defaults + const { scaleMode, width, height, ...rest } = Object.assign({}, + AnimatedGIF.defaultOptions, + options + ); + + super(new PIXI.Texture(PIXI.BaseTexture.fromBuffer(new Uint8Array(width * height * 4), width, height, options))); + this._name = options.name; + this._useFullFrames = false; + this._decompressedFrameData = decompressedFrames; + this._origDims = { width, height }; + let i, j, time = 0; + this._frameTimings = new Array(decompressedFrames.length); + for (i = 0; i < decompressedFrames.length; i++) + { + this._frameTimings[i] = + { + start: time, + end: time + decompressedFrames[i].delay + }; + time += decompressedFrames[i].delay; + } + this.duration = this._frameTimings[decompressedFrames.length - 1].end; + this._fullPixelData = []; + if (options.fullFrames !== undefined && options.fullFrames.length > 0) + { + this._fullPixelData = options.fullFrames; + this._useFullFrames = true; + } + this._playing = false; + this._currentTime = 0; + this._isConnectedToTicker = false; + Object.assign(this, rest); + + // Draw the first frame + this.currentFrame = 0; + this._prevRenderedFrameIdx = -1; + if (this.autoPlay) + { + this.play(); + } + } + + static updatePixelsForOneFrame (decompressedFrameData, pixelBuffer, gifWidth) + { + let i = 0; + let patchRow = 0, patchCol = 0; + let offset = 0; + let colorData; + + if (decompressedFrameData.pixels.length === pixelBuffer.length / 4) + { + // Not all GIF files are perfectly optimized + // and instead of having tiny patch of pixels that actually changed from previous frame + // they would have a full next frame. + // Knowing that, we can go faster by skipping math needed to determine where to put new pixels + // and just place them 1 to 1 over existing frame (probably internal browser optimizations also kick in). + // For large amounts of gifs running simultaniously this results in 58+FPS vs 15-25+FPS for "else" case. + for (i = 0; i < decompressedFrameData.pixels.length; i++) { + if (decompressedFrameData.pixels[i] !== decompressedFrameData.transparentIndex) { + colorData = decompressedFrameData.colorTable[decompressedFrameData.pixels[i]]; + offset = i * 4; + pixelBuffer[offset] = colorData[0]; + pixelBuffer[offset + 1] = colorData[1]; + pixelBuffer[offset + 2] = colorData[2]; + pixelBuffer[offset + 3] = 255; + } + } + } + else + { + for (i = 0; i < decompressedFrameData.pixels.length; i++) { + if (decompressedFrameData.pixels[i] !== decompressedFrameData.transparentIndex) { + colorData = decompressedFrameData.colorTable[decompressedFrameData.pixels[i]]; + patchRow = (i / decompressedFrameData.dims.width) | 0; + patchCol = i % decompressedFrameData.dims.width; + offset = (gifWidth * (decompressedFrameData.dims.top + patchRow) + decompressedFrameData.dims.left + patchCol) * 4; + pixelBuffer[offset] = colorData[0]; + pixelBuffer[offset + 1] = colorData[1]; + pixelBuffer[offset + 2] = colorData[2]; + pixelBuffer[offset + 3] = 255; + } + } + } + + } + + static computeFullFrames (decompressedFrames, gifWidth, gifHeight) + { + let t = performance.now(); + let i, j; + let patchRow = 0, patchCol = 0; + let offset = 0; + let colorData; + let pixelData = new Uint8Array(gifWidth * gifHeight * 4); + let fullPixelData = new Uint8Array(gifWidth * gifHeight * 4 * decompressedFrames.length); + for (i = 0; i < decompressedFrames.length; i++) + { + AnimatedGIF.updatePixelsForOneFrame(decompressedFrames[i], pixelData, gifWidth); + fullPixelData.set(pixelData, pixelData.length * i); + } + console.log("full frames construction time", performance.now() - t); + return fullPixelData; + } + + _constructNthFullFrame (desiredFrameIdx, prevRenderedFrameIdx, decompressedFrames, pixelBuffer) + { + let t = performance.now(); + // saving to variable instead of referencing object in the loop wins up to 5ms! + // (at the moment of development observed on Win10, Chrome 103.0.5060.114 (Official Build) (64-bit)) + const gifWidth = this._origDims.width; + let i; + for (i = prevRenderedFrameIdx + 1; i <= desiredFrameIdx; i++) + { + // this._updatePixelsForOneFrame(decompressedFrames[i], pixelBuffer); + AnimatedGIF.updatePixelsForOneFrame(decompressedFrames[i], pixelBuffer, gifWidth) + } + // console.log("constructed frames from", prevRenderedFrameIdx, "to", desiredFrameIdx, "(", desiredFrameIdx - prevRenderedFrameIdx, ")", performance.now() - t); + } + + /** Stops the animation. */ + stop() + { + if (!this._playing) + { + return; + } + + this._playing = false; + if (this._autoUpdate && this._isConnectedToTicker) + { + PIXI.Ticker.shared.remove(this.update, this); + this._isConnectedToTicker = false; + } + } + + /** Plays the animation. */ + play() + { + if (this._playing) + { + return; + } + + this._playing = true; + if (this._autoUpdate && !this._isConnectedToTicker) + { + PIXI.Ticker.shared.add(this.update, this, PIXI.UPDATE_PRIORITY.HIGH); + this._isConnectedToTicker = true; + } + + // If were on the last frame and stopped, play should resume from beginning + if (!this.loop && this.currentFrame === this._decompressedFrameData.length - 1) + { + this._currentTime = 0; + } + } + + /** + * Get the current progress of the animation from 0 to 1. + * @readonly + */ + get progress() + { + return this._currentTime / this.duration; + } + + /** `true` if the current animation is playing */ + get playing() + { + return this._playing; + } + + /** + * Updates the object transform for rendering. You only need to call this + * if the `autoUpdate` property is set to `false`. + * + * @param deltaTime - Time since last tick. + */ + update(deltaTime) + { + if (!this._playing) + { + return; + } + + const elapsed = this.animationSpeed * deltaTime / PIXI.settings.TARGET_FPMS; + const currentTime = this._currentTime + elapsed; + const localTime = currentTime % this.duration; + + const localFrame = this._frameTimings.findIndex((ft) => + ft.start <= localTime && ft.end > localTime); + + if (this._prevRenderedFrameIdx > localFrame) + { + this._prevRenderedFrameIdx = -1; + } + + if (currentTime >= this.duration) + { + if (this.loop) + { + this._currentTime = localTime; + this.updateFrameIndex(localFrame); + if (typeof this.onLoop === "function") + { + this.onLoop(); + } + } + else + { + this._currentTime = this.duration; + this.updateFrameIndex(this._decompressedFrameData.length - 1); + if (typeof this.onComplete === "function") + { + this.onComplete(); + } + this.stop(); + } + } + else + { + this._currentTime = localTime; + this.updateFrameIndex(localFrame); + } + } + + /** + * Redraw the current frame, is necessary for the animation to work when + */ + updateFrame() + { + // if (!this.dirty) + // { + // return; + // } + + if (this._prevRenderedFrameIdx === this._currentFrame) + { + return; + } + + // Update the current frame + if (this._useFullFrames) + { + this.texture.baseTexture.resource.data = new Uint8Array + ( + this._fullPixelData.buffer, this._currentFrame * this._origDims.width * this._origDims.height * 4, + this._origDims.width * this._origDims.height * 4 + ); + } + else + { + // this._updatePixelsForOneFrame(this._decompressedFrameData[this._currentFrame], this.texture.baseTexture.resource.data); + this._constructNthFullFrame(this._currentFrame, this._prevRenderedFrameIdx, this._decompressedFrameData, this.texture.baseTexture.resource.data); + } + + this.texture.update(); + // Mark as clean + // this.dirty = false; + this._prevRenderedFrameIdx = this._currentFrame; + } + + /** + * Renders the object using the WebGL renderer + * + * @param {PIXI.Renderer} renderer - The renderer + * @private + */ + _render(renderer) + { + let t = performance.now(); + this.updateFrame(); + // console.log("t2", this._name, performance.now() - t); + super._render(renderer); + } + + /** + * Renders the object using the WebGL renderer + * + * @param {PIXI.CanvasRenderer} renderer - The renderer + * @private + */ + _renderCanvas(renderer) + { + this.updateFrame(); + super._renderCanvas(renderer); + } + + /** + * Whether to use PIXI.Ticker.shared to auto update animation time. + * @default true + */ + get autoUpdate() + { + return this._autoUpdate; + } + + set autoUpdate(value) + { + if (value !== this._autoUpdate) + { + this._autoUpdate = value; + + if (!this._autoUpdate && this._isConnectedToTicker) + { + PIXI.Ticker.shared.remove(this.update, this); + this._isConnectedToTicker = false; + } + else if (this._autoUpdate && !this._isConnectedToTicker && this._playing) + { + PIXI.Ticker.shared.add(this.update, this); + this._isConnectedToTicker = true; + } + } + } + + /** Set the current frame number */ + get currentFrame() + { + return this._currentFrame; + } + + set currentFrame(value) + { + this.updateFrameIndex(value); + this._currentTime = this._frameTimings[value].start; + } + + /** Internally handle updating the frame index */ + updateFrameIndex(value) + { + if (value < 0 || value >= this._decompressedFrameData.length) + { + throw new Error(`Frame index out of range, expecting 0 to ${this.totalFrames}, got ${value}`); + } + if (this._currentFrame !== value) + { + this._currentFrame = value; + // this.dirty = true; + if (typeof this.onFrameChange === "function") + { + this.onFrameChange(value); + } + } + } + + /** + * Get the total number of frame in the GIF. + */ + get totalFrames() + { + return this._decompressedFrameData.length; + } + + /** Destroy and don't use after this. */ + destroy() + { + this.stop(); + super.destroy(true); + this._decompressedFrameData = null; + this._fullPixelData = null; + this.onComplete = null; + this.onFrameChange = null; + this.onLoop = null; + } + + /** + * Cloning the animation is a useful way to create a duplicate animation. + * This maintains all the properties of the original animation but allows + * you to control playback independent of the original animation. + * If you want to create a simple copy, and not control independently, + * then you can simply create a new Sprite, e.g. `const sprite = new Sprite(animation.texture)`. + */ + clone() + { + return new AnimatedGIF([...this._decompressedFrameData], { + autoUpdate: this._autoUpdate, + loop: this.loop, + autoPlay: this.autoPlay, + scaleMode: this.texture.baseTexture.scaleMode, + animationSpeed: this.animationSpeed, + width: this._origDims.width, + height: this._origDims.height, + onComplete: this.onComplete, + onFrameChange: this.onFrameChange, + onLoop: this.onLoop, + }); + } +} + +export { AnimatedGIF }; diff --git a/src/visual/ButtonStim.js b/src/visual/ButtonStim.js index b3dae41..cadc819 100644 --- a/src/visual/ButtonStim.js +++ b/src/visual/ButtonStim.js @@ -9,6 +9,7 @@ import { Mouse } from "../core/Mouse.js"; import { TextBox } from "./TextBox.js"; +import * as util from "../util/Util"; /** *

ButtonStim visual stimulus.

@@ -32,6 +33,7 @@ export class ButtonStim extends TextBox * @param {Color} [options.borderColor= Color("white")] the border color * @param {Color} [options.borderWidth= 0] the border width * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.depth= 0] - the depth (i.e. the z order) * @param {number} [options.letterHeight= undefined] - the height of the text * @param {boolean} [options.bold= true] - whether or not the text is bold * @param {boolean} [options.italic= false] - whether or not the text is italic @@ -55,12 +57,15 @@ export class ButtonStim extends TextBox borderColor, borderWidth = 0, opacity, + depth, letterHeight, bold = true, italic, autoDraw, autoLog, - draggable + draggable, + boxFn, + multiline } = {}, ) { @@ -79,13 +84,16 @@ export class ButtonStim extends TextBox borderColor, borderWidth, opacity, + depth, letterHeight, + multiline, bold, italic, alignment: "center", autoDraw, autoLog, - draggable + draggable, + boxFn }); this.psychoJS.logger.debug("create a new Button with name: ", name); @@ -115,7 +123,7 @@ export class ButtonStim extends TextBox if (this._autoLog) { - this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${util.toString(this)}`); } } diff --git a/src/visual/GifStim.js b/src/visual/GifStim.js new file mode 100644 index 0000000..59dc135 --- /dev/null +++ b/src/visual/GifStim.js @@ -0,0 +1,515 @@ +/** + * Gif Stimulus. + * + * @author Nikita Agafonov + * @version 2022.2.0 + * @copyright (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + */ + +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; +import {Camera} from "../hardware"; +// import { parseGIF, decompressFrames } from "gifuct-js"; +import { AnimatedGIF } from "./AnimatedGIF.js"; +import { parseGIF, decompressFrames } from "../util/GifParser.js"; + +/** + * Gif Stimulus. + * + * @name module:visual.GifStim + * @class + * @extends VisualStim + * @mixes ColorMixin + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {Window} options.win - the associated Window + * @param {boolean} options.precomputeFrames - compute full frames of the GIF and store them. Setting this to true will take the load off the CPU + * @param {string | HTMLImageElement} options.image - the name of the image resource or the HTMLImageElement corresponding to the image + * @param {string | HTMLImageElement} options.mask - the name of the mask resource or HTMLImageElement corresponding to the mask + * but GIF will take longer to load and occupy more memory space. In case when there's not enough CPU peformance (e.g. due to large amount of GIFs + * playing simultaneously or heavy load elsewhere in experiment) and you don't care much about app memory usage, use this flag to easily gain more performance. + * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices) + * @param {Array.} [options.pos= [0, 0]] - the position of the center of the stimulus + * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position + * @param {number} [options.ori= 0.0] - the orientation (in degrees) + * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified) + * @param {Color} [options.color= 'white'] the background color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.contrast= 1.0] - the contrast + * @param {number} [options.depth= 0] - the depth (i.e. the z order) + * @param {number} [options.texRes= 128] - the resolution of the text + * @param {boolean} [options.loop= true] - whether or not to loop the animation + * @param {boolean} [options.autoPlay= true] - whether or not to autoPlay the animation + * @param {boolean} [options.animationSpeed= 1] - animation speed, works as multiplyer e.g. 1 - normal speed, 0.5 - half speed, 2 - twice as fast etc. + * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated + * @param {boolean} [options.flipHoriz= false] - whether or not to flip horizontally + * @param {boolean} [options.flipVert= false] - whether or not to flip vertically + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ +export class GifStim extends util.mix(VisualStim).with(ColorMixin) +{ + constructor({ + name, + win, + image, + mask, + precomputeFrames, + pos, + units, + ori, + size, + color, + opacity, + contrast, + texRes, + depth, + interpolate, + loop, + autoPlay, + animationSpeed, + flipHoriz, + flipVert, + autoDraw, + autoLog + } = {}) + { + super({ name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog }); + + this._resource = undefined; + + this._addAttribute("precomputeFrames", precomputeFrames, false); + this._addAttribute("image", image); + this._addAttribute("mask", mask); + this._addAttribute("color", color, "white", this._onChange(true, false)); + this._addAttribute("contrast", contrast, 1.0, this._onChange(true, false)); + this._addAttribute("texRes", texRes, 128, this._onChange(true, false)); + this._addAttribute("interpolate", interpolate, false); + this._addAttribute("flipHoriz", flipHoriz, false, this._onChange(false, false)); + this._addAttribute("flipVert", flipVert, false, this._onChange(false, false)); + this._addAttribute("loop", loop, true); + this._addAttribute("autoPlay", autoPlay, true); + this._addAttribute("animationSpeed", animationSpeed, 1); + + // estimate the bounding box: + this._estimateBoundingBox(); + + if (this._autoLog) + { + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + } + } + + /** + * Getter for the playing property. + * + * @name module:visual.GifStim#isPlaying + * @public + */ + get isPlaying () + { + if (this._pixi) + { + return this._pixi.playing; + } + return false; + } + + /** + * Getter for the duration property. Shows animation duration time in milliseconds. + * + * @name module:visual.GifStim#duration + * @public + */ + get duration () + { + if (this._pixi) + { + return this._pixi.duration; + } + } + + /** + * Starts GIF playback. + * + * @name module:visual.GifStim#play + * @public + */ + play () + { + if (this._pixi) + { + this._pixi.play(); + } + } + + /** + * Pauses GIF playback. + * + * @name module:visual.GifStim#pause + * @public + */ + pause () + { + if (this._pixi) + { + this._pixi.stop(); + } + } + + /** + * Set wether or not to loop the animation. + * + * @name module:visual.GifStim#setLoop + * @public + * @param {boolean} [loop=true] - flag value + * @param {boolean} [log=false] - whether or not to log. + */ + setLoop (loop, log = false) + { + this._setAttribute("loop", loop, log); + if (this._pixi) + { + this._pixi.loop = loop; + } + } + + /** + * Set wether or not to autoplay the animation. + * + * @name module:visual.GifStim#setAutoPlay + * @public + * @param {boolean} [autoPlay=true] - flag value + * @param {boolean} [log=false] - whether or not to log. + */ + setAutoPlay (autoPlay, log = false) + { + this._setAttribute("autoPlay", autoPlay, log); + if (this._pixi) + { + this._pixi.autoPlay = autoPlay; + } + } + + /** + * Set animation speed of the animation. + * + * @name module:visual.GifStim#setAnimationSpeed + * @public + * @param {boolean} [animationSpeed=1] - multiplyer of the animation speed e.g. 1 - normal, 0.5 - half speed, 2 - twice as fast. + * @param {boolean} [log=false] - whether or not to log. + */ + setAnimationSpeed (animationSpeed = 1, log = false) + { + this._setAttribute("animationSpeed", animationSpeed, log); + if (this._pixi) + { + this._pixi.animationSpeed = animationSpeed; + } + } + + /** + * Setter for the image attribute. + * + * @name module:visual.GifStim#setImage + * @public + * @param {HTMLImageElement | string} image - the name of the image resource or HTMLImageElement corresponding to the image + * @param {boolean} [log= false] - whether or not to log + */ + setImage(image, log = false) + { + const response = { + origin: "GifStim.setImage", + context: "when setting the image of GifStim: " + this._name, + }; + + try + { + // image is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem + if (typeof image === "undefined") + { + this.psychoJS.logger.warn("setting the image of GifStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the image of GifStim: " + this._name + " as: undefined"); + } + else if (typeof image === "string") + { + // image is a string: it should be the name of a resource, which we load + const fullRD = this.psychoJS.serverManager.getFullResourceData(image); + console.log("gif resource", fullRD); + if (fullRD.cachedData === undefined) + { + // How GIF works: http://www.matthewflickinger.com/lab/whatsinagif/animation_and_transparency.asp + let t0 = performance.now(); + let parsedGif = parseGIF(fullRD.data); + let pt = performance.now() - t0; + let t2 = performance.now(); + let decompressedFrames = decompressFrames(parsedGif, false); + let dect = performance.now() - t2; + let fullFrames; + if (this._precomputeFrames) + { + fullFrames = AnimatedGIF.computeFullFrames(decompressedFrames, parsedGif.lsd.width, parsedGif.lsd.height); + } + this._resource = { parsedGif, decompressedFrames, fullFrames }; + this.psychoJS.serverManager.cacheResourceData(image, this._resource); + console.log(`animated gif "${this._name}",`, "parse=", pt, "decompress=", dect); + } + else + { + this._resource = fullRD.cachedData; + } + + // this.psychoJS.logger.debug(`set resource of GifStim: ${this._name} as ArrayBuffer(${this._resource.length})`); + const hasChanged = this._setAttribute("image", image, log); + if (hasChanged) + { + this._onChange(true, true)(); + } + } + } + catch (error) + { + throw Object.assign(response, { error }); + } + } + + /** + * Setter for the mask attribute. + * + * @name module:visual.GifStim#setMask + * @public + * @param {HTMLImageElement | string} mask - the name of the mask resource or HTMLImageElement corresponding to the mask + * @param {boolean} [log= false] - whether of not to log + */ + setMask(mask, log = false) + { + const response = { + origin: "GifStim.setMask", + context: "when setting the mask of GifStim: " + this._name, + }; + + try + { + // mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem + if (typeof mask === "undefined") + { + this.psychoJS.logger.warn("setting the mask of GifStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the mask of GifStim: " + this._name + " as: undefined"); + } + else + { + // mask is a string: it should be the name of a resource, which we load + if (typeof mask === "string") + { + mask = this.psychoJS.serverManager.getResource(mask); + } + + // mask should now be an actual HTMLImageElement: we raise an error if it is not + if (!(mask instanceof HTMLImageElement)) + { + throw "the argument: " + mask.toString() + ' is not an image" }'; + } + + this.psychoJS.logger.debug("set the mask of GifStim: " + this._name + " as: src= " + mask.src + ", size= " + mask.width + "x" + mask.height); + } + + this._setAttribute("mask", mask, log); + this._onChange(true, false)(); + } + catch (error) + { + throw Object.assign(response, { error }); + } + } + + /** + * Whether to interpolate (linearly) the texture in the stimulus. + * + * @name module:visual.GifStim#setInterpolate + * @public + * @param {boolean} interpolate - interpolate or not. + * @param {boolean} [log=false] - whether or not to log + */ + setInterpolate (interpolate = false, log = false) + { + this._setAttribute("interpolate", interpolate, log); + if (this._pixi instanceof PIXI.Sprite) { + this._pixi.texture.baseTexture.scaleMode = interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST; + this._pixi.texture.baseTexture.update(); + } + } + + /** + * Setter for the size attribute. + * + * @param {undefined | null | number | number[]} size - the stimulus size + * @param {boolean} [log= false] - whether of not to log + */ + setSize(size, log = false) + { + // size is either undefined, null, or a tuple of numbers: + if (typeof size !== "undefined" && size !== null) + { + size = util.toNumerical(size); + if (!Array.isArray(size)) + { + size = [size, size]; + } + } + + this._setAttribute("size", size, log); + + if (this._pixi) + { + const size_px = util.to_px(size, this.units, this.win); + const scaleX = size_px[0] / this._pixi.texture.width; + const scaleY = size_px[1] / this._pixi.texture.height; + this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX; + this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; + } + } + + /** + * Estimate the bounding box. + * + * @name module:visual.GifStim#_estimateBoundingBox + * @function + * @override + * @protected + */ + _estimateBoundingBox() + { + const size = this._getDisplaySize(); + if (typeof size !== "undefined") + { + this._boundingBox = new PIXI.Rectangle( + this._pos[0] - size[0] / 2, + this._pos[1] - size[1] / 2, + size[0], + size[1], + ); + } + + // TODO take the orientation into account + } + + /** + * Update the stimulus, if necessary. + * + * @name module:visual.GifStim#_updateIfNeeded + * @private + */ + _updateIfNeeded() + { + if (!this._needUpdate) + { + return; + } + this._needUpdate = false; + + // update the PIXI representation, if need be: + if (this._needPixiUpdate) + { + this._needPixiUpdate = false; + + if (typeof this._pixi !== "undefined") + { + this._pixi.destroy(true); + } + this._pixi = undefined; + + // no image to draw: return immediately + if (typeof this._resource === "undefined") + { + return; + } + + const gifOpts = + { + name: this._name, + width: this._resource.parsedGif.lsd.width, + height: this._resource.parsedGif.lsd.height, + fullFrames: this._resource.fullFrames, + scaleMode: this._interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST, + loop: this._loop, + autoPlay: this._autoPlay, + animationSpeed: this._animationSpeed + }; + + let t = performance.now(); + this._pixi = new AnimatedGIF(this._resource.decompressedFrames, gifOpts); + console.log(`animatedGif "${this._name}" instancing:`, performance.now() - t); + + // add a mask if need be: + if (typeof this._mask !== "undefined") + { + // Building new PIXI.BaseTexture each time we create a mask, to avoid PIXI's caching and use a unique resource. + this._pixi.mask = PIXI.Sprite.from(new PIXI.Texture(new PIXI.BaseTexture(this._mask))); + + // a 0.5, 0.5 anchor is required for the mask to be aligned with the image + this._pixi.mask.anchor.x = 0.5; + this._pixi.mask.anchor.y = 0.5; + this._pixi.addChild(this._pixi.mask); + } + + // since _texture.width may not be immediately available but the rest of the code needs its value + // we arrange for repeated calls to _updateIfNeeded until we have a width: + if (this._pixi.texture.width === 0) + { + this._needUpdate = true; + this._needPixiUpdate = true; + return; + } + } + + this._pixi.zIndex = -this._depth; + this._pixi.alpha = this.opacity; + + // set the scale: + const displaySize = this._getDisplaySize(); + const size_px = util.to_px(displaySize, this.units, this.win); + const scaleX = size_px[0] / this._pixi.texture.width; + const scaleY = size_px[1] / this._pixi.texture.height; + this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX; + this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; + + // set the position, rotation, and anchor (image centered on pos): + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); + this._pixi.rotation = -this.ori * Math.PI / 180; + this._pixi.anchor.x = 0.5; + this._pixi.anchor.y = 0.5; + + // re-estimate the bounding box, as the texture's width may now be available: + this._estimateBoundingBox(); + } + + /** + * Get the size of the display image, which is either that of the GifStim or that of the image + * it contains. + * + * @name module:visual.GifStim#_getDisplaySize + * @private + * @return {number[]} the size of the displayed image + */ + _getDisplaySize() + { + let displaySize = this.size; + + if (this._pixi && typeof displaySize === "undefined") + { + // use the size of the texture, if we have access to it: + if (typeof this._pixi.texture !== "undefined" && this._pixi.texture.width > 0) + { + const textureSize = [this._pixi.texture.width, this._pixi.texture.height]; + displaySize = util.to_unit(textureSize, "pix", this.win, this.units); + } + } + + return displaySize; + } +} diff --git a/src/visual/ImageStim.js b/src/visual/ImageStim.js index e06ba86..52ae8b1 100644 --- a/src/visual/ImageStim.js +++ b/src/visual/ImageStim.js @@ -47,11 +47,39 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip * @param {boolean} [options.autoLog= false] - whether or not to log * @param {boolean} [options.draggable= false] - whether or not to make stim draggable with mouse/touch/other pointer device + * @param {ImageStim.AspectRatioStrategy} [options.aspectRatio= ImageStim.AspectRatioStrategy.VARIABLE] - the aspect ratio handling strategy + * @param {number} [options.blurVal= 0] - the blur value. Goes 0 to as hish as you like. 0 is no blur. */ - constructor({ name, win, image, mask, pos, anchor, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog, draggable } = {}) + constructor({ + name, + win, + image, + mask, + pos, + anchor, + units, + ori, + size, + color, + opacity, + contrast, + texRes, + depth, + interpolate, + flipHoriz, + flipVert, + autoDraw, + autoLog, + aspectRatio, + draggable, + blurVal + } = {}) { super({ name, win, units, ori, opacity, depth, pos, anchor, size, autoDraw, autoLog, draggable }); + // Holds an instance of PIXI blur filter. Used if blur value is passed. + this._blurFilter = undefined; + this._addAttribute( "image", image, @@ -95,6 +123,17 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) false, this._onChange(false, false), ); + this._addAttribute( + "aspectRatio", + aspectRatio, + ImageStim.AspectRatioStrategy.VARIABLE, + this._onChange(true, true), + ); + this._addAttribute( + "blurVal", + blurVal, + 0 + ); // estimate the bounding box: this._estimateBoundingBox(); @@ -235,6 +274,33 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) } } + setBlurVal (blurVal = 0, log = false) + { + this._setAttribute("blurVal", blurVal, log); + if (this._pixi instanceof PIXI.Sprite) + { + if (this._blurFilter === undefined) + { + this._blurFilter = new PIXI.filters.BlurFilter(); + this._blurFilter.blur = blurVal; + } + else + { + this._blurFilter.blur = blurVal; + } + + // this._pixi might get destroyed and recreated again with no filters. + if (this._pixi.filters instanceof Array && this._pixi.filters.indexOf(this._blurFilter) === -1) + { + this._pixi.filters.push(this._blurFilter); + } + else + { + this._pixi.filters = [this._blurFilter]; + } + } + } + /** * Estimate the bounding box. * @@ -277,6 +343,7 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) if (typeof this._pixi !== "undefined") { + this._pixi.filters = null; this._pixi.destroy(true); } this._pixi = undefined; @@ -310,7 +377,18 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) this._texture = new PIXI.Texture(new PIXI.BaseTexture(this._image, texOpts)); } - this._pixi = PIXI.Sprite.from(this._texture); + if (this.aspectRatio === ImageStim.AspectRatioStrategy.HORIZONTAL_TILING) + { + const [width_px, _] = util.to_px([this.size[0], 0], this.units, this.win); + this._pixi = PIXI.TilingSprite.from(this._texture, 1, 1); + this._pixi.width = width_px; + this._pixi.height = this._texture.height; + } + else + { + this._pixi = PIXI.Sprite.from(this._texture); + } + // add a mask if need be: if (typeof this._mask !== "undefined") @@ -350,8 +428,24 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) // set the scale: const displaySize = this._getDisplaySize(); const size_px = util.to_px(displaySize, this.units, this.win); - const scaleX = size_px[0] / this._texture.width; - const scaleY = size_px[1] / this._texture.height; + let scaleX = size_px[0] / this._texture.width; + let scaleY = size_px[1] / this._texture.height; + if (this.aspectRatio === ImageStim.AspectRatioStrategy.FIT_TO_WIDTH) + { + scaleY = scaleX; + } + else if (this.aspectRatio === ImageStim.AspectRatioStrategy.FIT_TO_HEIGHT) + { + scaleX = scaleY; + } + else if (this.aspectRatio === ImageStim.AspectRatioStrategy.HORIZONTAL_TILING) + { + scaleX = 1.0; + scaleY = 1.0; + } + + // note: this calls VisualStim.setAnchor, which properly sets the PixiJS anchor + // from the PsychoPy text format this.anchor = this._anchor; this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX; this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; @@ -360,6 +454,11 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.rotation = -this.ori * Math.PI / 180; + if (this._blurVal > 0) + { + this.setBlurVal(this._blurVal); + } + // re-estimate the bounding box, as the texture's width may now be available: this._estimateBoundingBox(); } @@ -384,7 +483,47 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) displaySize = util.to_unit(textureSize, "pix", this.win, this.units); } } + else + { + if (this.aspectRatio === ImageStim.AspectRatioStrategy.FIT_TO_WIDTH) + { + // use the size of the texture, if we have access to it: + if (typeof this._texture !== "undefined" && this._texture.width > 0) + { + displaySize = [displaySize[0], displaySize[0] * this._texture.height / this._texture.width]; + } + } + else if (this.aspectRatio === ImageStim.AspectRatioStrategy.FIT_TO_HEIGHT) + { + // use the size of the texture, if we have access to it: + if (typeof this._texture !== "undefined" && this._texture.width > 0) + { + displaySize = [displaySize[1] * this._texture.width / this._texture.height, displaySize[1]]; + } + } + else if (this.aspectRatio === ImageStim.AspectRatioStrategy.HORIZONTAL_TILING) + { + // use the size of the texture, if we have access to it: + if (typeof this._texture !== "undefined" && this._texture.width > 0) + { + displaySize = [displaySize[0], this._texture.height]; + } + } + } return displaySize; } } + +/** + * ImageStim Aspect Ratio Strategy. + * + * @enum {Symbol} + * @readonly + */ +ImageStim.AspectRatioStrategy = { + FIT_TO_WIDTH: Symbol.for("FIT_TO_WIDTH"), + HORIZONTAL_TILING: Symbol.for("HORIZONTAL_TILING"), + FIT_TO_HEIGHT: Symbol.for("FIT_TO_HEIGHT"), + VARIABLE: Symbol.for("VARIABLE"), +}; diff --git a/src/visual/ParticleEmitter.js b/src/visual/ParticleEmitter.js new file mode 100644 index 0000000..b0f1ed0 --- /dev/null +++ b/src/visual/ParticleEmitter.js @@ -0,0 +1,331 @@ +/** + * Particle Emitter. + * + * @author Nikita Agafonov + * @version 2023.2.0 + * @copyright (c) 2020-2023 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + */ + +import * as PIXI from "pixi.js-legacy"; + +const DEFAULT_POOL_SIZE = 1024; +const DEFAULT_PARTICLE_WIDTH = 10; +const DEFAULT_PARTICLE_HEIGHT = 10; +const DEFAULT_PARTICLE_LIFETIME = 3; // Seconds. +const DEFAULT_PARTICLE_COLOR = 0xffffff; +const DEFAULT_PARTICLES_PER_SEC = 60; +const DEFAULT_PARTICLE_V = 100; + +class Particle +{ + constructor (cfg) + { + this.x = 0; + this.y = 0; + this.ax = 0; + this.ay = 0; + this.vx = 0; + this.vy = 0; + this.lifeTime = 0; + this.widthChange = 0; + this.heightChange = 0; + this.sprite = undefined; + this.inUse = false; + + if (cfg.particleImage !== undefined) + { + this.sprite = PIXI.Sprite.from(PIXI.Texture.from(cfg.particleImage)); + } + else + { + this.sprite = new PIXI.Sprite(PIXI.Texture.WHITE); + this.sprite.tint = cfg.particleColor || DEFAULT_PARTICLE_COLOR; + } + + // TODO: Should we instead incorporate that in position calculation? + // Consider: accurate spawn position of the particle confined by spawnArea. + this.sprite.anchor.set(0.5); + + this.width = cfg.particleWidth || DEFAULT_PARTICLE_WIDTH; + this.height = cfg.particleHeight || DEFAULT_PARTICLE_HEIGHT; + } + + set width (w) + { + this._width = w; + this.sprite.width = w; + } + + get width () + { + return this._width; + } + + set height (h) + { + this._height = h; + this.sprite.height = h; + } + + get height () + { + return this._height; + } + + update (dt) + { + const dt2 = dt * dt; + + // Update velocity with current acceleration. + this.vx += this.ax * dt; + this.vy += this.ay * dt; + + // Update position with current velocity and acceleration. + this.x = this.x + this.vx * dt + this.ax * dt2 * .5; + this.y = this.y + this.vy * dt + this.ay * dt2 * .5; + + this.sprite.rotation = Math.atan2(this.vy, this.vx); + + this.sprite.x = this.x; + this.sprite.y = this.y; + + if (this.width > 0) + { + this.width = Math.max(0, this.width + this.widthChange); + } + + if (this.height > 0) + { + this.height = Math.max(0, this.height + this.heightChange); + } + this.lifeTime -= dt; + + if (this.width <= 0 && this.height <= 0) + { + this.lifeTime = 0; + } + + if (this.lifeTime <= 0) + { + this.inUse = false; + } + } +} + +export class ParticleEmitter +{ + constructor (cfg = {}) + { + this.x = 0; + this.y = 0; + this._cfg = cfg; + this._particlesPerSec = cfg.particlesPerSec || DEFAULT_PARTICLES_PER_SEC; + this._spawnCoolDown = 0; + this._parentObj = undefined; + this._particlePool = new Array(DEFAULT_POOL_SIZE); + this.setParentObject(cfg.parentObject); + this._fillParticlePool(cfg); + } + + _fillParticlePool (cfg) + { + let i; + for (i = 0; i < this._particlePool.length; i++) + { + this._particlePool[i] = new Particle(cfg); + } + } + + _setupParticle (p) + { + let spawnAreaWidth = this._cfg.spawnAreaWidth || 0; + let spawnAreaHeight = this._cfg.spawnAreaHeight || 0; + + if (this._parentObj !== undefined && this._cfg.useParentSizeAsSpawnArea) + { + spawnAreaWidth = this._parentObj.width; + spawnAreaHeight = this._parentObj.height; + } + + const spawnOffsetX = Math.random() * spawnAreaWidth - spawnAreaWidth * .5; + const spawnOffsetY = Math.random() * spawnAreaHeight - spawnAreaHeight * .5; + const x = this.x + spawnOffsetX; + const y = this.y + spawnOffsetY; + + p.x = x; + p.y = y; + + p.ax = 0; + p.ay = 0; + + if (Number.isFinite(this._cfg.initialVx)) + { + p.vx = this._cfg.initialVx; + } + else if (this._cfg.initialVx instanceof Array && this._cfg.initialVx.length >= 2) + { + p.vx = Math.random() * (this._cfg.initialVx[1] - this._cfg.initialVx[0]) + this._cfg.initialVx[0]; + } + else + { + p.vx = Math.random() * DEFAULT_PARTICLE_V - DEFAULT_PARTICLE_V * .5; + } + + if (Number.isFinite(this._cfg.initialVy)) + { + p.vy = this._cfg.initialVy; + } + else if (this._cfg.initialVy instanceof Array && this._cfg.initialVy.length >= 2) + { + p.vy = Math.random() * (this._cfg.initialVy[1] - this._cfg.initialVy[0]) + this._cfg.initialVy[0]; + } + else + { + p.vy = Math.random() * DEFAULT_PARTICLE_V - DEFAULT_PARTICLE_V * .5; + } + + p.lifeTime = this._cfg.lifeTime || DEFAULT_PARTICLE_LIFETIME; + p.width = this._cfg.particleWidth || DEFAULT_PARTICLE_WIDTH; + p.height = this._cfg.particleHeight || DEFAULT_PARTICLE_HEIGHT; + p.widthChange = this._cfg.particleWidthChange || 0; + p.heightChange = this._cfg.particleHeightChange || 0; + + // TODO: run proper checks here. + if (this._cfg.particleImage) + { + p.sprite.texture = PIXI.Texture.from(this._cfg.particleImage); + } + else + { + p.sprite.texture = PIXI.Texture.WHITE; + } + + if (this._cfg.particleColor !== undefined) + { + p.sprite.tint = this._cfg.particleColor; + } + else + { + p.sprite.tint = 0xffffff; + } + } + + _spawnParticles (n = 0) + { + let i; + for (i = 0; i < this._particlePool.length && n > 0; i++) + { + if (this._particlePool[i].inUse === false) + { + this._particlePool[i].inUse = true; + n--; + + this._setupParticle(this._particlePool[i]); + this._cfg.container.addChild(this._particlePool[i].sprite); + } + } + } + + _getResultingExternalForce () + { + let externalForce = [0, 0]; + if (this._cfg.externalForces instanceof Array) + { + let i; + for (i = 0; i < this._cfg.externalForces.length; i++) + { + externalForce[0] += this._cfg.externalForces[i][0]; + externalForce[1] += this._cfg.externalForces[i][1]; + } + } + + return externalForce; + } + + setParentObject (po) + { + this._parentObj = po; + } + + /** + * @desc: Adds external force which acts on a particle + * @param: f - Array with two elements, first is x component, second is y component. + * It's a vector of length L which sets the direction and the margnitude of the force. + * */ + addExternalForce (f) + { + this._cfg.externalForces.push(f); + } + + removeExternalForce (f) + { + const i = this._cfg.externalForces.indexOf(f); + if (i !== -1) + { + this._cfg.externalForces.splice(i, 1); + } + } + + removeExternalForceByIdx (idx) + { + if (this._cfg.externalForces[idx] !== undefined) + { + this._cfg.externalForces.splice(idx, 1); + } + } + + update (dt) + { + let externalForce; + + // Sync with parent object if it exists. + if (this._parentObj !== undefined) + { + this.x = this._parentObj.x; + this.y = this._parentObj.y; + } + + if (Number.isFinite(this._cfg.positionOffsetX)) + { + this.x += this._cfg.positionOffsetX; + } + + if (Number.isFinite(this._cfg.positionOffsetY)) + { + this.y += this._cfg.positionOffsetY; + } + + if (this._spawnCoolDown <= 0) + { + this._spawnCoolDown = 1 / this._particlesPerSec; + + // Assuming that we have at least 60FPS. + const frameTime = Math.min(dt, 1 / 60); + const particlesPerFrame = Math.ceil(frameTime / this._spawnCoolDown); + this._spawnParticles(particlesPerFrame); + } + else + { + this._spawnCoolDown -= dt; + } + + let i; + for (i = 0; i < this._particlePool.length; i++) + { + if (this._particlePool[i].inUse) + { + externalForce = this._getResultingExternalForce(); + this._particlePool[i].ax = externalForce[0]; + this._particlePool[i].ay = externalForce[1]; + this._particlePool[i].update(dt); + } + + // Check if particle should be removed. + if (this._particlePool[i].lifeTime <= 0 && this._particlePool[i].sprite.parent) + { + this._cfg.container.removeChild(this._particlePool[i].sprite); + } + } + } +} diff --git a/src/visual/Progress.js b/src/visual/Progress.js new file mode 100644 index 0000000..0d27771 --- /dev/null +++ b/src/visual/Progress.js @@ -0,0 +1,162 @@ +import * as PIXI from "pixi.js-legacy"; +import * as util from "../util/Util.js"; +import { Color } from "../util/Color.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import { VisualStim } from "./VisualStim.js"; + +export class Progress extends VisualStim +{ + constructor ( + { + name, + win, + units = "pix", + ori, + opacity, + depth, + pos, + anchor = "left", + size = [300, 30], + clipMask, + autoDraw, + autoLog, + progress = 1, + type, + fillColor, + fillTexture + }) + { + super({ + name, + win, + units, + ori, + opacity, + depth, + pos, + anchor, + size, + clipMask, + autoDraw, + autoLog + }); + + this._addAttribute("progress", progress, 0); + this._addAttribute("type", type, PROGRESS_TYPES.BAR); + this._addAttribute("fillColor", fillColor, "lightgreen"); + this._addAttribute("fillTexture", fillTexture, PIXI.Texture.WHITE); + + if (this._autoLog) + { + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + } + } + + /** + * Setter for the progress attribute. + */ + setProgress (progress = 0, log = false) + { + this._setAttribute("progress", Math.min(1.0, Math.max(0.0, progress)), log); + if (this._pixi !== undefined) + { + this._pixi.clear(); + const size_px = util.to_px(this._size, this._units, this._win); + const progressWidth = size_px[0] * this._progress; + if (this._fillTexture) + { + let t = PIXI.Texture.WHITE; + if (typeof this._fillTexture === "string") + { + t = PIXI.Texture.from(this._fillTexture); + t.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST; + } + this._pixi.beginTextureFill({ + texture: t + }); + } + else + { + this._pixi.beginFill(new Color(this._fillColor).int, this._opacity); + } + + if (this._type === PROGRESS_TYPES.BAR) + { + this._pixi.drawRect(0, 0, progressWidth, size_px[1]); + } + + this._pixi.endFill(); + + // TODO: is there a better way to ensure anchor works? + this.anchor = this._anchor; + } + } + + /** + * Estimate the bounding box. + * + * @override + * @protected + */ + _estimateBoundingBox() + { + let boundingBox = new PIXI.Rectangle(0, 0, 0, 0); + const anchorNum = this._anchorTextToNum(this._anchor); + const pos_px = util.to_px(this._pos, this._units, this._win); + const size_px = util.to_px(this._size, this._units, this._win); + boundingBox.x = pos_px[ 0 ] - anchorNum[ 0 ] * size_px[ 0 ]; + boundingBox.y = pos_px[ 1 ] - anchorNum[ 1 ] * size_px[ 1 ]; + boundingBox.width = size_px[ 0 ]; + boundingBox.height = size_px[ 1 ]; + + this._boundingBox = boundingBox; + } + + /** + * Update the stimulus, if necessary. + * + * @protected + */ + _updateIfNeeded() + { + // TODO: figure out what is the error with estimateBoundBox on resize? + if (!this._needUpdate) + { + return; + } + this._needUpdate = false; + + // update the PIXI representation, if need be: + if (this._needPixiUpdate) + { + this._needPixiUpdate = false; + + if (typeof this._pixi !== "undefined") + { + this._pixi.destroy(true); + } + this._pixi = new PIXI.Graphics(); + // TODO: Should we do this? + // this._pixi.lineStyle(this._lineWidth, this._lineColor.int, this._opacity, 0.5); + + // TODO: Should just .setProgress() be called? + this.setProgress(this._progress); + + this._pixi.scale.y = -1; + this._pixi.zIndex = -this._depth; + this.anchor = this._anchor; + } + + // set polygon position and rotation: + this._pixi.position = to_pixiPoint(this._pos, this._units, this._win); + this._pixi.rotation = -this.ori * Math.PI / 180.0; + + this._estimateBoundingBox(); + } +} + +export const PROGRESS_TYPES = +{ + BAR: 0, + CIRCLE: 1 +} diff --git a/src/visual/ShapeStim.js b/src/visual/ShapeStim.js index 5ebbdc0..8630a85 100644 --- a/src/visual/ShapeStim.js +++ b/src/visual/ShapeStim.js @@ -402,4 +402,29 @@ ShapeStim.KnownShapes = { [-0.39, 0.31], [-0.09, 0.18], ], + + triangle: [ + [+0.0, 0.5], // Point + [-0.5, -0.5], // Bottom left + [+0.5, -0.5], // Bottom right + ], + + rectangle: [ + [-.5, .5], // Top left + [ .5, .5], // Top right + [ .5, -.5], // Bottom left + [-.5, -.5], // Bottom right + ], + + arrow: [ + [0.0, 0.5], + [-0.5, 0.0], + [-1/6, 0.0], + [-1/6, -0.5], + [1/6, -0.5], + [1/6, 0.0], + [0.5, 0.0], + ], }; +// Alias some names for convenience +ShapeStim.KnownShapes['star'] = ShapeStim.KnownShapes['star7'] diff --git a/src/visual/Survey.js b/src/visual/Survey.js index b573e16..57bf41f 100644 --- a/src/visual/Survey.js +++ b/src/visual/Survey.js @@ -82,6 +82,10 @@ export class Survey extends VisualStim { super({ name, win, units, ori, depth, pos, size, autoDraw, autoLog }); + // Storing all existing signaturePad questions to properly handle their resize. + // Unfortunately signaturepad question type can't handle resizing properly by itself. + this._signaturePads = []; + // whether the user is done with the survey, independently of whether the survey is completed: this.isFinished = false; @@ -968,8 +972,6 @@ export class Survey extends VisualStim this.psychoJS.logger.warn(`Flag _isCompletedAll is false!`); } - this._detachResizeObservers(); - this._surveyRunningPromiseResolve(completionCode); } @@ -1137,34 +1139,46 @@ export class Survey extends VisualStim this._lastPageSwitchHandledIdx = -1; } - _handleSignaturePadResize(entries) + _handleWindowResize(e) { - for (let i = 0; i < entries.length; i++) + if (this._surveyModel) { - // const signatureCanvas = entries[i].target.querySelector("canvas"); - const question = this._surveyModel.getQuestionByName(entries[i].target.dataset.name); - question.signatureWidth = Math.min(question.maxSignatureWidth, entries[i].contentBoxSize[0].inlineSize); + for (let i = this._signaturePads.length - 1; i >= 0; i--) + { + // As of writing this (24.03.2023). SurveyJS doesn't have a proper event + // for question being removed from nested locations, such as dynamic panel. + // However, surveyJS will set .signaturePad property to null once the question is removed. + // Utilising this knowledge to sync our lists. + if (this._signaturePads[ i ].question.signaturePad) + { + this._signaturePads[ i ].question.signatureWidth = Math.min( + this._signaturePads[i].question.maxSignatureWidth, + this._signaturePads[ i ].htmlElement.getBoundingClientRect().width + ); + } + else + { + // Signature pad was removed. Syncing list. + this._signaturePads.splice(i, 1); + } + } } } _addEventListeners() { - this._signaturePadRO = new ResizeObserver(this._handleSignaturePadResize.bind(this)); + window.addEventListener("resize", (e) => this._handleWindowResize(e)); } _handleAfterQuestionRender (sender, options) { if (options.question.getType() === "signaturepad") { - this._signaturePadRO.observe(options.htmlElement); + this._signaturePads.push(options); + options.question.signatureWidth = Math.min(options.question.maxSignatureWidth, options.htmlElement.getBoundingClientRect().width); } } - _detachResizeObservers() - { - this._signaturePadRO.disconnect(); - } - /** * Init the SurveyJS.io library and various extensions, setup the theme. * diff --git a/src/visual/TextBox.js b/src/visual/TextBox.js index 0f4d91c..90afbde 100644 --- a/src/visual/TextBox.js +++ b/src/visual/TextBox.js @@ -88,7 +88,8 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) autoDraw, autoLog, fitToContent, - draggable + draggable, + boxFn } = {}, ) { @@ -204,12 +205,14 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) // and setSize called from super class would not have a proper effect this.setSize(size); + this._addAttribute("boxFn", boxFn, null); + // estimate the bounding box: this._estimateBoundingBox(); if (this._autoLog) { - this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${util.toString(this)}`); } } @@ -483,6 +486,26 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) alignmentStyles = ["center", "center"]; } + let box; + if (this._boxFn !== null) + { + box = this._boxFn; + } + else + { + // note: box style properties eventually become PIXI.Graphics settings, so same syntax applies + box = { + fill: new Color(this._fillColor).int, + alpha: this._fillColor === undefined || this._fillColor === null ? 0 : 1, + rounded: 5, + stroke: { + color: new Color(this._borderColor).int, + width: borderWidth_px, + alpha: this._borderColor === undefined || this._borderColor === null ? 0 : 1 + } + }; + } + return { // input style properties eventually become CSS, so same syntax applies input: { @@ -506,41 +529,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) overflow: "hidden", pointerEvents: "none" }, - // box style properties eventually become PIXI.Graphics settings, so same syntax applies - box: { - fill: new Color(this._fillColor).int, - alpha: this._fillColor === undefined || this._fillColor === null ? 0 : 1, - rounded: 5, - stroke: { - color: new Color(this._borderColor).int, - width: borderWidth_px, - alpha: this._borderColor === undefined || this._borderColor === null ? 0 : 1 - }, - /*default: { - fill: new Color(this._fillColor).int, - rounded: 5, - stroke: { - color: new Color(this._borderColor).int, - width: borderWidth_px - } - }, - focused: { - fill: new Color(this._fillColor).int, - rounded: 5, - stroke: { - color: new Color(this._borderColor).int, - width: borderWidth_px - } - }, - disabled: { - fill: new Color(this._fillColor).int, - rounded: 5, - stroke: { - color: new Color(this._borderColor).int, - width: borderWidth_px - } - }*/ - }, + box }; } diff --git a/src/visual/index.js b/src/visual/index.js index 8c604fa..67890f2 100644 --- a/src/visual/index.js +++ b/src/visual/index.js @@ -2,6 +2,7 @@ export * from "./ButtonStim.js"; export * from "./Form.js"; export * from "./ImageStim.js"; export * from "./GratingStim.js"; +export * from "./GifStim.js"; export * from "./MovieStim.js"; export * from "./Polygon.js"; export * from "./Rect.js"; @@ -13,3 +14,5 @@ export * from "./TextStim.js"; export * from "./VisualStim.js"; export * from "./FaceDetector.js"; export * from "./Survey.js"; +export * from "./ParticleEmitter.js"; +export * from "./Progress.js"; \ No newline at end of file diff --git a/src/visual/survey/widgets/MaxDiffMatrix.js b/src/visual/survey/widgets/MaxDiffMatrix.js index d9958c5..a50c784 100644 --- a/src/visual/survey/widgets/MaxDiffMatrix.js +++ b/src/visual/survey/widgets/MaxDiffMatrix.js @@ -95,18 +95,11 @@ class MaxDiffMatrix question.setCssRoot(rootClass); question.cssClasses.mainRoot = rootClass; } - let html; - let headerCells = ""; - let subHeaderCells = ""; - let bodyCells = ""; - let bodyHTML = ""; - let cellGenerator; - let i, j; // Relying on a fact that there's always 2 columns. // This is correct according current Qualtrics design for MaxDiff matrices. // Header generation - headerCells = + let headerCells = `${question.columns[0].text} @@ -114,9 +107,10 @@ class MaxDiffMatrix ${question.columns[1].text}`; // Body generation - for (i = 0; i < question.rows.length; i++) + let bodyHTML = ""; + for (let i = 0; i < question.rows.length; i++) { - bodyCells = + const bodyCells = `