diff --git a/package-lock.json b/package-lock.json index b22e1455ef..cf4a1de742 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.3.tgz", "integrity": "sha512-n1RU5vuCX0CsaqaXm9I0KUCNKNQMy5epmzl/xdSSm70bSqhg9GWhgeosypyQLc0bK24+Xpk1WGzZlI9pJtkZdg==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.28", @@ -124,6 +125,7 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -148,6 +150,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -157,6 +160,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -187,6 +191,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -235,6 +240,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -251,6 +257,7 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -274,6 +281,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -290,6 +298,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -299,6 +308,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -308,6 +318,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { @@ -391,6 +402,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -414,6 +426,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -427,6 +440,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -457,6 +471,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -516,6 +531,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -534,6 +550,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -558,6 +575,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -571,6 +589,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -2032,6 +2051,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -2046,6 +2066,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -2064,6 +2085,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -4279,6 +4301,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4289,6 +4312,7 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -4505,6 +4529,7 @@ "version": "2.1.8-no-fsevents.3", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, "license": "MIT", "optional": true }, @@ -10718,6 +10743,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -10731,6 +10757,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" @@ -10740,6 +10767,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", @@ -10750,6 +10778,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" @@ -12284,15 +12313,6 @@ "ajv": "^8.8.2" } }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "license": "BSD-3-Clause OR MIT", - "engines": { - "node": ">=0.4.2" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -12370,7 +12390,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -13113,6 +13133,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-3.5.1.tgz", "integrity": "sha512-1jlEJCSmLaJM4tjIKpu64UZ833COCHmwR77bFJDOye+zlwf80uR1b8p41l4tClx1QsrfI+qV6w/5AiPYQgaMUQ==", "deprecated": "this package has been renamed to babel-plugin-formatjs", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.4.5", @@ -13126,6 +13147,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -13140,6 +13162,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -13149,6 +13172,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -13308,7 +13332,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14182,36 +14206,6 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "license": "Apache-2.0" }, - "node_modules/cat-blocks": { - "name": "scratch-blocks", - "version": "0.1.0-prerelease.20220318143026", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20220318143026.tgz", - "integrity": "sha512-eYJYzjYt3fmF5a243eBIgQfNj+c3ApHFE8jkm1KV/tEiFanJ8XlLZay9LmbnhYhK0618+s0uEQrpJ9WC6xnX/Q==", - "deprecated": "Obsolete pre-release", - "license": "Apache-2.0", - "dependencies": { - "exports-loader": "0.6.3", - "google-closure-library": "20190301.0.0", - "imports-loader": "0.6.5", - "scratch-l10n": "3.14.20220317031619" - } - }, - "node_modules/cat-blocks/node_modules/scratch-l10n": { - "version": "3.14.20220317031619", - "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.14.20220317031619.tgz", - "integrity": "sha512-tDKXRFxKFob9htBeOu+873mujoePXc4sGQulWDdMVCM8cYz63geCWziX5fCwp2pu3pJKQLEth0ftd2eoXkT1RA==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/cli": "^7.1.2", - "@babel/core": "^7.1.2", - "babel-plugin-react-intl": "^3.0.1", - "transifex": "1.6.6" - }, - "bin": { - "build-i18n-src": "scripts/build-i18n-src.js", - "tx-push-src": "scripts/tx-push-src.js" - } - }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -14371,7 +14365,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -14771,6 +14765,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -15086,6 +15081,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/convert-to-spaces": { @@ -18166,65 +18162,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/exports-loader": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.6.3.tgz", - "integrity": "sha512-vBQgTnvmEB7qWmr7gzAzJRWptzYhkhvdXeH8sRnS//mIai6MgLZe1crlQ+VWTjCCXLlnhGuiuVMq0YfjA5AUOw==", - "dependencies": { - "loader-utils": "0.2.x", - "source-map": "0.1.x" - } - }, - "node_modules/exports-loader/node_modules/big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/exports-loader/node_modules/emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/exports-loader/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/exports-loader/node_modules/loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug==", - "license": "MIT", - "dependencies": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - }, - "node_modules/exports-loader/node_modules/source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/expose-loader": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-1.0.3.tgz", @@ -19888,6 +19825,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true, "license": "MIT" }, "node_modules/fs.realpath": { @@ -19978,6 +19916,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -21699,66 +21638,6 @@ "node": ">=8" } }, - "node_modules/imports-loader": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.6.5.tgz", - "integrity": "sha512-fYIzBL9JOzJszvfeSGSKVjAtkWEtPUwP+OWiUxIWApcxsYh3iqZWZAp8xjTuhsvqglhqaetxeLLTaYyxIv1d4Q==", - "license": "MIT", - "dependencies": { - "loader-utils": "0.2.x", - "source-map": "0.1.x" - } - }, - "node_modules/imports-loader/node_modules/big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/imports-loader/node_modules/emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/imports-loader/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/imports-loader/node_modules/loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug==", - "license": "MIT", - "dependencies": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - }, - "node_modules/imports-loader/node_modules/source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -22131,6 +22010,7 @@ "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.8.1.tgz", "integrity": "sha512-IMSCKVf0USrM/959vj3xac7s8f87sc+80Y/ipBzdKy4ifBv5Gsj2tZ41EAaURVg01QU71fYr77uA8Meh6kELbg==", "deprecated": "We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": { @@ -22308,7 +22188,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -25085,6 +24965,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -34424,7 +34305,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -35346,9 +35227,9 @@ } }, "node_modules/scratch-blocks": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-1.2.5.tgz", - "integrity": "sha512-S1cL7j0ajhOl/fJk6vKGtYwGHjGlEX5VOcAA/rjd5GhfneL6l3fopa5AXoVvYQHow3+s2wtFGaAJheYle/ygUg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-1.3.0.tgz", + "integrity": "sha512-RvTTClge6htuEml1nt77lh6VxLPkXK/GIeOPgeOzV6qyLquarsisIy+17F79CW/1E3rc8myE9JwC3iwKMcENgw==", "license": "Apache-2.0", "dependencies": { "exports-loader": "^0.7.0", @@ -36426,6 +36307,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -41311,7 +41193,6 @@ "balance-text": "3.3.1", "base64-loader": "1.0.0", "bowser": "1.9.4", - "cat-blocks": "npm:scratch-blocks@0.1.0-prerelease.20220318143026", "classnames": "2.5.1", "computed-style-to-inline-style": "3.0.0", "cookie": "0.6.0", @@ -41347,7 +41228,7 @@ "react-intl": "6.8.9", "react-modal": "3.16.3", "react-popover": "0.5.10", - "react-redux": "8.1.3", + "react-redux": "^8.0.0", "react-responsive": "9.0.2", "react-style-proptype": "3.2.2", "react-tabs": "5.2.0", @@ -41356,7 +41237,7 @@ "react-visibility-sensor": "5.1.1", "redux-throttle": "0.1.1", "scratch-audio": "2.0.268", - "scratch-blocks": "1.2.5", + "scratch-blocks": "1.3.0", "scratch-l10n": "6.1.25", "scratch-paint": "4.1.19", "scratch-render-fonts": "1.0.252", @@ -41380,6 +41261,7 @@ "@types/react-modal": "3.16.3", "babel-core": "7.0.0-bridge.0", "babel-loader": "9.2.1", + "babel-plugin-react-intl": "3.5.1", "buffer": "6.0.3", "cheerio": "1.1.2", "cross-env": "7.0.3", @@ -41708,7 +41590,7 @@ "js-md5": "0.7.3", "jsdoc": "3.6.11", "pngjs": "3.4.0", - "scratch-blocks": "1.2.5", + "scratch-blocks": "1.3.0", "scratch-l10n": "6.1.25", "scratch-render-fonts": "1.0.252", "scratch-semantic-release-config": "4.0.0", diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 0417cc815d..6dbfff4cf6 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -71,7 +71,6 @@ "balance-text": "3.3.1", "base64-loader": "1.0.0", "bowser": "1.9.4", - "cat-blocks": "npm:scratch-blocks@0.1.0-prerelease.20220318143026", "classnames": "2.5.1", "computed-style-to-inline-style": "3.0.0", "cookie": "0.6.0", @@ -107,7 +106,7 @@ "react-intl": "6.8.9", "react-modal": "3.16.3", "react-popover": "0.5.10", - "react-redux": "8.1.3", + "react-redux": "^8.0.0", "react-responsive": "9.0.2", "react-style-proptype": "3.2.2", "react-tabs": "5.2.0", @@ -116,7 +115,7 @@ "react-visibility-sensor": "5.1.1", "redux-throttle": "0.1.1", "scratch-audio": "2.0.268", - "scratch-blocks": "1.2.5", + "scratch-blocks": "1.3.0", "scratch-l10n": "6.1.25", "scratch-paint": "4.1.19", "scratch-render-fonts": "1.0.252", @@ -146,6 +145,7 @@ "@types/react-modal": "3.16.3", "babel-core": "7.0.0-bridge.0", "babel-loader": "9.2.1", + "babel-plugin-react-intl": "3.5.1", "buffer": "6.0.3", "cheerio": "1.1.2", "cross-env": "7.0.3", diff --git a/packages/scratch-gui/src/components/gui/gui.jsx b/packages/scratch-gui/src/components/gui/gui.jsx index 1fa06119e2..28a3b9d59f 100644 --- a/packages/scratch-gui/src/components/gui/gui.jsx +++ b/packages/scratch-gui/src/components/gui/gui.jsx @@ -34,7 +34,8 @@ import TelemetryModal from '../telemetry-modal/telemetry-modal.jsx'; import layout, {STAGE_SIZE_MODES} from '../../lib/layout-constants'; import {resolveStageSize} from '../../lib/screen-utils'; -import {themeMap} from '../../lib/themes'; +import {colorModeMap} from '../../lib/settings/color-mode/index.js'; +import {DEFAULT_THEME, themeMap} from '../../lib/settings/theme/index.js'; import {AccountMenuOptionsPropTypes} from '../../lib/account-menu-options'; import styles from './gui.css'; @@ -43,6 +44,7 @@ import costumesIcon from './icon--costumes.svg'; import soundsIcon from './icon--sounds.svg'; import DebugModal from '../debug-modal/debug-modal.jsx'; import {setPlatform} from '../../reducers/platform.js'; +import {setTheme} from '../../reducers/settings.js'; import {PLATFORM} from '../../lib/platform.js'; // Cache this value to only retrieve it once the first time. @@ -67,6 +69,7 @@ const GUIComponent = props => { blocksTabVisible, cardsVisible, canChangeLanguage, + canChangeColorMode, canChangeTheme, canCreateNew, canEditTitle, @@ -84,7 +87,9 @@ const GUIComponent = props => { onDebugModalClose, onTutorialSelect, enableCommunity, + hasActiveMembership, isCreating, + isFetchingUserData, isFullScreen, isPlayerOnly, isRtl, @@ -129,6 +134,7 @@ const GUIComponent = props => { stageSizeMode, targetIsStage, telemetryModalVisible, + colorMode, theme, tipsLibraryVisible, useExternalPeripheralList, @@ -144,10 +150,23 @@ const GUIComponent = props => { useEffect(() => { if (props.platform) { + // TODO: This uses the imported `setPlatform` directly, + // but it should probably use the dispatched version from props. setPlatform(props.platform); } }, [props.platform]); + useEffect(() => { + if ( + !isFetchingUserData && + !themeMap[theme]?.isAvailable?.({hasActiveMembership}) + ) { + // If the preferred theme is not available, fall back to default. + // TODO: It would be cleaner to do this on redux init. + props.setTheme(DEFAULT_THEME); + } + }, [theme, hasActiveMembership, props.setTheme]); + const tabClassNames = { tabs: styles.tabs, tab: classNames(tabStyles.reactTabsTab, styles.tab), @@ -258,6 +277,7 @@ const GUIComponent = props => { authorThumbnailUrl={authorThumbnailUrl} authorUsername={authorUsername} canChangeLanguage={canChangeLanguage} + canChangeColorMode={canChangeColorMode} canChangeTheme={canChangeTheme} canCreateCopy={canCreateCopy} canCreateNew={canCreateNew} @@ -268,6 +288,7 @@ const GUIComponent = props => { canShare={canShare} className={styles.menuBarPosition} enableCommunity={enableCommunity} + hasActiveMembership={hasActiveMembership} isShared={isShared} isTotallyNormal={isTotallyNormal} logo={logo} @@ -362,15 +383,15 @@ const GUIComponent = props => { ({ // This is the button's mode, as opposed to the actual current state blocksId: state.scratchGui.timeTravel.year.toString(), stageSizeMode: state.scratchGui.stageSize.stageSize, - theme: state.scratchGui.theme.theme + colorMode: state.scratchGui.settings.colorMode, + theme: state.scratchGui.settings.theme }); const mapDispatchToProps = dispatch => ({ - setPlatform: platform => dispatch(setPlatform(platform)) + setPlatform: platform => dispatch(setPlatform(platform)), + setTheme: theme => dispatch(setTheme(theme)) }); export default connect(mapStateToProps, diff --git a/packages/scratch-gui/src/components/menu-bar/menu-bar.jsx b/packages/scratch-gui/src/components/menu-bar/menu-bar.jsx index 58f955a1bb..ec00cc7cd3 100644 --- a/packages/scratch-gui/src/components/menu-bar/menu-bar.jsx +++ b/packages/scratch-gui/src/components/menu-bar/menu-bar.jsx @@ -452,9 +452,12 @@ class MenuBar extends React.Component { onClick={this.props.onClickLogo} /> - {(this.props.canChangeTheme || this.props.canChangeLanguage) && ( { + const item = props.item; + + return ( + +
+ + {item.icon && } + +
+
); +}; + +PreferenceItem.propTypes = { + isSelected: PropTypes.bool, + onClick: PropTypes.func, + item: PropTypes.shape({ + icon: PropTypes.string, + label: intlMessageShape.isRequired + }) +}; + +const PreferenceMenu = ({ + itemsMap, + open, + onChange, + onRequestOpen, + defaultMenuIconSrc, + submenuLabel, + selectedItemKey, + isRtl +}) => { + const itemKeys = useMemo(() => Object.keys(itemsMap), [itemsMap]); + const selectedItem = useMemo(() => itemsMap[selectedItemKey], [itemsMap, selectedItemKey]); + return ( + +
+ + + + + +
+ + {itemKeys.map(itemKey => ( + onChange(itemKey)} + item={itemsMap[itemKey]} + />) + )} + +
+ ); +}; + +PreferenceMenu.propTypes = { + itemsMap: PropTypes.objectOf(PropTypes.shape({ + icon: PropTypes.string, + label: intlMessageShape.isRequired + })).isRequired, + open: PropTypes.bool, + onChange: PropTypes.func, + onRequestCloseSettings: PropTypes.func, + onRequestOpen: PropTypes.func, + defaultMenuIconSrc: PropTypes.string, + submenuLabel: intlMessageShape.isRequired, + selectedItemKey: PropTypes.string, + isRtl: PropTypes.bool +}; + +const mapStateToProps = state => ({ + isRtl: state.locales.isRtl +}); + +export default connect( + mapStateToProps +)(PreferenceMenu); diff --git a/packages/scratch-gui/src/components/menu-bar/settings-menu.css b/packages/scratch-gui/src/components/menu-bar/settings-menu.css index 4171aa1505..7d09671776 100644 --- a/packages/scratch-gui/src/components/menu-bar/settings-menu.css +++ b/packages/scratch-gui/src/components/menu-bar/settings-menu.css @@ -2,7 +2,8 @@ width: 1.5rem; } -.theme-label { +/* Unused? */ +.color-mode-label { flex: 1; } diff --git a/packages/scratch-gui/src/components/menu-bar/settings-menu.jsx b/packages/scratch-gui/src/components/menu-bar/settings-menu.jsx index 683f08a926..2728762707 100644 --- a/packages/scratch-gui/src/components/menu-bar/settings-menu.jsx +++ b/packages/scratch-gui/src/components/menu-bar/settings-menu.jsx @@ -1,65 +1,173 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useMemo} from 'react'; import {FormattedMessage} from 'react-intl'; +import {connect} from 'react-redux'; import LanguageMenu from './language-menu.jsx'; import MenuBarMenu from './menu-bar-menu.jsx'; -import ThemeMenu from './theme-menu.jsx'; import {MenuSection} from '../menu/menu.jsx'; +import PreferenceMenu from './preference-menu.jsx'; + +import {DEFAULT_MODE, HIGH_CONTRAST_MODE, colorModeMap} from '../../lib/settings/color-mode/index.js'; +import {themeMap} from '../../lib/settings/theme/index.js'; +import {persistColorMode} from '../../lib/settings/color-mode/persistence.js'; +import {persistTheme} from '../../lib/settings/theme/persistence.js'; +import {setColorMode, setTheme} from '../../reducers/settings.js'; import menuBarStyles from './menu-bar.css'; import styles from './settings-menu.css'; import dropdownCaret from './dropdown-caret.svg'; import settingsIcon from './icon--settings.svg'; +import themeIcon from '../../lib/assets/icon--theme.svg'; +import {colorModeMenuOpen, themeMenuOpen, openColorModeMenu, openThemeMenu} from '../../reducers/menus.js'; + +const enabledColorModes = [DEFAULT_MODE, HIGH_CONTRAST_MODE]; const SettingsMenu = ({ canChangeLanguage, + canChangeColorMode, canChangeTheme, + hasActiveMembership, isRtl, + isColorModeMenuOpen, + isThemeMenuOpen, + activeColorMode, + onChangeColorMode, + onRequestOpenColorMode, + onRequestOpenTheme, + activeTheme, + onChangeTheme, onRequestClose, onRequestOpen, settingsMenuOpen -}) => ( -
- - - - - - { + const enabledColorModesMap = useMemo(() => Object.keys(colorModeMap).reduce((acc, colorMode) => { + if (enabledColorModes.includes(colorMode)) { + acc[colorMode] = colorModeMap[colorMode]; + } + return acc; + }, {}), []); + const availableThemesMap = useMemo(() => Object.keys(themeMap).reduce((acc, themeKey) => { + const theme = themeMap[themeKey]; + if (theme.isAvailable?.({hasActiveMembership})) { + acc[themeKey] = theme; + } + return acc; + }, {}), [hasActiveMembership]); + const availableThemesLength = useMemo(() => Object.keys(availableThemesMap).length, [availableThemesMap]); + + return ( +
- - {canChangeLanguage && } - {canChangeTheme && } - - -
-); + + + + + + + + {canChangeLanguage && } + {canChangeTheme && + // TODO: Consider always showing the theme menu, even if there is a single available theme + availableThemesLength > 1 && + } + {canChangeColorMode && } + + +
+ ); +}; SettingsMenu.propTypes = { canChangeLanguage: PropTypes.bool, + canChangeColorMode: PropTypes.bool, canChangeTheme: PropTypes.bool, + hasActiveMembership: PropTypes.bool, isRtl: PropTypes.bool, + activeColorMode: PropTypes.string, + onChangeColorMode: PropTypes.func, + onRequestOpenColorMode: PropTypes.func, + isColorModeMenuOpen: PropTypes.bool, + activeTheme: PropTypes.string, + onChangeTheme: PropTypes.func, + onRequestOpenTheme: PropTypes.func, + isThemeMenuOpen: PropTypes.bool, onRequestClose: PropTypes.func, onRequestOpen: PropTypes.func, settingsMenuOpen: PropTypes.bool }; -export default SettingsMenu; +const mapStateToProps = state => ({ + activeColorMode: state.scratchGui.settings.colorMode, + activeTheme: state.scratchGui.settings.theme, + isColorModeMenuOpen: colorModeMenuOpen(state), + isThemeMenuOpen: themeMenuOpen(state) +}); + +const mapDispatchToProps = (dispatch, ownProps) => ({ + onRequestOpenColorMode: () => { + dispatch(openColorModeMenu()); + }, + onRequestOpenTheme: () => { + dispatch(openThemeMenu()); + }, + onChangeColorMode: colorMode => { + dispatch(setColorMode(colorMode)); + ownProps.onRequestClose(); + persistColorMode(colorMode); + }, + onChangeTheme: theme => { + dispatch(setTheme(theme)); + ownProps.onRequestClose(); + persistTheme(theme); + } +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(SettingsMenu); diff --git a/packages/scratch-gui/src/components/menu-bar/theme-menu.jsx b/packages/scratch-gui/src/components/menu-bar/theme-menu.jsx deleted file mode 100644 index e9ce24f1de..0000000000 --- a/packages/scratch-gui/src/components/menu-bar/theme-menu.jsx +++ /dev/null @@ -1,118 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {FormattedMessage} from 'react-intl'; -import {connect} from 'react-redux'; - -import check from './check.svg'; -import {MenuItem, Submenu} from '../menu/menu.jsx'; -import {DEFAULT_THEME, HIGH_CONTRAST_THEME, themeMap} from '../../lib/themes'; -import {persistTheme} from '../../lib/themes/themePersistance'; -import {openThemeMenu, themeMenuOpen} from '../../reducers/menus.js'; -import {setTheme} from '../../reducers/theme.js'; - -import styles from './settings-menu.css'; - -import dropdownCaret from './dropdown-caret.svg'; - -const ThemeMenuItem = props => { - const themeInfo = themeMap[props.theme]; - - return ( - -
- - - -
-
); -}; - -ThemeMenuItem.propTypes = { - isSelected: PropTypes.bool, - onClick: PropTypes.func, - theme: PropTypes.string -}; - -const ThemeMenu = ({ - isRtl, - menuOpen, - onChangeTheme, - onRequestOpen, - theme -}) => { - const enabledThemes = [DEFAULT_THEME, HIGH_CONTRAST_THEME]; - const themeInfo = themeMap[theme]; - - return ( - -
- - - - - -
- - {enabledThemes.map(enabledTheme => ( - onChangeTheme(enabledTheme)} - theme={enabledTheme} - />) - )} - -
- ); -}; - -ThemeMenu.propTypes = { - isRtl: PropTypes.bool, - menuOpen: PropTypes.bool, - onChangeTheme: PropTypes.func, - // eslint-disable-next-line react/no-unused-prop-types - onRequestCloseSettings: PropTypes.func, - onRequestOpen: PropTypes.func, - theme: PropTypes.string -}; - -const mapStateToProps = state => ({ - isRtl: state.locales.isRtl, - menuOpen: themeMenuOpen(state), - theme: state.scratchGui.theme.theme -}); - -const mapDispatchToProps = (dispatch, ownProps) => ({ - onChangeTheme: theme => { - dispatch(setTheme(theme)); - ownProps.onRequestCloseSettings(); - persistTheme(theme); - }, - onRequestOpen: () => dispatch(openThemeMenu()) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ThemeMenu); diff --git a/packages/scratch-gui/src/components/monitor/monitor.jsx b/packages/scratch-gui/src/components/monitor/monitor.jsx index d79e34fc9a..df2f337483 100644 --- a/packages/scratch-gui/src/components/monitor/monitor.jsx +++ b/packages/scratch-gui/src/components/monitor/monitor.jsx @@ -8,7 +8,7 @@ import DefaultMonitor from './default-monitor.jsx'; import LargeMonitor from './large-monitor.jsx'; import SliderMonitor from '../../containers/slider-monitor.jsx'; import ListMonitor from '../../containers/list-monitor.jsx'; -import {getColorsForTheme} from '../../lib/themes/index.js'; +import {getColorsForMode} from '../../lib/settings/color-mode/index.js'; import contextMenuStyles from '../context-menu/context-menu.css'; import {MenuItem, BorderedMenuItem} from '../context-menu/context-menu.jsx'; @@ -33,8 +33,8 @@ const modes = { list: ListMonitor }; -const getCategoryColor = (theme, category) => { - const colors = getColorsForTheme(theme); +const getCategoryColor = (colorMode, category) => { + const colors = getColorsForMode(colorMode); return { background: colors[categoryColorMap[category]].primary, text: colors.text @@ -62,7 +62,7 @@ const MonitorComponent = props => ( {React.createElement(modes[props.mode], { categoryColor: getCategoryColor( - props.theme, + props.colorMode, props.category ), ...props @@ -159,7 +159,7 @@ MonitorComponent.propTypes = { onSetModeToLarge: PropTypes.func, onSetModeToSlider: PropTypes.func, onSliderPromptOpen: PropTypes.func, - theme: PropTypes.string.isRequired + colorMode: PropTypes.string.isRequired }; MonitorComponent.defaultProps = { diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index f5ea9d561b..295dcf1d9f 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -19,8 +19,9 @@ import {BLOCKS_DEFAULT_SCALE, STAGE_DISPLAY_SIZES} from '../lib/layout-constants import DropAreaHOC from '../lib/drop-area-hoc.jsx'; import DragConstants from '../lib/drag-constants'; import defineDynamicBlock from '../lib/define-dynamic-block'; -import {DEFAULT_THEME, getColorsForTheme, themeMap} from '../lib/themes'; -import {injectExtensionBlockTheme, injectExtensionCategoryTheme} from '../lib/themes/blockHelpers'; +import {DEFAULT_MODE, getColorsForMode, colorModeMap} from '../lib/settings/color-mode'; +import {CAT_BLOCKS_THEME} from '../lib/settings/theme'; +import {injectExtensionBlockMode, injectExtensionCategoryMode} from '../lib/settings/color-mode/blockHelpers'; import {connect} from 'react-redux'; import {updateToolbox} from '../reducers/toolbox'; @@ -103,7 +104,7 @@ class Blocks extends React.Component { const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options, - {rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme)} + {rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForMode(this.props.colorMode)} ); this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); @@ -362,15 +363,15 @@ class Blocks extends React.Component { const stageCostumes = stage.getCostumes(); const targetCostumes = target.getCostumes(); const targetSounds = target.getSounds(); - const dynamicBlocksXML = injectExtensionCategoryTheme( + const dynamicBlocksXML = injectExtensionCategoryMode( this.props.vm.runtime.getBlocksXML(target), - this.props.theme + this.props.colorMode ); return makeToolboxXML(false, target.isStage, target.id, dynamicBlocksXML, targetCostumes[targetCostumes.length - 1].name, stageCostumes[stageCostumes.length - 1].name, targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '', - getColorsForTheme(this.props.theme) + getColorsForMode(this.props.colorMode) ); } catch { return null; @@ -455,7 +456,7 @@ class Blocks extends React.Component { if (blockInfo.info && blockInfo.info.isDynamic) { dynamicBlocksInfo.push(blockInfo); } else if (blockInfo.json) { - staticBlocksJson.push(injectExtensionBlockTheme(blockInfo.json, this.props.theme)); + staticBlocksJson.push(injectExtensionBlockMode(blockInfo.json, this.props.colorMode)); } // otherwise it's a non-block entry such as '---' }); @@ -652,7 +653,7 @@ Blocks.propTypes = { collapse: PropTypes.bool }), stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired, - theme: PropTypes.oneOf(Object.keys(themeMap)), + colorMode: PropTypes.oneOf(Object.keys(colorModeMap)), toolboxXML: PropTypes.string, updateMetrics: PropTypes.func, updateToolboxState: PropTypes.func, @@ -684,7 +685,7 @@ Blocks.defaultOptions = { Blocks.defaultProps = { isVisible: true, options: Blocks.defaultOptions, - theme: DEFAULT_THEME + colorMode: DEFAULT_MODE }; const mapStateToProps = state => ({ @@ -699,7 +700,7 @@ const mapStateToProps = state => ({ toolboxXML: state.scratchGui.toolbox.toolboxXML, customProceduresVisible: state.scratchGui.customProcedures.active, workspaceMetrics: state.scratchGui.workspaceMetrics, - useCatBlocks: isTimeTravel2020(state) + useCatBlocks: isTimeTravel2020(state) || state.scratchGui.settings.theme === CAT_BLOCKS_THEME }); const mapDispatchToProps = dispatch => ({ diff --git a/packages/scratch-gui/src/containers/monitor.jsx b/packages/scratch-gui/src/containers/monitor.jsx index 69489fc34a..110766ab2a 100644 --- a/packages/scratch-gui/src/containers/monitor.jsx +++ b/packages/scratch-gui/src/containers/monitor.jsx @@ -10,7 +10,7 @@ import {addMonitorRect, getInitialPosition, resizeMonitorRect, removeMonitorRect import {getVariable, setVariableValue} from '../lib/variable-utils'; import importCSV from '../lib/import-csv'; import downloadBlob from '../lib/download-blob'; -import {DEFAULT_THEME} from '../lib/themes'; +import {DEFAULT_MODE} from '../lib/settings/color-mode/index.js'; import SliderPrompt from './slider-prompt.jsx'; import {connect} from 'react-redux'; @@ -215,7 +215,7 @@ class Monitor extends React.Component { min={this.props.min} mode={this.props.mode} targetId={this.props.targetId} - theme={this.props.theme} + colorMode={this.props.colorMode} width={this.props.width} onDragEnd={this.handleDragEnd} onExport={isList ? this.handleExport : null} @@ -253,7 +253,7 @@ Monitor.propTypes = { resizeMonitorRect: PropTypes.func.isRequired, spriteName: PropTypes.string, targetId: PropTypes.string, - theme: PropTypes.string, + colorMode: PropTypes.string, toolboxXML: PropTypes.string, value: PropTypes.oneOfType([ PropTypes.string, @@ -269,11 +269,11 @@ Monitor.propTypes = { y: PropTypes.number }; Monitor.defaultProps = { - theme: DEFAULT_THEME + colorMode: DEFAULT_MODE }; const mapStateToProps = state => ({ monitorLayout: state.scratchGui.monitorLayout, - theme: state.scratchGui.theme.theme, + colorMode: state.scratchGui.settings.colorMode, // render on toolbox updates since changes to the blocks could affect monitor labels, i.e. updated locale toolboxXML: state.scratchGui.toolbox.toolboxXML, vm: state.scratchGui.vm diff --git a/packages/scratch-gui/src/lib/assets/icon--theme.svg b/packages/scratch-gui/src/lib/assets/icon--theme.svg new file mode 100644 index 0000000000..ae95a23fcd --- /dev/null +++ b/packages/scratch-gui/src/lib/assets/icon--theme.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 14fafc92c9..2768ac06d7 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -5,7 +5,15 @@ * @returns {ScratchBlocks} ScratchBlocks connected with the vm */ export default function (vm, useCatBlocks) { - const ScratchBlocks = useCatBlocks ? require('cat-blocks') : require('scratch-blocks'); + const ScratchBlocks = require('scratch-blocks'); + + // TODO: Set theme from editor settings + if (useCatBlocks) { + ScratchBlocks.setTheme(ScratchBlocks.Themes.CAT_BLOCKS); + } else { + ScratchBlocks.setTheme(ScratchBlocks.Themes.CLASSIC); + } + const jsonForMenuBlock = function (name, menuOptionsFn, colors, start) { return { message0: '%1', diff --git a/packages/scratch-gui/src/lib/make-toolbox-xml.js b/packages/scratch-gui/src/lib/make-toolbox-xml.js index 90346b8b03..74faccf99c 100644 --- a/packages/scratch-gui/src/lib/make-toolbox-xml.js +++ b/packages/scratch-gui/src/lib/make-toolbox-xml.js @@ -1,5 +1,5 @@ import ScratchBlocks from 'scratch-blocks'; -import {defaultColors} from './themes'; +import {defaultColors} from './settings/color-mode'; const categorySeparator = ''; @@ -754,7 +754,7 @@ const xmlClose = ''; * @param {?string} costumeName - The name of the default selected costume dropdown. * @param {?string} backdropName - The name of the default selected backdrop dropdown. * @param {?string} soundName - The name of the default selected sound dropdown. - * @param {?object} colors - The colors for the theme. + * @param {?object} colors - The colors for the color mode. * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox. */ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categoriesXML = [], diff --git a/packages/scratch-gui/src/lib/themes/blockHelpers.js b/packages/scratch-gui/src/lib/settings/color-mode/blockHelpers.js similarity index 66% rename from packages/scratch-gui/src/lib/themes/blockHelpers.js rename to packages/scratch-gui/src/lib/settings/color-mode/blockHelpers.js index b201241eeb..56e5dd6129 100644 --- a/packages/scratch-gui/src/lib/themes/blockHelpers.js +++ b/packages/scratch-gui/src/lib/settings/color-mode/blockHelpers.js @@ -1,4 +1,4 @@ -import {DEFAULT_THEME, getColorsForTheme, themeMap} from '.'; +import {DEFAULT_MODE, getColorsForMode, colorModeMap} from '.'; const getBlockIconURI = extensionIcons => { if (!extensionIcons) return null; @@ -13,23 +13,23 @@ const getCategoryIconURI = extensionIcons => { }; // scratch-blocks colours has a pen property that scratch-gui uses for all extensions -const getExtensionColors = theme => getColorsForTheme(theme).pen; +const getExtensionColors = mode => getColorsForMode(mode).pen; /** - * Applies extension color theme to categories. - * No changes are applied if called with the default theme, allowing extensions to provide their own colors. + * Applies extension color mode to categories. + * No changes are applied if called with the default color mode, allowing extensions to provide their own colors. * These colors are not seen if the category provides a blockIconURI. * @param {Array.} dynamicBlockXML - XML for each category of extension blocks, returned from getBlocksXML * in the vm runtime. - * @param {string} theme - Theme name + * @param {string} mode - Color Mode name * @returns {Array.} Dynamic block XML updated with colors. */ -const injectExtensionCategoryTheme = (dynamicBlockXML, theme) => { - // Don't do any manipulation for the default theme - if (theme === DEFAULT_THEME) return dynamicBlockXML; +const injectExtensionCategoryMode = (dynamicBlockXML, mode) => { + // Don't do any manipulation for the default mode + if (mode === DEFAULT_MODE) return dynamicBlockXML; - const extensionColors = getExtensionColors(theme); - const extensionIcons = themeMap[theme].extensions; + const extensionColors = getExtensionColors(mode); + const extensionIcons = colorModeMap[mode].extensions; const parser = new DOMParser(); const serializer = new XMLSerializer(); @@ -52,12 +52,12 @@ const injectExtensionCategoryTheme = (dynamicBlockXML, theme) => { }); }; -const injectBlockIcons = (blockInfoJson, theme) => { +const injectBlockIcons = (blockInfoJson, mode) => { // Block icons are the first element of `args0` if (!blockInfoJson.args0 || blockInfoJson.args0.length < 1 || blockInfoJson.args0[0].type !== 'field_image') return blockInfoJson; - const extensionIcons = themeMap[theme].extensions; + const extensionIcons = colorModeMap[mode].extensions; const extensionId = blockInfoJson.type.substring(0, blockInfoJson.type.indexOf('_')); const blockIconURI = getBlockIconURI(extensionIcons[extensionId]); @@ -77,20 +77,20 @@ const injectBlockIcons = (blockInfoJson, theme) => { }; /** - * Applies extension color theme to static block json. - * No changes are applied if called with the default theme, allowing extensions to provide their own colors. + * Applies extension color mode to static block json. + * No changes are applied if called with the default mode, allowing extensions to provide their own colors. * @param {object} blockInfoJson - Static block json - * @param {string} theme - Theme name + * @param {string} mode - Color Mode name * @returns {object} Block info json with updated colors. The original blockInfoJson is not modified. */ -const injectExtensionBlockTheme = (blockInfoJson, theme) => { - // Don't do any manipulation for the default theme - if (theme === DEFAULT_THEME) return blockInfoJson; +const injectExtensionBlockMode = (blockInfoJson, mode) => { + // Don't do any manipulation for the default mode + if (mode === DEFAULT_MODE) return blockInfoJson; - const extensionColors = getExtensionColors(theme); + const extensionColors = getExtensionColors(mode); return { - ...injectBlockIcons(blockInfoJson, theme), + ...injectBlockIcons(blockInfoJson, mode), colour: extensionColors.primary, colourSecondary: extensionColors.secondary, colourTertiary: extensionColors.tertiary, @@ -99,6 +99,6 @@ const injectExtensionBlockTheme = (blockInfoJson, theme) => { }; export { - injectExtensionBlockTheme, - injectExtensionCategoryTheme + injectExtensionBlockMode, + injectExtensionCategoryMode }; diff --git a/packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js b/packages/scratch-gui/src/lib/settings/color-mode/dark/__mocks__/index.js similarity index 100% rename from packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/dark/__mocks__/index.js diff --git a/packages/scratch-gui/src/lib/themes/dark/index.js b/packages/scratch-gui/src/lib/settings/color-mode/dark/index.js similarity index 100% rename from packages/scratch-gui/src/lib/themes/dark/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/dark/index.js diff --git a/packages/scratch-gui/src/lib/themes/default/__mocks__/index.js b/packages/scratch-gui/src/lib/settings/color-mode/default/__mocks__/index.js similarity index 100% rename from packages/scratch-gui/src/lib/themes/default/__mocks__/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/default/__mocks__/index.js diff --git a/packages/scratch-gui/src/lib/themes/default/icon.svg b/packages/scratch-gui/src/lib/settings/color-mode/default/icon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/default/icon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/default/icon.svg diff --git a/packages/scratch-gui/src/lib/themes/default/index.js b/packages/scratch-gui/src/lib/settings/color-mode/default/index.js similarity index 100% rename from packages/scratch-gui/src/lib/themes/default/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/default/index.js diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/comment-arrow-down.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/comment-arrow-down.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/comment-arrow-down.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/comment-arrow-down.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/comment-arrow-up.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/comment-arrow-up.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/comment-arrow-up.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/comment-arrow-up.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/delete-x.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/delete-x.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/delete-x.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/delete-x.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/dropdown-arrow.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/dropdown-arrow.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/dropdown-arrow.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/dropdown-arrow.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/eyedropper.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/eyedropper.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/eyedropper.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/eyedropper.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/icons/arrow_button.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/icons/arrow_button.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/icons/arrow_button.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/icons/arrow_button.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/repeat.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/repeat.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/repeat.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/repeat.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/rotate-left.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/rotate-left.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/rotate-left.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/rotate-left.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/rotate-right.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/rotate-right.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/rotate-right.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/rotate-right.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-in.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-in.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-in.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-in.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-out.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-out.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-out.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-out.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-reset.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-reset.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/blocks-media/zoom-reset.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/blocks-media/zoom-reset.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/faceSensingIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/faceSensingIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/faceSensingIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/faceSensingIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/musicIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/musicIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/musicIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/musicIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/penIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/penIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/penIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/penIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/text2speechIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/text2speechIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/text2speechIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/text2speechIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/translateIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/translateIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/translateIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/translateIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/extensions/videoSensingIcon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/videoSensingIcon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/extensions/videoSensingIcon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/extensions/videoSensingIcon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/icon.svg b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/icon.svg similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/icon.svg rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/icon.svg diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/index.js b/packages/scratch-gui/src/lib/settings/color-mode/high-contrast/index.js similarity index 100% rename from packages/scratch-gui/src/lib/themes/high-contrast/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/high-contrast/index.js diff --git a/packages/scratch-gui/src/lib/themes/index.js b/packages/scratch-gui/src/lib/settings/color-mode/index.js similarity index 62% rename from packages/scratch-gui/src/lib/themes/index.js rename to packages/scratch-gui/src/lib/settings/color-mode/index.js index 673d453e74..a014d91b8e 100644 --- a/packages/scratch-gui/src/lib/themes/index.js +++ b/packages/scratch-gui/src/lib/settings/color-mode/index.js @@ -14,68 +14,68 @@ import {blockColors as defaultColors} from './default'; import defaultIcon from './default/icon.svg'; import highContrastIcon from './high-contrast/icon.svg'; -const DEFAULT_THEME = 'default'; -const HIGH_CONTRAST_THEME = 'high-contrast'; -const DARK_THEME = 'dark'; +const DEFAULT_MODE = 'default'; +const HIGH_CONTRAST_MODE = 'high-contrast'; +const DARK_MODE = 'dark'; const mergeWithDefaults = colors => defaultsDeep({}, colors, defaultColors); const messages = defineMessages({ - [DEFAULT_THEME]: { + [DEFAULT_MODE]: { id: 'gui.theme.default', defaultMessage: 'Original', - description: 'label for original theme' + description: 'label for original color mode' }, - [DARK_THEME]: { + [DARK_MODE]: { id: 'gui.theme.dark', defaultMessage: 'Dark', - description: 'label for dark mode theme' + description: 'label for dark mode' }, - [HIGH_CONTRAST_THEME]: { + [HIGH_CONTRAST_MODE]: { id: 'gui.theme.highContrast', defaultMessage: 'High Contrast', - description: 'label for high theme' + description: 'label for high contrast mode' } }); -const themeMap = { - [DEFAULT_THEME]: { +const colorModeMap = { + [DEFAULT_MODE]: { blocksMediaFolder: 'blocks-media/default', colors: defaultColors, extensions: {}, - label: messages[DEFAULT_THEME], + label: messages[DEFAULT_MODE], icon: defaultIcon }, - [DARK_THEME]: { + [DARK_MODE]: { blocksMediaFolder: 'blocks-media/default', colors: mergeWithDefaults(darkModeBlockColors), extensions: darkModeExtensions, - label: messages[DARK_THEME] + label: messages[DARK_MODE] }, - [HIGH_CONTRAST_THEME]: { + [HIGH_CONTRAST_MODE]: { blocksMediaFolder: 'blocks-media/high-contrast', colors: mergeWithDefaults(highContrastBlockColors), extensions: highContrastExtensions, - label: messages[HIGH_CONTRAST_THEME], + label: messages[HIGH_CONTRAST_MODE], icon: highContrastIcon } }; -const getColorsForTheme = theme => { - const themeInfo = themeMap[theme]; +const getColorsForMode = colorMode => { + const modeInfo = colorModeMap[colorMode]; - if (!themeInfo) { - throw new Error(`Undefined theme ${theme}`); + if (!modeInfo) { + throw new Error(`Undefined color mode ${colorMode}`); } - return themeInfo.colors; + return modeInfo.colors; }; export { - DEFAULT_THEME, - DARK_THEME, - HIGH_CONTRAST_THEME, + DEFAULT_MODE, + DARK_MODE, + HIGH_CONTRAST_MODE, defaultColors, - getColorsForTheme, - themeMap + getColorsForMode, + colorModeMap }; diff --git a/packages/scratch-gui/src/lib/settings/color-mode/persistence.js b/packages/scratch-gui/src/lib/settings/color-mode/persistence.js new file mode 100644 index 0000000000..a26e79186d --- /dev/null +++ b/packages/scratch-gui/src/lib/settings/color-mode/persistence.js @@ -0,0 +1,47 @@ +import cookie from 'cookie'; + +import {DEFAULT_MODE, HIGH_CONTRAST_MODE} from '.'; + +const PREFERS_HIGH_CONTRAST_QUERY = '(prefers-contrast: more)'; +// Technically what we are persisting is the color mode, but for historical reasons, +// we should continue using 'scratchtheme' as the cookie key. +const COOKIE_KEY = 'scratchtheme'; + +// Dark mode isn't enabled yet +const isValidColorMode = colorMode => [DEFAULT_MODE, HIGH_CONTRAST_MODE].includes(colorMode); + +const systemPreferencesColorMode = () => { + if (window.matchMedia && window.matchMedia(PREFERS_HIGH_CONTRAST_QUERY).matches) return HIGH_CONTRAST_MODE; + + return DEFAULT_MODE; +}; + +const detectColorMode = () => { + const obj = cookie.parse(document.cookie) || {}; + const colorModeCookie = obj.scratchtheme; + + if (isValidColorMode(colorModeCookie)) return colorModeCookie; + + // No cookie set. Fall back to system preferences + return systemPreferencesColorMode(); +}; + +const persistColorMode = mode => { + if (!isValidColorMode(mode)) { + throw new Error(`Invalid color mode: ${mode}`); + } + + if (systemPreferencesColorMode() === mode) { + // Clear the cookie to represent using the system preferences + document.cookie = `${COOKIE_KEY}=;path=/`; + return; + } + + const expires = new Date(new Date().setYear(new Date().getFullYear() + 1)).toUTCString(); + document.cookie = `${COOKIE_KEY}=${mode};expires=${expires};path=/`; +}; + +export { + detectColorMode, + persistColorMode +}; diff --git a/packages/scratch-gui/src/lib/settings/theme/index.js b/packages/scratch-gui/src/lib/settings/theme/index.js new file mode 100644 index 0000000000..7e3c44952e --- /dev/null +++ b/packages/scratch-gui/src/lib/settings/theme/index.js @@ -0,0 +1,37 @@ +import {defineMessages} from 'react-intl'; + +const DEFAULT_THEME = 'default'; +const CAT_BLOCKS_THEME = 'cat-blocks'; + +const messages = defineMessages({ + [DEFAULT_THEME]: { + id: 'gui.blockTheme.default', + defaultMessage: 'Default', + description: 'label for default theme' + }, + [CAT_BLOCKS_THEME]: { + id: 'gui.blockTheme.catBlocks', + defaultMessage: 'Cat Blocks', + description: 'label for cat blocks theme' + } +}); + +// Keeping this as a map for consistency with the color modes +const themeMap = { + [DEFAULT_THEME]: { + label: messages[DEFAULT_THEME], + isAvailable: () => true + }, + [CAT_BLOCKS_THEME]: { + label: messages[CAT_BLOCKS_THEME], + // TODO: This should probably also depend on `isTimeTravel2020`, + // but it should be fine to stay as-is for now. + isAvailable: userInfo => userInfo.hasActiveMembership + } +}; + +export { + DEFAULT_THEME, + CAT_BLOCKS_THEME, + themeMap +}; diff --git a/packages/scratch-gui/src/lib/settings/theme/persistence.js b/packages/scratch-gui/src/lib/settings/theme/persistence.js new file mode 100644 index 0000000000..968ff504c7 --- /dev/null +++ b/packages/scratch-gui/src/lib/settings/theme/persistence.js @@ -0,0 +1,35 @@ +import cookie from 'cookie'; + +import {DEFAULT_THEME, CAT_BLOCKS_THEME} from '.'; + +const COOKIE_KEY = 'scratchblockstheme'; + +const isValidTheme = theme => [DEFAULT_THEME, CAT_BLOCKS_THEME].includes(theme); + +// TODO: The correct way of implementing this would be to know the user info here +// (e.g., membership status) and filter out unavailable themes. However, there is no easy way to currently do this, +// as user info is generally passed as component properties to GUI, rather than through redux. +// For now, we'll take the user preference here as-is and switch to default theme if needed, +// once we have the user info available. +const detectTheme = () => { + const obj = cookie.parse(document.cookie) || {}; + const themeCookie = obj[COOKIE_KEY]; + + if (isValidTheme(themeCookie)) return themeCookie; + + return DEFAULT_THEME; +}; + +const persistTheme = theme => { + if (!isValidTheme(theme)) { + throw new Error(`Invalid theme: ${theme}`); + } + + const expires = new Date(new Date().setYear(new Date().getFullYear() + 1)).toUTCString(); + document.cookie = `${COOKIE_KEY}=${theme};expires=${expires};path=/`; +}; + +export { + detectTheme, + persistTheme +}; diff --git a/packages/scratch-gui/src/lib/system-preferences-hoc.jsx b/packages/scratch-gui/src/lib/system-preferences-hoc.jsx index 2c9a4e251c..99ce3aa56e 100644 --- a/packages/scratch-gui/src/lib/system-preferences-hoc.jsx +++ b/packages/scratch-gui/src/lib/system-preferences-hoc.jsx @@ -2,8 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; -import {setTheme} from '../reducers/theme'; -import {detectTheme} from './themes/themePersistance'; +import {setColorMode} from '../reducers/settings'; +import {detectColorMode} from './settings/color-mode/persistence'; // Dark mode is not yet supported // const prefersDarkQuery = '(prefers-color-scheme: dark)'; @@ -12,7 +12,7 @@ const prefersHighContrastQuery = '(prefers-contrast: more)'; const systemPreferencesHOC = function (WrappedComponent) { class SystemPreferences extends React.Component { componentDidMount () { - this.preferencesListener = () => this.props.onSetTheme(detectTheme()); + this.preferencesListener = () => this.props.onSetColorMode(detectColorMode()); if (window.matchMedia) { this.highContrastMatchMedia = window.matchMedia(prefersHighContrastQuery); @@ -39,7 +39,7 @@ const systemPreferencesHOC = function (WrappedComponent) { render () { const { - onSetTheme, + onSetColorMode, ...props } = this.props; @@ -48,11 +48,11 @@ const systemPreferencesHOC = function (WrappedComponent) { } SystemPreferences.propTypes = { - onSetTheme: PropTypes.func + onSetColorMode: PropTypes.func }; const mapDispatchToProps = dispatch => ({ - onSetTheme: theme => dispatch(setTheme(theme)) + onSetColorMode: mode => dispatch(setColorMode(mode)) }); return connect( diff --git a/packages/scratch-gui/src/lib/themes/themePersistance.js b/packages/scratch-gui/src/lib/themes/themePersistance.js deleted file mode 100644 index 6056708651..0000000000 --- a/packages/scratch-gui/src/lib/themes/themePersistance.js +++ /dev/null @@ -1,45 +0,0 @@ -import cookie from 'cookie'; - -import {DEFAULT_THEME, HIGH_CONTRAST_THEME} from '.'; - -const PREFERS_HIGH_CONTRAST_QUERY = '(prefers-contrast: more)'; -const COOKIE_KEY = 'scratchtheme'; - -// Dark mode isn't enabled yet -const isValidTheme = theme => [DEFAULT_THEME, HIGH_CONTRAST_THEME].includes(theme); - -const systemPreferencesTheme = () => { - if (window.matchMedia && window.matchMedia(PREFERS_HIGH_CONTRAST_QUERY).matches) return HIGH_CONTRAST_THEME; - - return DEFAULT_THEME; -}; - -const detectTheme = () => { - const obj = cookie.parse(document.cookie) || {}; - const themeCookie = obj.scratchtheme; - - if (isValidTheme(themeCookie)) return themeCookie; - - // No cookie set. Fall back to system preferences - return systemPreferencesTheme(); -}; - -const persistTheme = theme => { - if (!isValidTheme(theme)) { - throw new Error(`Invalid theme: ${theme}`); - } - - if (systemPreferencesTheme() === theme) { - // Clear the cookie to represent using the system preferences - document.cookie = `${COOKIE_KEY}=;path=/`; - return; - } - - const expires = new Date(new Date().setYear(new Date().getFullYear() + 1)).toUTCString(); - document.cookie = `${COOKIE_KEY}=${theme};expires=${expires};path=/`; -}; - -export { - detectTheme, - persistTheme -}; diff --git a/packages/scratch-gui/src/reducers/gui.ts b/packages/scratch-gui/src/reducers/gui.ts index 327b483689..516b651ac4 100644 --- a/packages/scratch-gui/src/reducers/gui.ts +++ b/packages/scratch-gui/src/reducers/gui.ts @@ -22,7 +22,7 @@ import fontsLoadedReducer, {fontsLoadedInitialState} from './fonts-loaded'; import restoreDeletionReducer, {restoreDeletionInitialState} from './restore-deletion'; import stageSizeReducer, {stageSizeInitialState} from './stage-size'; import targetReducer, {targetsInitialState} from './targets'; -import themeReducer, {themeInitialState} from './theme'; +import settingsReducer, {settingsInitialState} from './settings'; import timeoutReducer, {timeoutInitialState} from './timeout'; import timeTravelReducer, {timeTravelInitialState} from './time-travel'; import toolboxReducer, {toolboxInitialState} from './toolbox'; @@ -61,7 +61,7 @@ const buildInitialState = (config: GUIConfig) => ({ fontsLoaded: fontsLoadedInitialState, restoreDeletion: restoreDeletionInitialState, targets: targetsInitialState, - theme: themeInitialState, + settings: settingsInitialState, timeout: timeoutInitialState, timeTravel: timeTravelInitialState, toolbox: toolboxInitialState, @@ -170,7 +170,7 @@ const guiReducer = combineReducers({ fontsLoaded: fontsLoadedReducer, restoreDeletion: restoreDeletionReducer, targets: targetReducer, - theme: themeReducer, + settings: settingsReducer, timeout: timeoutReducer, timeTravel: timeTravelReducer, toolbox: toolboxReducer, diff --git a/packages/scratch-gui/src/reducers/menus.js b/packages/scratch-gui/src/reducers/menus.js index 4fec54b8a6..8e62120337 100644 --- a/packages/scratch-gui/src/reducers/menus.js +++ b/packages/scratch-gui/src/reducers/menus.js @@ -9,6 +9,7 @@ const MENU_LANGUAGE = 'languageMenu'; const MENU_LOGIN = 'loginMenu'; const MENU_MODE = 'modeMenu'; const MENU_SETTINGS = 'settingsMenu'; +const MENU_COLOR_MODE = 'colorModeMenu'; const MENU_THEME = 'themeMenu'; class Menu { @@ -51,6 +52,7 @@ const rootMenu = new Menu('root') .addChild( new Menu(MENU_SETTINGS) .addChild(new Menu(MENU_LANGUAGE)) + .addChild(new Menu(MENU_COLOR_MODE)) .addChild(new Menu(MENU_THEME)) ) .addChild(new Menu(MENU_FILE)) @@ -70,6 +72,7 @@ const initialState = { [MENU_LOGIN]: false, [MENU_MODE]: false, [MENU_SETTINGS]: false, + [MENU_COLOR_MODE]: false, [MENU_THEME]: false }; @@ -142,6 +145,10 @@ const openSettingsMenu = () => openMenu(MENU_SETTINGS); const closeSettingsMenu = () => closeMenu(MENU_SETTINGS); const settingsMenuOpen = state => state.scratchGui.menus[MENU_SETTINGS]; +const openColorModeMenu = () => openMenu(MENU_COLOR_MODE); +const closeColorModeMenu = () => closeMenu(MENU_COLOR_MODE); +const colorModeMenuOpen = state => state.scratchGui.menus[MENU_COLOR_MODE]; + const openThemeMenu = () => openMenu(MENU_THEME); const closeThemeMenu = () => closeMenu(MENU_THEME); const themeMenuOpen = state => state.scratchGui.menus[MENU_THEME]; @@ -173,6 +180,9 @@ export { openSettingsMenu, closeSettingsMenu, settingsMenuOpen, + openColorModeMenu, + closeColorModeMenu, + colorModeMenuOpen, openThemeMenu, closeThemeMenu, themeMenuOpen diff --git a/packages/scratch-gui/src/reducers/settings.js b/packages/scratch-gui/src/reducers/settings.js new file mode 100644 index 0000000000..bf520ae58c --- /dev/null +++ b/packages/scratch-gui/src/reducers/settings.js @@ -0,0 +1,38 @@ +import {detectColorMode} from '../lib/settings/color-mode/persistence'; +import {detectTheme} from '../lib/settings/theme/persistence'; + +const SET_COLOR_MODE = 'scratch-gui/settings/SET_COLOR_MODE'; +const SET_THEME = 'scratch-gui/settings/SET_THEME'; + +const initialState = { + colorMode: detectColorMode(), + theme: detectTheme() +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case SET_COLOR_MODE: + return {...state, colorMode: action.colorMode}; + case SET_THEME: + return {...state, theme: action.theme}; + default: + return state; + } +}; + +const setColorMode = colorMode => ({ + type: SET_COLOR_MODE, + colorMode +}); + +const setTheme = theme => ({ + type: SET_THEME, + theme +}); + +export { + reducer as default, + initialState as settingsInitialState, + setColorMode, + setTheme +}; diff --git a/packages/scratch-gui/src/reducers/theme.js b/packages/scratch-gui/src/reducers/theme.js deleted file mode 100644 index 4330f94dad..0000000000 --- a/packages/scratch-gui/src/reducers/theme.js +++ /dev/null @@ -1,27 +0,0 @@ -import {detectTheme} from '../lib/themes/themePersistance'; - -const SET_THEME = 'scratch-gui/theme/SET_THEME'; - -const initialState = { - theme: detectTheme() -}; - -const reducer = (state = initialState, action) => { - switch (action.type) { - case SET_THEME: - return {...state, theme: action.theme}; - default: - return state; - } -}; - -const setTheme = theme => ({ - type: SET_THEME, - theme -}); - -export { - reducer as default, - initialState as themeInitialState, - setTheme -}; diff --git a/packages/scratch-gui/test/integration/menu-bar.test.js b/packages/scratch-gui/test/integration/menu-bar.test.js index 46103bf793..1927246177 100644 --- a/packages/scratch-gui/test/integration/menu-bar.test.js +++ b/packages/scratch-gui/test/integration/menu-bar.test.js @@ -95,7 +95,7 @@ describe('Menu bar settings', () => { await findByText('project1-sprite'); }); - test('Theme picker shows themes', async () => { + test('Color mode picker shows color modes', async () => { await loadUri(uri); await clickXpath(SETTINGS_MENU_XPATH); await clickText('Color Mode', scope.menuBar); @@ -104,13 +104,13 @@ describe('Menu bar settings', () => { expect(await (await findByText('High Contrast', scope.menuBar)).isDisplayed()).toBe(true); }); - test('Theme picker switches to high contrast', async () => { + test('Color mode picker switches to high contrast', async () => { await loadUri(uri); await clickXpath(SETTINGS_MENU_XPATH); await clickText('Color Mode', scope.menuBar); await clickText('High Contrast', scope.menuBar); - // There is a tiny delay for the color theme to be applied to the categories. + // There is a tiny delay for the color color mode to be applied to the categories. await driver.wait(async () => { const motionCategoryDiv = await findByXpath( '//div[contains(@class, "scratchCategoryMenuItem") and ' + @@ -121,20 +121,20 @@ describe('Menu bar settings', () => { // returns the value. Locally I am seeing 'rgba(128, 181, 255, 1)', // but this is a bit flexible just in case. return /128,\s?181,\s?255/.test(color) || color.includes('80B5FF'); - }, 5000, 'Motion category color does not match high contrast theme'); + }, 5000, 'Motion category color does not match high contrast color mode'); }); test('Settings menu switches between submenus', async () => { await loadUri(uri); await clickXpath(SETTINGS_MENU_XPATH); - // Language and theme options not visible yet + // Language and color mode options not visible yet expect(await (await findByText('High Contrast', scope.menuBar)).isDisplayed()).toBe(false); expect(await (await findByText('Esperanto', scope.menuBar)).isDisplayed()).toBe(false); await clickText('Color Mode', scope.menuBar); - // Only theme options visible + // Only color mode options visible expect(await (await findByText('High Contrast', scope.menuBar)).isDisplayed()).toBe(true); expect(await (await findByText('Esperanto', scope.menuBar)).isDisplayed()).toBe(false); diff --git a/packages/scratch-gui/test/unit/components/__snapshots__/monitor.test.jsx.snap b/packages/scratch-gui/test/unit/components/__snapshots__/monitor.test.jsx.snap index 11230197cf..e6770d0f10 100644 --- a/packages/scratch-gui/test/unit/components/__snapshots__/monitor.test.jsx.snap +++ b/packages/scratch-gui/test/unit/components/__snapshots__/monitor.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Monitor Component it selects the correct colors based on dark mode theme 1`] = ` +exports[`Monitor Component it selects the correct colors based on dark mode 1`] = `
`; -exports[`Monitor Component it selects the correct colors based on default theme 1`] = ` +exports[`Monitor Component it selects the correct colors based on default color mode 1`] = `
{ projectState: { loadingState: LoadingState.NOT_LOADED }, - theme: { - theme: DEFAULT_THEME + settings: { + colorMode: DEFAULT_MODE }, timeTravel: { year: 'NOW' diff --git a/packages/scratch-gui/test/unit/components/monitor-list.test.jsx b/packages/scratch-gui/test/unit/components/monitor-list.test.jsx index 51edd6caaa..8f60e5cb9b 100644 --- a/packages/scratch-gui/test/unit/components/monitor-list.test.jsx +++ b/packages/scratch-gui/test/unit/components/monitor-list.test.jsx @@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store'; import {Provider} from 'react-redux'; import {renderWithIntl} from '../../helpers/intl-helpers.jsx'; import MonitorList from '../../../src/components/monitor-list/monitor-list.jsx'; -import {DEFAULT_THEME} from '../../../src/lib/themes'; +import {DEFAULT_MODE} from '../../../src/lib/settings/color-mode'; describe('MonitorListComponent', () => { const store = configureStore()({ @@ -13,8 +13,8 @@ describe('MonitorListComponent', () => { monitors: {}, savedMonitorPositions: {} }, - theme: { - theme: DEFAULT_THEME + settings: { + colorMode: DEFAULT_MODE }, toolbox: { toolboxXML: '' diff --git a/packages/scratch-gui/test/unit/components/monitor.test.jsx b/packages/scratch-gui/test/unit/components/monitor.test.jsx index 7a5d0b0c28..853510adcc 100644 --- a/packages/scratch-gui/test/unit/components/monitor.test.jsx +++ b/packages/scratch-gui/test/unit/components/monitor.test.jsx @@ -1,10 +1,10 @@ import React from 'react'; import {render} from '@testing-library/react'; import Monitor from '../../../src/components/monitor/monitor'; -import {DARK_THEME, DEFAULT_THEME} from '../../../src/lib/themes'; +import {DARK_MODE, DEFAULT_MODE} from '../../../src/lib/settings/color-mode'; -jest.mock('../../../src/lib/themes/default'); -jest.mock('../../../src/lib/themes/dark'); +jest.mock('../../../src/lib/settings/color-mode/default'); +jest.mock('../../../src/lib/settings/color-mode/dark'); describe('Monitor Component', () => { const noop = jest.fn(); @@ -22,19 +22,19 @@ describe('Monitor Component', () => { onNextMode: noop }; - test('it selects the correct colors based on default theme', () => { + test('it selects the correct colors based on default color mode', () => { const {container} = render(); expect(container.firstChild).toMatchSnapshot(); }); - test('it selects the correct colors based on dark mode theme', () => { + test('it selects the correct colors based on dark mode', () => { const {container} = render(); expect(container.firstChild).toMatchSnapshot(); diff --git a/packages/scratch-gui/test/unit/util/themes.test.js b/packages/scratch-gui/test/unit/util/color-modes.test.js similarity index 66% rename from packages/scratch-gui/test/unit/util/themes.test.js rename to packages/scratch-gui/test/unit/util/color-modes.test.js index 35d8fa25b9..ba0e315daa 100644 --- a/packages/scratch-gui/test/unit/util/themes.test.js +++ b/packages/scratch-gui/test/unit/util/color-modes.test.js @@ -1,32 +1,32 @@ import { - DARK_THEME, + DARK_MODE, defaultColors, - DEFAULT_THEME, - getColorsForTheme, - HIGH_CONTRAST_THEME -} from '../../../src/lib/themes'; -import {injectExtensionBlockTheme, injectExtensionCategoryTheme} from '../../../src/lib/themes/blockHelpers'; -import {detectTheme, persistTheme} from '../../../src/lib/themes/themePersistance'; + DEFAULT_MODE, + getColorsForMode, + HIGH_CONTRAST_MODE +} from '../../../src/lib/settings/color-mode'; +import {injectExtensionBlockMode, injectExtensionCategoryMode} from '../../../src/lib/settings/color-mode/blockHelpers'; +import {detectColorMode, persistColorMode} from '../../../src/lib/settings/color-mode/persistence'; -jest.mock('../../../src/lib/themes/default'); -jest.mock('../../../src/lib/themes/dark'); +jest.mock('../../../src/lib/settings/color-mode/default'); +jest.mock('../../../src/lib/settings/color-mode/dark'); -describe('themes', () => { +describe('color modes', () => { let serializeToString; describe('core functionality', () => { - test('provides the default theme colors', () => { + test('provides the default color mode colors', () => { expect(defaultColors.motion.primary).toEqual('#111111'); }); test('returns the dark mode', () => { - const colors = getColorsForTheme(DARK_THEME); + const colors = getColorsForMode(DARK_MODE); expect(colors.motion.primary).toEqual('#AAAAAA'); }); - test('uses default theme colors when not specified', () => { - const colors = getColorsForTheme(DARK_THEME); + test('uses default color mode colors when not specified', () => { + const colors = getColorsForMode(DARK_MODE); expect(colors.motion.secondary).toEqual('#222222'); }); @@ -45,7 +45,7 @@ describe('themes', () => { global.XMLSerializer = XMLSerializer; }); - test('updates extension block colors based on theme', () => { + test('updates extension block colors based on color mode', () => { const blockInfoJson = { type: 'dummy_block', colour: '#0FBD8C', @@ -53,7 +53,7 @@ describe('themes', () => { colourTertiary: '#0B8E69' }; - const updated = injectExtensionBlockTheme(blockInfoJson, DARK_THEME); + const updated = injectExtensionBlockMode(blockInfoJson, DARK_MODE); expect(updated).toEqual({ type: 'dummy_block', @@ -65,7 +65,7 @@ describe('themes', () => { expect(blockInfoJson.colour).toBe('#0FBD8C'); }); - test('updates extension block icon based on theme', () => { + test('updates extension block icon based on color mode', () => { const blockInfoJson = { type: 'pen_block', args0: [ @@ -79,7 +79,7 @@ describe('themes', () => { colourTertiary: '#0B8E69' }; - const updated = injectExtensionBlockTheme(blockInfoJson, DARK_THEME); + const updated = injectExtensionBlockMode(blockInfoJson, DARK_MODE); expect(updated).toEqual({ type: 'pen_block', @@ -97,7 +97,7 @@ describe('themes', () => { expect(blockInfoJson.args0[0].src).toBe('original'); }); - test('bypasses updates if using the default theme', () => { + test('bypasses updates if using the default color mode', () => { const blockInfoJson = { type: 'dummy_block', colour: '#0FBD8C', @@ -105,7 +105,7 @@ describe('themes', () => { colourTertiary: '#0B8E69' }; - const updated = injectExtensionBlockTheme(blockInfoJson, DEFAULT_THEME); + const updated = injectExtensionBlockMode(blockInfoJson, DEFAULT_MODE); expect(updated).toEqual({ type: 'dummy_block', @@ -115,7 +115,7 @@ describe('themes', () => { }); }); - test('updates extension category based on theme', () => { + test('updates extension category based on color mode', () => { const dynamicBlockXML = [ { id: 'pen', @@ -123,7 +123,7 @@ describe('themes', () => { } ]; - injectExtensionCategoryTheme(dynamicBlockXML, DARK_THEME); + injectExtensionCategoryMode(dynamicBlockXML, DARK_MODE); // XMLSerializer is not available outside the browser. // Verify the mocked XMLSerializer.serializeToString is called with updated colors. @@ -133,35 +133,35 @@ describe('themes', () => { }); }); - describe('theme persistance', () => { - test('returns the theme stored in a cookie', () => { - window.document.cookie = `scratchtheme=${HIGH_CONTRAST_THEME}`; + describe('color mode persistence', () => { + test('returns the color mode stored in a cookie', () => { + window.document.cookie = `scratchtheme=${HIGH_CONTRAST_MODE}`; - const theme = detectTheme(); + const colorMode = detectColorMode(); - expect(theme).toEqual(HIGH_CONTRAST_THEME); + expect(colorMode).toEqual(HIGH_CONTRAST_MODE); }); - test('returns the system theme when no cookie', () => { + test('returns the system color mode when no cookie', () => { window.document.cookie = 'scratchtheme='; - const theme = detectTheme(); + const colorMode = detectColorMode(); - expect(theme).toEqual(DEFAULT_THEME); + expect(colorMode).toEqual(DEFAULT_MODE); }); - test('persists theme to cookie', () => { + test('persists color mode to cookie', () => { window.document.cookie = 'scratchtheme='; - persistTheme(HIGH_CONTRAST_THEME); + persistColorMode(HIGH_CONTRAST_MODE); - expect(window.document.cookie).toEqual(`scratchtheme=${HIGH_CONTRAST_THEME}`); + expect(window.document.cookie).toEqual(`scratchtheme=${HIGH_CONTRAST_MODE}`); }); - test('clears theme when matching system preferences', () => { - window.document.cookie = `scratchtheme=${HIGH_CONTRAST_THEME}`; + test('clears color mode when matching system preferences', () => { + window.document.cookie = `scratchtheme=${HIGH_CONTRAST_MODE}`; - persistTheme(DEFAULT_THEME); + persistColorMode(DEFAULT_MODE); expect(window.document.cookie).toEqual('scratchtheme='); }); diff --git a/packages/scratch-gui/webpack.config.js b/packages/scratch-gui/webpack.config.js index 34e2eda2e3..57168477a3 100644 --- a/packages/scratch-gui/webpack.config.js +++ b/packages/scratch-gui/webpack.config.js @@ -77,7 +77,7 @@ const baseConfig = new ScratchWebpackConfigBuilder( { // overwrite some of the default block media with high-contrast versions // this entry must come after copying scratch-blocks/media into the high-contrast directory - from: 'src/lib/themes/high-contrast/blocks-media', + from: 'src/lib/settings/color-mode/high-contrast/blocks-media', to: 'static/blocks-media/high-contrast', force: true }, diff --git a/packages/scratch-vm/package.json b/packages/scratch-vm/package.json index 4193990016..71209c4e5c 100644 --- a/packages/scratch-vm/package.json +++ b/packages/scratch-vm/package.json @@ -91,7 +91,7 @@ "js-md5": "0.7.3", "jsdoc": "3.6.11", "pngjs": "3.4.0", - "scratch-blocks": "1.2.5", + "scratch-blocks": "1.3.0", "scratch-l10n": "6.1.25", "scratch-render-fonts": "1.0.252", "scratch-semantic-release-config": "4.0.0",