diff --git a/.circleci/config.yml b/.circleci/config.yml
index f386894d3..74624b633 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -225,6 +225,7 @@ workflows:
ignore:
- master
- develop
+ - beta
- /^hotfix\/.*/
push-translations:
triggers:
@@ -254,6 +255,7 @@ workflows:
ignore:
- master
- develop
+ - beta
- /^hotfix\/.*/
build-test-deploy:
jobs:
@@ -265,6 +267,7 @@ workflows:
only:
- master
- develop
+ - beta
- /^hotfix\/.*/
- lint:
context:
@@ -296,6 +299,7 @@ workflows:
only:
- master
- develop
+ - beta
- /^hotfix\/.*/
- store_dist:
context:
@@ -307,6 +311,7 @@ workflows:
only:
- master
- develop
+ - beta
- /^hotfix\/.*/
- deploy-npm:
context:
@@ -322,6 +327,7 @@ workflows:
only:
- master
- develop
+ - beta
- /^hotfix\/.*/
- deploy-gh-pages:
context:
diff --git a/README.md b/README.md
index 4477a140c..77aeb5f09 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,12 @@ If you want to watch the browser as it runs the test, rather than running headle
USE_HEADLESS=no $(npm bin)/jest --runInBand test/integration/backpack.test.js
```
+_Note: If you are seeing failed tests related to `chromedriver` being incompatible with your version of Chrome, you may need to update `chromedriver` with:_
+
+```bash
+npm install chromedriver@{version}
+```
+
## Troubleshooting
### Ignoring optional dependencies
diff --git a/package-lock.json b/package-lock.json
index 038fa6586..836223c1e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,11 +16,11 @@
"cat-blocks": "npm:scratch-blocks@0.1.0-prerelease.20220318143026",
"classnames": "2.2.6",
"computed-style-to-inline-style": "3.0.0",
+ "cookie": "0.5.0",
"copy-webpack-plugin": "6.4.1",
"core-js": "2.5.7",
"css-loader": "^1.0.0",
"es6-object-assign": "1.1.0",
- "file-loader": "2.0.0",
"get-float-time-domain-data": "0.1.0",
"get-user-media-promise": "1.1.4",
"immutable": "3.8.2",
@@ -58,9 +58,9 @@
"redux": "3.7.2",
"redux-throttle": "0.1.1",
"scratch-audio": "0.1.0-prerelease.20221123180128",
- "scratch-blocks": "0.1.0-prerelease.20230507065123",
+ "scratch-blocks": "0.2.0-prerelease.20230509131059",
"scratch-l10n": "3.15.20230507032520",
- "scratch-paint": "1.1.54",
+ "scratch-paint": "1.2.0-beta.1",
"scratch-render": "0.1.0-prerelease.20230318150639",
"scratch-render-fonts": "1.0.0-prerelease.20221102164332",
"scratch-storage": "2.2.1",
@@ -95,6 +95,7 @@
"eslint-plugin-import": "2.23.4",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-react": "7.24.0",
+ "file-loader": "6.2.0",
"gh-pages": "3.2.3",
"html-webpack-plugin": "3.2.0",
"husky": "8.0.1",
@@ -109,6 +110,7 @@
"selenium-webdriver": "3.6.0",
"semantic-release": "19.0.5",
"uglifyjs-webpack-plugin": "1.3.0",
+ "url-loader": "4.1.1",
"web-audio-test-api": "0.5.2",
"webpack": "4.46.0",
"webpack-cli": "3.3.12",
@@ -4872,12 +4874,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/@commitlint/config-validator/node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
"node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -7389,10 +7385,9 @@
}
},
"node_modules/@types/json-schema": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
- "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
- "dev": true
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@@ -7813,14 +7808,18 @@
}
},
"node_modules/ajv": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
- "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dependencies": {
- "fast-deep-equal": "^2.0.1",
+ "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-errors": {
@@ -7832,9 +7831,9 @@
}
},
"node_modules/ajv-keywords": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
- "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"peerDependencies": {
"ajv": "^6.9.1"
}
@@ -8548,37 +8547,6 @@
"webpack": ">=2"
}
},
- "node_modules/babel-loader/node_modules/@types/json-schema": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
- "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
- "dev": true
- },
- "node_modules/babel-loader/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/babel-loader/node_modules/ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true,
- "peerDependencies": {
- "ajv": "^6.9.1"
- }
- },
"node_modules/babel-loader/node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@@ -8588,12 +8556,6 @@
"node": ">= 4"
}
},
- "node_modules/babel-loader/node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
"node_modules/babel-loader/node_modules/find-cache-dir": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
@@ -10257,15 +10219,6 @@
"node": ">= 0.6"
}
},
- "node_modules/compressible/node_modules/mime-db": {
- "version": "1.45.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
- "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
@@ -10503,10 +10456,9 @@
}
},
"node_modules/cookie": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
- "dev": true,
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
@@ -10578,34 +10530,6 @@
"webpack": "^4.37.0 || ^5.0.0"
}
},
- "node_modules/copy-webpack-plugin/node_modules/@types/json-schema": {
- "version": "7.0.9",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
- "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ=="
- },
- "node_modules/copy-webpack-plugin/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "peerDependencies": {
- "ajv": "^6.9.1"
- }
- },
"node_modules/copy-webpack-plugin/node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -10669,11 +10593,6 @@
"node": ">= 4"
}
},
- "node_modules/copy-webpack-plugin/node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- },
"node_modules/copy-webpack-plugin/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -13641,6 +13560,15 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
"dev": true
},
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/express/node_modules/qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -13895,9 +13823,9 @@
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
},
"node_modules/fast-deep-equal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
- "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.2.6",
@@ -14096,18 +14024,64 @@
}
},
"node_modules/file-loader": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz",
- "integrity": "sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
"dependencies": {
- "loader-utils": "^1.0.2",
- "schema-utils": "^1.0.0"
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
},
"engines": {
- "node": ">= 6.9.0 < 7.0.0 || >= 8.9.0"
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0"
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/file-loader/node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/file-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/file-loader/node_modules/schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
"node_modules/file-type": {
@@ -19955,19 +19929,19 @@
}
},
"node_modules/mime-db": {
- "version": "1.40.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
- "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
- "version": "2.1.24",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
- "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
- "mime-db": "1.40.0"
+ "mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
@@ -25902,9 +25876,9 @@
}
},
"node_modules/scratch-blocks": {
- "version": "0.1.0-prerelease.20230507065123",
- "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20230507065123.tgz",
- "integrity": "sha512-ESFkBv3EyyjXQbfgnbOXMCkLtlTzR1K+mPUANobSWim/evedSowx3l6uuqGVumEbo/9K2ullKyXQo7LDZDrvOw==",
+ "version": "0.2.0-prerelease.20230509131059",
+ "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.2.0-prerelease.20230509131059.tgz",
+ "integrity": "sha512-TPrMjmXhOh3AwlLJu3ggkMUwfHsUN8Y0tk7NnrLM02R0qtr32pgOHttBobXDya7R4Pu+HyfGSuxwtuJftb0MsQ==",
"dependencies": {
"exports-loader": "0.7.0",
"google-closure-library": "20190301.0.0",
@@ -25930,9 +25904,9 @@
}
},
"node_modules/scratch-paint": {
- "version": "1.1.54",
- "resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-1.1.54.tgz",
- "integrity": "sha512-23fylk1yn74TEpthDX1wGpnVGLRDNuQALhTD+Hrq5b0yfWDW1U+Ane6MuA6zOR2noFRcby357rvCdKNXU9p6zQ==",
+ "version": "1.2.0-beta.1",
+ "resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-1.2.0-beta.1.tgz",
+ "integrity": "sha512-YyVBRv5+ahJA02N0KKjalHUyJXKZOvQmpq658gswePsyU/GkcHByJCRQjd0iYWN8nJZlDEZrOExLuh+noTHjuA==",
"dependencies": {
"@scratch/paper": "0.11.20200728195508",
"classnames": "2.2.5",
@@ -29898,6 +29872,74 @@
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
+ "node_modules/url-loader": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
+ "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "mime-types": "^2.1.27",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "file-loader": "*",
+ "webpack": "^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "file-loader": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/url-loader/node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/url-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/url-loader/node_modules/schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/url-parse": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
@@ -34909,12 +34951,6 @@
"uri-js": "^4.2.2"
}
},
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -36784,10 +36820,9 @@
}
},
"@types/json-schema": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
- "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
- "dev": true
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"@types/json5": {
"version": "0.0.29",
@@ -37162,11 +37197,11 @@
}
},
"ajv": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
- "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
- "fast-deep-equal": "^2.0.1",
+ "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
@@ -37179,9 +37214,9 @@
"requires": {}
},
"ajv-keywords": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
- "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"requires": {}
},
"amdefine": {
@@ -37736,43 +37771,12 @@
"schema-utils": "^2.6.5"
},
"dependencies": {
- "@types/json-schema": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
- "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
- "dev": true
- },
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true,
- "requires": {}
- },
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
"find-cache-dir": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
@@ -39183,14 +39187,6 @@
"dev": true,
"requires": {
"mime-db": ">= 1.43.0 < 2"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.45.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
- "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
- "dev": true
- }
}
},
"compression": {
@@ -39389,10 +39385,9 @@
}
},
"cookie": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
- "dev": true
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
},
"cookie-signature": {
"version": "1.0.6",
@@ -39446,28 +39441,6 @@
"webpack-sources": "^1.4.3"
},
"dependencies": {
- "@types/json-schema": {
- "version": "7.0.9",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
- "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ=="
- },
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "requires": {}
- },
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -39516,11 +39489,6 @@
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
},
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -41888,6 +41856,12 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
"dev": true
},
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+ "dev": true
+ },
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -42082,9 +42056,9 @@
}
},
"fast-deep-equal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
- "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-glob": {
"version": "3.2.6",
@@ -42250,12 +42224,43 @@
}
},
"file-loader": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz",
- "integrity": "sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
"requires": {
- "loader-utils": "^1.0.2",
- "schema-utils": "^1.0.0"
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "dependencies": {
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ }
}
},
"file-type": {
@@ -46936,16 +46941,16 @@
"dev": true
},
"mime-db": {
- "version": "1.40.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
- "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
- "version": "2.1.24",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
- "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
- "mime-db": "1.40.0"
+ "mime-db": "1.52.0"
}
},
"mimic-fn": {
@@ -51459,9 +51464,9 @@
}
},
"scratch-blocks": {
- "version": "0.1.0-prerelease.20230507065123",
- "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20230507065123.tgz",
- "integrity": "sha512-ESFkBv3EyyjXQbfgnbOXMCkLtlTzR1K+mPUANobSWim/evedSowx3l6uuqGVumEbo/9K2ullKyXQo7LDZDrvOw==",
+ "version": "0.2.0-prerelease.20230509131059",
+ "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.2.0-prerelease.20230509131059.tgz",
+ "integrity": "sha512-TPrMjmXhOh3AwlLJu3ggkMUwfHsUN8Y0tk7NnrLM02R0qtr32pgOHttBobXDya7R4Pu+HyfGSuxwtuJftb0MsQ==",
"requires": {
"exports-loader": "0.7.0",
"google-closure-library": "20190301.0.0",
@@ -51483,9 +51488,9 @@
}
},
"scratch-paint": {
- "version": "1.1.54",
- "resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-1.1.54.tgz",
- "integrity": "sha512-23fylk1yn74TEpthDX1wGpnVGLRDNuQALhTD+Hrq5b0yfWDW1U+Ane6MuA6zOR2noFRcby357rvCdKNXU9p6zQ==",
+ "version": "1.2.0-beta.1",
+ "resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-1.2.0-beta.1.tgz",
+ "integrity": "sha512-YyVBRv5+ahJA02N0KKjalHUyJXKZOvQmpq658gswePsyU/GkcHByJCRQjd0iYWN8nJZlDEZrOExLuh+noTHjuA==",
"requires": {
"@scratch/paper": "0.11.20200728195508",
"classnames": "2.2.5",
@@ -54639,6 +54644,47 @@
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
+ "url-loader": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
+ "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "mime-types": "^2.1.27",
+ "schema-utils": "^3.0.0"
+ },
+ "dependencies": {
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ }
+ }
+ },
"url-parse": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
diff --git a/package.json b/package.json
index a263fb453..1d815fbe0 100644
--- a/package.json
+++ b/package.json
@@ -39,11 +39,11 @@
"cat-blocks": "npm:scratch-blocks@0.1.0-prerelease.20220318143026",
"classnames": "2.2.6",
"computed-style-to-inline-style": "3.0.0",
+ "cookie": "0.5.0",
"copy-webpack-plugin": "6.4.1",
"core-js": "2.5.7",
"css-loader": "^1.0.0",
"es6-object-assign": "1.1.0",
- "file-loader": "2.0.0",
"get-float-time-domain-data": "0.1.0",
"get-user-media-promise": "1.1.4",
"immutable": "3.8.2",
@@ -81,9 +81,9 @@
"redux": "3.7.2",
"redux-throttle": "0.1.1",
"scratch-audio": "0.1.0-prerelease.20221123180128",
- "scratch-blocks": "0.1.0-prerelease.20230507065123",
+ "scratch-blocks": "0.2.0-prerelease.20230509131059",
"scratch-l10n": "3.15.20230507032520",
- "scratch-paint": "1.1.54",
+ "scratch-paint": "1.2.0-beta.1",
"scratch-render": "0.1.0-prerelease.20230318150639",
"scratch-render-fonts": "1.0.0-prerelease.20221102164332",
"scratch-storage": "2.2.1",
@@ -122,6 +122,7 @@
"eslint-plugin-import": "2.23.4",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-react": "7.24.0",
+ "file-loader": "6.2.0",
"gh-pages": "3.2.3",
"html-webpack-plugin": "3.2.0",
"husky": "8.0.1",
@@ -136,6 +137,7 @@
"selenium-webdriver": "3.6.0",
"semantic-release": "19.0.5",
"uglifyjs-webpack-plugin": "1.3.0",
+ "url-loader": "4.1.1",
"web-audio-test-api": "0.5.2",
"webpack": "4.46.0",
"webpack-cli": "3.3.12",
diff --git a/release.config.js b/release.config.js
index d4156eb15..416c2f5a1 100644
--- a/release.config.js
+++ b/release.config.js
@@ -8,6 +8,11 @@ module.exports = {
{
name: 'hotfix/REPLACE', // replace with actual hotfix branch name
channel: 'hotfix'
+ },
+ {
+ name: 'beta',
+ channel: 'beta',
+ prerelease: true
}
]
};
diff --git a/src/components/action-menu/action-menu.css b/src/components/action-menu/action-menu.css
index 14dbb5df0..03be98cac 100644
--- a/src/components/action-menu/action-menu.css
+++ b/src/components/action-menu/action-menu.css
@@ -19,7 +19,7 @@ $more-button-size: 2.25rem;
justify-content: center;
cursor: pointer;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- background: $motion-primary;
+ background: $looks-secondary;
outline: none;
border: none;
transition: background-color 0.2s;
@@ -45,14 +45,14 @@ button::-moz-focus-inner {
border-radius: 100%;
width: $main-button-size;
height: $main-button-size;
- box-shadow: 0 0 0 4px $motion-transparent;
+ box-shadow: 0 0 0 4px $looks-transparent;
z-index: $z-index-add-button;
transition: transform, box-shadow 0.5s;
}
.main-button:hover {
transform: scale(1.1);
- box-shadow: 0 0 0 6px $motion-transparent;
+ box-shadow: 0 0 0 6px $looks-transparent;
}
.main-icon {
@@ -72,7 +72,7 @@ button::-moz-focus-inner {
*/
overflow-y: hidden;
- background: $motion-tertiary;
+ background: $looks-secondary-dark;
border-top-left-radius: $more-button-size;
border-top-right-radius: $more-button-size;
width: $more-button-size;
@@ -115,7 +115,7 @@ button::-moz-focus-inner {
.more-button {
width: $more-button-size;
height: $more-button-size;
- background: $motion-tertiary;
+ background: $looks-secondary-dark;
}
.more-icon {
diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css
index 6ff5510da..712bdf342 100644
--- a/src/components/asset-panel/selector.css
+++ b/src/components/asset-panel/selector.css
@@ -20,7 +20,7 @@
align-items: center;
justify-content: space-around;
padding: 0.75rem 0;
- color: $motion-primary;
+ color: $looks-secondary;
text-align: center;
background: none;
}
diff --git a/src/components/audio-trimmer/audio-trimmer.css b/src/components/audio-trimmer/audio-trimmer.css
index f3ce9fa42..9583558df 100644
--- a/src/components/audio-trimmer/audio-trimmer.css
+++ b/src/components/audio-trimmer/audio-trimmer.css
@@ -41,7 +41,7 @@ $hover-scale: 1.25;
}
.selection-background {
- background: $motion-primary;
+ background: $looks-secondary;
opacity: 0.5;
}
@@ -64,7 +64,7 @@ $hover-scale: 1.25;
}
.selector .trim-line {
- border: 1px solid $motion-tertiary;
+ border: 1px solid $looks-secondary;
}
.playhead-container {
@@ -83,7 +83,7 @@ $hover-scale: 1.25;
*/
width: 100%;
height: 100%;
- border-left: 1px solid $motion-primary;
+ border-left: 1px solid $looks-secondary;
border-top: none;
border-bottom: none;
border-right: none;
@@ -118,7 +118,7 @@ $hover-scale: 1.25;
}
.trimmer .trim-handle {
- filter: hue-rotate(150deg);
+ filter: hue-rotate(-240deg) brightness(1.35);
}
.trim-handle img {
diff --git a/src/components/audio-trimmer/icon--handle.svg b/src/components/audio-trimmer/icon--handle.svg
index dad1ea751..69eb54a7e 100644
--- a/src/components/audio-trimmer/icon--handle.svg
+++ b/src/components/audio-trimmer/icon--handle.svg
@@ -8,9 +8,9 @@
-
+
-
+
diff --git a/src/components/backpack/backpack.css b/src/components/backpack/backpack.css
index 25bc42d45..98a35a5e8 100644
--- a/src/components/backpack/backpack.css
+++ b/src/components/backpack/backpack.css
@@ -88,7 +88,7 @@
}
.more {
- background: $motion-primary;
+ background: $looks-secondary;
color: $ui-white;
border: none;
outline: none;
diff --git a/src/components/browser-modal/browser-modal.css b/src/components/browser-modal/browser-modal.css
index 2f4145bf6..5fa57bbec 100644
--- a/src/components/browser-modal/browser-modal.css
+++ b/src/components/browser-modal/browser-modal.css
@@ -60,10 +60,10 @@
}
.button-row button {
- border: 1px solid $motion-primary;
+ border: 1px solid $looks-secondary;
border-radius: 0.25rem;
padding: 0.5rem 2rem;
- background: $motion-primary;
+ background: $looks-secondary;
color: white;
font-weight: bold;
font-size: 0.875rem;
@@ -77,6 +77,6 @@
}
.faq-link {
- color: $motion-primary;
+ color: $looks-secondary;
text-decoration: none;
}
diff --git a/src/components/cards/card.css b/src/components/cards/card.css
index ad712e339..523323fff 100644
--- a/src/components/cards/card.css
+++ b/src/components/cards/card.css
@@ -214,7 +214,7 @@
}
.deck-name {
- color: $motion-primary;
+ color: $looks-secondary;
font-weight: bold;
font-size: 0.85rem;
margin: .625rem 0px;
@@ -260,7 +260,7 @@
.see-all-button {
cursor: pointer;
padding: 0.5rem 1rem;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
color: white;
font-weight: bold;
border-radius: 0.25rem;
diff --git a/src/components/close-button/close-button.css b/src/components/close-button/close-button.css
index 48a18f88d..2a023e290 100644
--- a/src/components/close-button/close-button.css
+++ b/src/components/close-button/close-button.css
@@ -28,7 +28,7 @@
.small {
width: 0.825rem;
height: 0.825rem;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
color: $ui-white;
}
diff --git a/src/components/connection-modal/connection-modal.css b/src/components/connection-modal/connection-modal.css
index fde45e015..a97c4355d 100644
--- a/src/components/connection-modal/connection-modal.css
+++ b/src/components/connection-modal/connection-modal.css
@@ -80,8 +80,8 @@
border-radius: 0.25rem;
font-weight: 600;
font-size: 0.85rem;
- background: $motion-primary;
- border: $motion-primary;
+ background: $looks-secondary;
+ border: $looks-secondary;
color: white;
cursor: pointer;
}
@@ -172,9 +172,9 @@
right: -15px;
left: -15px;
padding: 5px 5px;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
border-radius: 100%;
- box-shadow: 0px 0px 0px 4px $motion-transparent;
+ box-shadow: 0px 0px 0px 4px $looks-transparent;
/* animation: pulse-blue-ring 1s infinite ease-in-out alternate; */
animation: wiggle 0.5s infinite ease-in-out alternate;
@@ -182,7 +182,7 @@
@keyframes pulse-blue-ring {
100% {
- box-shadow: 0px 0px 0px 8px $motion-light-transparent;
+ box-shadow: 0px 0px 0px 8px $looks-light-transparent;
}
}
@@ -208,9 +208,9 @@
.bluetooth-centered-icon {
position: absolute;
padding: 5px 5px;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
border-radius: 100%;
- box-shadow: 0px 0px 0px 2px $motion-transparent;
+ box-shadow: 0px 0px 0px 2px $looks-transparent;
}
.peripheral-tile-widgets {
@@ -220,7 +220,7 @@
.activityArea {
height: 165px;
- background-color: $motion-light-transparent;
+ background-color: $looks-light-transparent;
display: flex;
justify-content: center;
align-items: center;
@@ -297,7 +297,7 @@
.connection-button {
padding: 0.6rem 0.75rem;
border-radius: 0.5rem;
- background: $motion-primary;
+ background: $looks-secondary;
color: white;
font-weight: 600;
font-size: 0.85rem;
@@ -309,7 +309,7 @@
}
.connection-button:disabled {
- background: $motion-transparent;
+ background: $looks-transparent;
}
.segmented-button {
@@ -390,7 +390,7 @@
display: flex;
padding: 0.25rem 0.1rem;
border-radius: 1rem;
- background: $motion-light-transparent;
+ background: $looks-light-transparent;
}
.dots-holder-success {
@@ -409,11 +409,11 @@
}
.inactive-step-dot {
- background: $motion-transparent;
+ background: $looks-transparent;
}
.active-step-dot {
- background: $motion-primary;
+ background: $looks-secondary;
}
.success-dot {
diff --git a/src/components/context-menu/context-menu.css b/src/components/context-menu/context-menu.css
index 7f21c92f7..5548e066b 100644
--- a/src/components/context-menu/context-menu.css
+++ b/src/components/context-menu/context-menu.css
@@ -25,7 +25,7 @@
}
.menu-item:hover {
- background: $motion-primary;
+ background: $looks-secondary;
color: white;
}
diff --git a/src/components/crash-message/crash-message.css b/src/components/crash-message/crash-message.css
index 605f7fdab..daac90c53 100644
--- a/src/components/crash-message/crash-message.css
+++ b/src/components/crash-message/crash-message.css
@@ -10,12 +10,18 @@
justify-content: center;
align-items: center;
}
+
.body {
width: 35%;
color: white;
text-align: center;
}
+/* scratch-www was overriding the colors */
+.body p, .body h2 {
+ color: inherit;
+}
+
.reloadButton {
border: 1px solid $motion-primary;
border-radius: 0.25rem;
diff --git a/src/components/custom-procedures/custom-procedures.css b/src/components/custom-procedures/custom-procedures.css
index 96cc55d15..d54fd7d82 100644
--- a/src/components/custom-procedures/custom-procedures.css
+++ b/src/components/custom-procedures/custom-procedures.css
@@ -53,8 +53,8 @@
}
.option-card:hover {
- border: 2px solid $motion-primary;
- box-shadow: 0px 0px 0px 4px $motion-transparent;
+ border: 2px solid $looks-secondary;
+ box-shadow: 0px 0px 0px 4px $looks-transparent;
}
.option-card + .option-card {
@@ -92,8 +92,8 @@
}
.button-row button.ok-button {
- background: $motion-primary;
- border: $motion-primary;
+ background: $looks-secondary;
+ border: $looks-secondary;
color: white;
}
diff --git a/src/components/delete-button/delete-button.css b/src/components/delete-button/delete-button.css
index 6abca7ec0..692a9d352 100644
--- a/src/components/delete-button/delete-button.css
+++ b/src/components/delete-button/delete-button.css
@@ -18,8 +18,8 @@
overflow: hidden; /* Mask the icon animation */
width: 1.75rem;
height: 1.75rem;
- box-shadow: 0px 0px 0px 2px $motion-transparent;
- background-color: $motion-primary;
+ box-shadow: 0px 0px 0px 2px $looks-transparent;
+ background-color: $looks-secondary;
color: $ui-white;
border-radius: 50%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/src/components/direction-picker/dial.css b/src/components/direction-picker/dial.css
index ce9b7daea..43ab7c7d0 100644
--- a/src/components/direction-picker/dial.css
+++ b/src/components/direction-picker/dial.css
@@ -35,7 +35,7 @@ $dial-size: 40px;
}
.gauge-path {
- fill: $motion-transparent;
- stroke: $motion-primary;
+ fill: $looks-transparent;
+ stroke: $looks-secondary;
stroke-width: 1px;
}
diff --git a/src/components/direction-picker/direction-picker.css b/src/components/direction-picker/direction-picker.css
index 501ff0217..e39721317 100644
--- a/src/components/direction-picker/direction-picker.css
+++ b/src/components/direction-picker/direction-picker.css
@@ -1,32 +1,4 @@
-@import "../../css/colors.css";
-
.button-row {
- display: flex;
- flex-direction: row;
justify-content: center;
-
-}
-
-.icon-button {
- margin: 0.25rem;
- border: none;
- background: none;
- outline: none;
- cursor: pointer;
- user-select: none;
-}
-
-.icon-button:active > img {
- width: 20px;
- height: 20px;
- transform: scale(1.15);
-}
-
-.icon-button > img {
- transition: transform 0.1s;
- filter: grayscale(100%);
-}
-
-.icon-button.active > img {
- filter: none;
+ margin-bottom: 1rem;
}
diff --git a/src/components/direction-picker/direction-picker.jsx b/src/components/direction-picker/direction-picker.jsx
index 84a15db7a..3f06f3dc4 100644
--- a/src/components/direction-picker/direction-picker.jsx
+++ b/src/components/direction-picker/direction-picker.jsx
@@ -1,12 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
-import classNames from 'classnames';
import Popover from 'react-popover';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
import Label from '../forms/label.jsx';
import Input from '../forms/input.jsx';
import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
+import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';
import Dial from './dial.jsx';
import styles from './direction-picker.css';
@@ -62,44 +62,29 @@ const DirectionPicker = props => (
direction={props.direction}
onChange={props.onChangeDirection}
/>
-
+
}
isOpen={props.popoverOpen}
diff --git a/src/components/direction-picker/icon--all-around.svg b/src/components/direction-picker/icon--all-around.svg
index 2412c0b23..8ace45b7c 100644
--- a/src/components/direction-picker/icon--all-around.svg
+++ b/src/components/direction-picker/icon--all-around.svg
@@ -5,6 +5,6 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/direction-picker/icon--dial.svg b/src/components/direction-picker/icon--dial.svg
index d4aa8ed82..e2109dfb6 100644
--- a/src/components/direction-picker/icon--dial.svg
+++ b/src/components/direction-picker/icon--dial.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/components/direction-picker/icon--dont-rotate.svg b/src/components/direction-picker/icon--dont-rotate.svg
index 4796c03af..222054e82 100644
--- a/src/components/direction-picker/icon--dont-rotate.svg
+++ b/src/components/direction-picker/icon--dont-rotate.svg
@@ -5,6 +5,6 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/direction-picker/icon--handle.svg b/src/components/direction-picker/icon--handle.svg
index 8e5fee6e0..ea901a7a4 100644
--- a/src/components/direction-picker/icon--handle.svg
+++ b/src/components/direction-picker/icon--handle.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/components/direction-picker/icon--left-right.svg b/src/components/direction-picker/icon--left-right.svg
index 4525bd6e9..8565562d8 100644
--- a/src/components/direction-picker/icon--left-right.svg
+++ b/src/components/direction-picker/icon--left-right.svg
@@ -4,11 +4,9 @@
left-right-active
Created with Sketch.
-
-
-
-
-
-
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/components/drag-layer/drag-layer.css b/src/components/drag-layer/drag-layer.css
index 926c0ee9f..cb8885374 100644
--- a/src/components/drag-layer/drag-layer.css
+++ b/src/components/drag-layer/drag-layer.css
@@ -29,7 +29,7 @@
margin-top: -50%;
padding: 0.25rem;
- border: 2px solid $motion-primary;
+ border: 2px solid $looks-secondary;
background: $ui-white;
border-radius: 0.5rem;
diff --git a/src/components/filter/filter.css b/src/components/filter/filter.css
index fffef4812..ad8ed36f4 100644
--- a/src/components/filter/filter.css
+++ b/src/components/filter/filter.css
@@ -35,7 +35,7 @@
}
.filter:focus-within {
- box-shadow: 0 0 0 .25rem $motion-transparent;
+ box-shadow: 0 0 0 .25rem $looks-transparent;
}
/*
diff --git a/src/components/filter/icon--filter.svg b/src/components/filter/icon--filter.svg
index 400bd6234..c60cc9172 100644
--- a/src/components/filter/icon--filter.svg
+++ b/src/components/filter/icon--filter.svg
@@ -1,3 +1,3 @@
diff --git a/src/components/forms/input.css b/src/components/forms/input.css
index ee1010dbc..ce0ab17b7 100644
--- a/src/components/forms/input.css
+++ b/src/components/forms/input.css
@@ -32,12 +32,12 @@
}
.input-form:hover {
- border-color: $motion-primary;
+ border-color: $looks-secondary;
}
.input-form:focus {
- border-color: $motion-primary;
- box-shadow: 0 0 0 0.25rem $motion-transparent;
+ border-color: $looks-secondary;
+ box-shadow: 0 0 0 0.25rem $looks-transparent;
}
.input-small {
diff --git a/src/components/green-flag/green-flag.css b/src/components/green-flag/green-flag.css
index aa4eb536d..3ca52f61e 100644
--- a/src/components/green-flag/green-flag.css
+++ b/src/components/green-flag/green-flag.css
@@ -11,9 +11,9 @@
}
.green-flag:hover {
- background-color: $motion-light-transparent;
+ background-color: $looks-light-transparent;
}
.green-flag.is-active {
- background-color: $motion-transparent;
+ background-color: $looks-transparent;
}
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index eeb3989a1..2002e5bb2 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -124,7 +124,7 @@
.tab.is-selected {
height: 90%;
- color: $motion-primary;
+ color: $looks-secondary;
background-color: $ui-white;
z-index: 4; /* Make sure selected is always above */
}
@@ -236,9 +236,9 @@
left: 0;
right: 0;
z-index: $z-index-extension-button;
- background: $motion-primary;
+ background: $looks-secondary;
- border: 1px solid $motion-primary;
+ border: 1px solid $looks-secondary;
box-sizing: content-box; /* To match scratch-block vertical toolbox borders */
}
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 477a91213..f48e5a6ec 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -33,6 +33,7 @@ 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 styles from './gui.css';
import addExtensionIcon from './icon--extensions.svg';
@@ -68,6 +69,7 @@ const GUIComponent = props => {
blocksTabVisible,
cardsVisible,
canChangeLanguage,
+ canChangeTheme,
canCreateNew,
canEditTitle,
canManageFiles,
@@ -119,6 +121,7 @@ const GUIComponent = props => {
stageSizeMode,
targetIsStage,
telemetryModalVisible,
+ theme,
tipsLibraryVisible,
vm,
...componentProps
@@ -214,6 +217,7 @@ const GUIComponent = props => {
authorThumbnailUrl={authorThumbnailUrl}
authorUsername={authorUsername}
canChangeLanguage={canChangeLanguage}
+ canChangeTheme={canChangeTheme}
canCreateCopy={canCreateCopy}
canCreateNew={canCreateNew}
canEditTitle={canEditTitle}
@@ -303,14 +307,15 @@ const GUIComponent = props => {
@@ -379,6 +384,7 @@ GUIComponent.propTypes = {
blocksTabVisible: PropTypes.bool,
blocksId: PropTypes.string,
canChangeLanguage: PropTypes.bool,
+ canChangeTheme: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canEditTitle: PropTypes.bool,
@@ -428,6 +434,7 @@ GUIComponent.propTypes = {
stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)),
targetIsStage: PropTypes.bool,
telemetryModalVisible: PropTypes.bool,
+ theme: PropTypes.string,
tipsLibraryVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
@@ -437,6 +444,7 @@ GUIComponent.defaultProps = {
basePath: './',
blocksId: 'original',
canChangeLanguage: true,
+ canChangeTheme: true,
canCreateNew: false,
canEditTitle: false,
canManageFiles: true,
@@ -456,8 +464,9 @@ GUIComponent.defaultProps = {
const mapStateToProps = state => ({
// This is the button's mode, as opposed to the actual current state
+ blocksId: state.scratchGui.timeTravel.year.toString(),
stageSizeMode: state.scratchGui.stageSize.stageSize,
- blocksId: state.scratchGui.timeTravel.year.toString()
+ theme: state.scratchGui.theme.theme
});
export default injectIntl(connect(
diff --git a/src/components/gui/icon--code.svg b/src/components/gui/icon--code.svg
index 390e683b1..c06998055 100644
--- a/src/components/gui/icon--code.svg
+++ b/src/components/gui/icon--code.svg
@@ -5,6 +5,6 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/gui/icon--costumes.svg b/src/components/gui/icon--costumes.svg
index de50ce39b..5111126f0 100644
--- a/src/components/gui/icon--costumes.svg
+++ b/src/components/gui/icon--costumes.svg
@@ -5,6 +5,6 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/gui/icon--sounds.svg b/src/components/gui/icon--sounds.svg
index 0de67c537..d05c06309 100644
--- a/src/components/gui/icon--sounds.svg
+++ b/src/components/gui/icon--sounds.svg
@@ -5,6 +5,6 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/icon-button/icon-button.css b/src/components/icon-button/icon-button.css
index 002c817ae..5e6b4845e 100644
--- a/src/components/icon-button/icon-button.css
+++ b/src/components/icon-button/icon-button.css
@@ -7,7 +7,7 @@
cursor: pointer;
font-size: 0.75rem;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- color: $motion-primary;
+ color: $looks-secondary;
border-radius: 0.5rem;
}
@@ -26,5 +26,5 @@
}
.container:active {
- background-color: $motion-light-transparent;
+ background-color: $looks-light-transparent;
}
diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css
index 013e340dd..d5fb68618 100644
--- a/src/components/language-selector/language-selector.css
+++ b/src/components/language-selector/language-selector.css
@@ -4,7 +4,7 @@
/* Position the language select over the language icon, and make it transparent */
.language-select {
position: absolute;
- width: $language-selector-width;
+ width: 100%;
height: $menu-bar-height;
opacity: 0;
user-select: none;
diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css
index d4edd136d..1a7d15933 100644
--- a/src/components/library-item/library-item.css
+++ b/src/components/library-item/library-item.css
@@ -29,7 +29,7 @@
.library-item:hover {
border-width: 2px;
- border-color: $motion-primary;
+ border-color: $looks-secondary;
}
.hidden {
diff --git a/src/components/library/library.css b/src/components/library/library.css
index 51287daa7..df13ec5f7 100644
--- a/src/components/library/library.css
+++ b/src/components/library/library.css
@@ -25,7 +25,7 @@
justify-content: flex-start;
align-items: center;
height: calc($library-filter-bar-height + 2rem); /* padding */
- background-color: $motion-transparent;
+ background-color: $looks-transparent;
padding: 0 1rem;
font-size: .875rem;
}
diff --git a/src/components/menu-bar/check.svg b/src/components/menu-bar/check.svg
new file mode 100644
index 000000000..afe11c097
--- /dev/null
+++ b/src/components/menu-bar/check.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css
index f2b401385..d35070da0 100644
--- a/src/components/menu-bar/menu-bar.css
+++ b/src/components/menu-bar/menu-bar.css
@@ -27,7 +27,7 @@
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: $menu-bar-standard-font-size;
font-weight: bold;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
color: $ui-white;
}
@@ -54,16 +54,28 @@
.language-icon {
height: 1.5rem;
vertical-align: middle;
+ margin: 0 .125rem 0 0;
}
-.language-caret {
- margin: 0 .125rem;
+[dir="rtl"] .language-icon {
+ margin: 0 0 0 .125rem;
}
.language-menu {
box-sizing: border-box;
display: inline-flex;
- width: $language-selector-width;
+}
+
+.language-label {
+ max-width: 7rem;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ margin: 0 .5rem 0 .25rem;
+}
+
+[dir="rtl"] .language-label {
+ margin: 0 .25rem 0 .5rem;
}
.menu-bar-item {
@@ -105,20 +117,17 @@
}
.file-group .menu-bar-item {
+ margin: 0 .25rem;
padding: 0 0.75rem;
}
-.menu-bar-item.language-menu {
- padding: 0 0.5rem;
-}
-
.menu-bar-menu {
margin-top: $menu-bar-height;
z-index: $z-index-menu-bar;
}
.feedback-link {
- color: $motion-primary;
+ color: $looks-secondary;
text-decoration: none;
}
@@ -160,6 +169,7 @@
}
.account-info-group .menu-bar-item {
+ margin: 0 .25rem;
padding: 0 0.75rem;
}
@@ -169,7 +179,11 @@
}
.help-icon {
- margin-right: 0.35rem;
+ margin: 0 .25rem 0 0;
+}
+
+[dir="rtl"] .help-icon {
+ margin: 0 0 0 .25rem;
}
.account-nav-menu, .mystuff-button {
@@ -226,3 +240,13 @@
margin: 0.5rem;
vertical-align: middle;
}
+
+@media only screen and (max-width: 1024px) {
+ .language-label, .tutorials-label {
+ display: none;
+ }
+
+ .help-icon {
+ margin-right: 0;
+ }
+}
diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx
index 5aba0ffaf..5ed80abee 100644
--- a/src/components/menu-bar/menu-bar.jsx
+++ b/src/components/menu-bar/menu-bar.jsx
@@ -8,6 +8,7 @@ import bowser from 'bowser';
import React from 'react';
import VM from 'scratch-vm';
+import locales from 'scratch-l10n';
import Box from '../box/box.jsx';
import Button from '../button/button.jsx';
@@ -28,6 +29,7 @@ import SB3Downloader from '../../containers/sb3-downloader.jsx';
import DeletionRestorer from '../../containers/deletion-restorer.jsx';
import TurboMode from '../../containers/turbo-mode.jsx';
import MenuBarHOC from '../../containers/menu-bar-hoc.jsx';
+import ThemeMenu from './theme-menu.jsx';
import {openTipsLibrary} from '../../reducers/modals';
import {setPlayer} from '../../reducers/mode';
@@ -67,10 +69,14 @@ import {
openLoginMenu,
closeLoginMenu,
loginMenuOpen,
+ openThemeMenu,
+ closeThemeMenu,
+ themeMenuOpen,
openModeMenu,
closeModeMenu,
modeMenuOpen
} from '../../reducers/menus';
+import {setTheme} from '../../reducers/theme.js';
import collectMetadata from '../../lib/collect-metadata';
@@ -91,6 +97,7 @@ import prehistoricLogo from './prehistoric-logo.svg';
import oldtimeyLogo from './oldtimey-logo.svg';
import sharedMessages from '../../lib/shared-messages';
+import {persistTheme} from '../../lib/themes/themePersistance';
const ariaMessages = defineMessages({
language: {
@@ -176,6 +183,7 @@ class MenuBar extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
+ 'handleChangeTheme',
'handleClickNew',
'handleClickRemix',
'handleClickSave',
@@ -302,6 +310,11 @@ class MenuBar extends React.Component {
this.props.onClickLanguage(e);
}
}
+ handleChangeTheme (theme) {
+ this.props.onChangeTheme(theme);
+ this.props.onRequestCloseTheme();
+ persistTheme(theme);
+ }
restoreOptionMessage (deletedItem) {
switch (deletedItem) {
case 'Sprite':
@@ -449,18 +462,26 @@ class MenuBar extends React.Component {
{(this.props.canChangeLanguage) && (
-
-

-

-
+

+
{locales[this.props.currentLocale].name}
+
)}
+ {(this.props.canChangeTheme) && ()}
+
{(this.props.canManageFiles) && (
)}
-
-
-

-
-
-
{this.props.canEditTitle ? (
) : [])}
+
+
+
+

+
+
+
+
+
{/* show the proper UI in the account menu, given whether the user is
@@ -856,6 +880,7 @@ MenuBar.propTypes = {
authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
autoUpdateProject: PropTypes.func,
canChangeLanguage: PropTypes.bool,
+ canChangeTheme: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canEditTitle: PropTypes.bool,
@@ -865,6 +890,7 @@ MenuBar.propTypes = {
canShare: PropTypes.bool,
className: PropTypes.string,
confirmReadyToReplaceProject: PropTypes.func,
+ currentLocale: PropTypes.string.isRequired,
editMenuOpen: PropTypes.bool,
enableCommunity: PropTypes.bool,
fileMenuOpen: PropTypes.bool,
@@ -878,13 +904,13 @@ MenuBar.propTypes = {
locale: PropTypes.string.isRequired,
loginMenuOpen: PropTypes.bool,
logo: PropTypes.string,
+ onChangeTheme: PropTypes.func,
modeMenuOpen: PropTypes.bool,
modeNow: PropTypes.bool,
mode220022BC: PropTypes.bool,
mode1920: PropTypes.bool,
mode1990: PropTypes.bool,
mode2020: PropTypes.bool,
-
onClickAbout: PropTypes.oneOfType([
PropTypes.func, // button mode: call this callback when the About button is clicked
PropTypes.arrayOf( // menu mode: list of items in the About menu
@@ -917,6 +943,8 @@ MenuBar.propTypes = {
onRequestCloseFile: PropTypes.func,
onRequestCloseLanguage: PropTypes.func,
onRequestCloseLogin: PropTypes.func,
+ onRequestCloseTheme: PropTypes.func,
+ onRequestOpenTheme: PropTypes.func,
onRequestCloseMode: PropTypes.func,
onSeeCommunity: PropTypes.func,
onShare: PropTypes.func,
@@ -927,6 +955,8 @@ MenuBar.propTypes = {
sessionExists: PropTypes.bool,
shouldSaveBeforeTransition: PropTypes.func,
showComingSoon: PropTypes.bool,
+ theme: PropTypes.string,
+ themeMenuOpen: PropTypes.bool,
userOwnsProject: PropTypes.bool,
username: PropTypes.string,
vm: PropTypes.instanceOf(VM).isRequired
@@ -943,6 +973,7 @@ const mapStateToProps = (state, ownProps) => {
return {
aboutMenuOpen: aboutMenuOpen(state),
accountMenuOpen: accountMenuOpen(state),
+ currentLocale: state.locales.locale,
fileMenuOpen: fileMenuOpen(state),
editMenuOpen: editMenuOpen(state),
isRtl: state.locales.isRtl,
@@ -954,6 +985,8 @@ const mapStateToProps = (state, ownProps) => {
modeMenuOpen: modeMenuOpen(state),
projectTitle: state.scratchGui.projectTitle,
sessionExists: state.session && typeof state.session.session !== 'undefined',
+ theme: state.scratchGui.theme.theme,
+ themeMenuOpen: themeMenuOpen(state),
username: user ? user.username : null,
userOwnsProject: ownProps.authorUsername && user &&
(ownProps.authorUsername === user.username),
@@ -983,11 +1016,14 @@ const mapDispatchToProps = dispatch => ({
onRequestCloseMode: () => dispatch(closeModeMenu()),
onRequestOpenAbout: () => dispatch(openAboutMenu()),
onRequestCloseAbout: () => dispatch(closeAboutMenu()),
+ onRequestOpenTheme: () => dispatch(openThemeMenu()),
+ onRequestCloseTheme: () => dispatch(closeThemeMenu()),
onClickNew: needSave => dispatch(requestNewProject(needSave)),
onClickRemix: () => dispatch(remixProject()),
onClickSave: () => dispatch(manualUpdateProject()),
onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()),
onSeeCommunity: () => dispatch(setPlayer(true)),
+ onChangeTheme: theme => dispatch(setTheme(theme)),
onSetTimeTravelMode: mode => dispatch(setTimeTravel(mode))
});
diff --git a/src/components/menu-bar/theme-menu.css b/src/components/menu-bar/theme-menu.css
new file mode 100644
index 000000000..7c79945c2
--- /dev/null
+++ b/src/components/menu-bar/theme-menu.css
@@ -0,0 +1,52 @@
+@import "../../css/colors.css";
+
+.theme-label {
+ max-width: 7rem;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ margin: 0 .5rem 0 .25rem;
+}
+
+[dir="rtl"] .theme-label {
+ margin: 0 .25rem 0 .5rem;
+}
+
+.theme-icon {
+ height: 1.5rem;
+ vertical-align: middle;
+ margin: 0 .25rem 0 0;
+}
+
+[dir="rtl"] .theme-icon {
+ margin: 0 0 0 .25rem;
+}
+
+.theme-option {
+ display: flex;
+ align-items: center;
+ gap: .5rem;
+}
+
+.theme-active {
+ background-color: $ui-black-transparent;
+}
+
+.check {
+ margin: 0 .25rem 0 0;
+ visibility: hidden;
+}
+
+[dir="rtl"] .check {
+ margin: 0 0 0 .25rem;
+}
+
+.selected {
+ visibility: visible;
+}
+
+@media only screen and (max-width: 1024px) {
+ .theme-label {
+ display: none;
+ }
+}
diff --git a/src/components/menu-bar/theme-menu.jsx b/src/components/menu-bar/theme-menu.jsx
new file mode 100644
index 000000000..afd58aa53
--- /dev/null
+++ b/src/components/menu-bar/theme-menu.jsx
@@ -0,0 +1,105 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
+import classNames from 'classnames';
+
+import MenuBarMenu from './menu-bar-menu.jsx';
+import {DEFAULT_THEME, HIGH_CONTRAST_THEME, themeMap} from '../../lib/themes';
+import {MenuItem, MenuSection} from '../menu/menu.jsx';
+
+import dropdownCaret from './dropdown-caret.svg';
+import check from './check.svg';
+
+import styles from './theme-menu.css';
+import menuBarStyles from './menu-bar.css';
+
+const messages = defineMessages({
+ colorTheme: {
+ id: 'gui.theme.colorTheme',
+ defaultMessage: 'Color theme',
+ description: 'label for selecting the site\'s color theme'
+ }
+});
+
+const ThemeMenuItem = props => {
+ const themeInfo = themeMap[props.theme];
+
+ return (
+ );
+};
+
+ThemeMenuItem.propTypes = {
+ isRtl: PropTypes.bool,
+ isSelected: PropTypes.bool,
+ onClick: PropTypes.func,
+ theme: PropTypes.string
+};
+
+const ThemeMenu = props => {
+ const enabledThemes = [DEFAULT_THEME, HIGH_CONTRAST_THEME];
+ const themeInfo = themeMap[props.theme];
+
+ return (
+

+
+
+
+

+
+
+ {enabledThemes.map(enabledTheme => (
+ props.onChange(enabledTheme)}
+ theme={enabledTheme}
+ />)
+ )}
+
+
+
);
+};
+
+ThemeMenu.propTypes = {
+ className: PropTypes.string,
+ intl: intlShape.isRequired,
+ isRtl: PropTypes.bool,
+ onChange: PropTypes.func,
+ onRequestClose: PropTypes.func,
+ onRequestOpen: PropTypes.func,
+ theme: PropTypes.string,
+ themeMenuOpen: PropTypes.bool
+};
+
+export default injectIntl(ThemeMenu);
diff --git a/src/components/menu/menu.css b/src/components/menu/menu.css
index c48f0d25d..e5d0a66ef 100644
--- a/src/components/menu/menu.css
+++ b/src/components/menu/menu.css
@@ -4,7 +4,7 @@
position: absolute;
border: 1px solid $ui-black-transparent;
border-radius: 0 0 8px 8px;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
padding: 0;
margin: 0;
min-width: 186px;
diff --git a/src/components/modal/modal.css b/src/components/modal/modal.css
index 1ed2425ce..6beaaf8f6 100644
--- a/src/components/modal/modal.css
+++ b/src/components/modal/modal.css
@@ -67,7 +67,7 @@ $sides: 20rem;
box-sizing: border-box;
width: 100%;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1rem;
diff --git a/src/components/monitor/default-monitor.jsx b/src/components/monitor/default-monitor.jsx
index 992014775..3368519dd 100644
--- a/src/components/monitor/default-monitor.jsx
+++ b/src/components/monitor/default-monitor.jsx
@@ -10,7 +10,10 @@ const DefaultMonitor = ({categoryColor, label, value}) => (
{value}
@@ -19,7 +22,10 @@ const DefaultMonitor = ({categoryColor, label, value}) => (
);
DefaultMonitor.propTypes = {
- categoryColor: PropTypes.string.isRequired,
+ categoryColor: PropTypes.shape({
+ background: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+ }).isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
diff --git a/src/components/monitor/large-monitor.jsx b/src/components/monitor/large-monitor.jsx
index 5101ad0a5..64ac32640 100644
--- a/src/components/monitor/large-monitor.jsx
+++ b/src/components/monitor/large-monitor.jsx
@@ -6,7 +6,10 @@ const LargeMonitor = ({categoryColor, value}) => (
{value}
@@ -14,7 +17,10 @@ const LargeMonitor = ({categoryColor, value}) => (
);
LargeMonitor.propTypes = {
- categoryColor: PropTypes.string,
+ categoryColor: PropTypes.shape({
+ background: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+ }).isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
diff --git a/src/components/monitor/list-monitor-scroller.jsx b/src/components/monitor/list-monitor-scroller.jsx
index cfcd6a248..ea0de244e 100644
--- a/src/components/monitor/list-monitor-scroller.jsx
+++ b/src/components/monitor/list-monitor-scroller.jsx
@@ -41,7 +41,10 @@ class ListMonitorScroller extends React.Component {
{this.props.draggable && this.props.activeIndex === index ? (
@@ -51,6 +54,7 @@ class ListMonitorScroller extends React.Component {
autoComplete={false}
className={classNames(styles.listInput, 'no-drag')}
spellCheck={false}
+ style={{color: this.props.categoryColor.text}}
type="text"
value={this.props.activeValue}
onBlur={this.props.onDeactivate}
@@ -97,7 +101,10 @@ class ListMonitorScroller extends React.Component {
ListMonitorScroller.propTypes = {
activeIndex: PropTypes.number,
activeValue: PropTypes.string,
- categoryColor: PropTypes.string,
+ categoryColor: PropTypes.shape({
+ background: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+ }).isRequired,
draggable: PropTypes.bool,
height: PropTypes.number,
onActivate: PropTypes.func,
diff --git a/src/components/monitor/list-monitor.jsx b/src/components/monitor/list-monitor.jsx
index e3422e5b3..4bd9a2563 100644
--- a/src/components/monitor/list-monitor.jsx
+++ b/src/components/monitor/list-monitor.jsx
@@ -54,7 +54,10 @@ const ListMonitor = ({draggable, label, width, height, value, onResizeMouseDown,
ListMonitor.propTypes = {
activeIndex: PropTypes.number,
- categoryColor: PropTypes.string.isRequired,
+ categoryColor: PropTypes.shape({
+ background: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+ }).isRequired,
draggable: PropTypes.bool.isRequired,
height: PropTypes.number,
label: PropTypes.string.isRequired,
diff --git a/src/components/monitor/monitor.css b/src/components/monitor/monitor.css
index 0947db638..a4011457a 100644
--- a/src/components/monitor/monitor.css
+++ b/src/components/monitor/monitor.css
@@ -42,7 +42,6 @@
align-items: center;
min-width: 40px;
text-align: center;
- color: white;
margin: 0 5px;
border-radius: calc($space / 2);
padding: 0 2px;
@@ -55,7 +54,6 @@
min-width: 3rem;
padding: 0.1rem 0.25rem;
text-align: center;
- color: white;
font-size: 1rem;
white-space: pre-wrap;
transform: translateZ(0); /* Fixes flickering in Safari */
@@ -115,7 +113,6 @@
.list-value {
min-width: 40px;
text-align: left;
- color: white;
margin: 0 3px;
border-radius: calc($space / 2);
border: 1px solid $ui-black-transparent;
@@ -161,7 +158,6 @@
padding: 3px 5px;
border: 0;
background: none;
- color: $ui-white;
outline: none;
font-size: 0.75rem;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
@@ -174,7 +170,6 @@
padding: 0;
padding-right: 5px;
cursor: pointer;
- color: $ui-white;
}
.add-button {
diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx
index ddb2f2e57..d9ceb9269 100644
--- a/src/components/monitor/monitor.jsx
+++ b/src/components/monitor/monitor.jsx
@@ -10,17 +10,19 @@ 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 styles from './monitor.css';
-const categories = {
- data: '#FF8C1A',
- sensing: '#5CB1D6',
- sound: '#CF63CF',
- looks: '#9966FF',
- motion: '#4C97FF',
- list: '#FC662C',
- extension: '#0FBD8C'
+// Map category name to color name used in scratch-blocks Blockly.Colours
+const categoryColorMap = {
+ data: 'data',
+ sensing: 'sensing',
+ sound: 'sounds',
+ looks: 'looks',
+ motion: 'motion',
+ list: 'data_lists',
+ extension: 'pen'
};
const modes = {
@@ -30,6 +32,14 @@ const modes = {
list: ListMonitor
};
+const getCategoryColor = (theme, category) => {
+ const colors = getColorsForTheme(theme);
+ return {
+ background: colors[categoryColorMap[category]].primary,
+ text: colors.text
+ };
+};
+
const MonitorComponent = props => (
(
onDoubleClick={props.mode === 'list' || !props.draggable ? null : props.onNextMode}
>
{React.createElement(modes[props.mode], {
- categoryColor: categories[props.category],
+ categoryColor: getCategoryColor(props.theme, props.category),
...props
})}
@@ -122,12 +132,10 @@ const MonitorComponent = props => (
);
-MonitorComponent.categories = categories;
-
const monitorModes = Object.keys(modes);
MonitorComponent.propTypes = {
- category: PropTypes.oneOf(Object.keys(categories)),
+ category: PropTypes.oneOf(Object.keys(categoryColorMap)),
componentRef: PropTypes.func.isRequired,
draggable: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
@@ -140,7 +148,8 @@ MonitorComponent.propTypes = {
onSetModeToDefault: PropTypes.func,
onSetModeToLarge: PropTypes.func,
onSetModeToSlider: PropTypes.func,
- onSliderPromptOpen: PropTypes.func
+ onSliderPromptOpen: PropTypes.func,
+ theme: PropTypes.string.isRequired
};
MonitorComponent.defaultProps = {
diff --git a/src/components/monitor/slider-monitor.jsx b/src/components/monitor/slider-monitor.jsx
index 0271b140a..2bda8f593 100644
--- a/src/components/monitor/slider-monitor.jsx
+++ b/src/components/monitor/slider-monitor.jsx
@@ -12,7 +12,10 @@ const SliderMonitor = ({categoryColor, isDiscrete, label, min, max, value, onSli
{value}
@@ -33,7 +36,10 @@ const SliderMonitor = ({categoryColor, isDiscrete, label, min, max, value, onSli
);
SliderMonitor.propTypes = {
- categoryColor: PropTypes.string.isRequired,
+ categoryColor: PropTypes.shape({
+ background: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+ }).isRequired,
isDiscrete: PropTypes.bool,
label: PropTypes.string.isRequired,
max: PropTypes.number,
diff --git a/src/components/prompt/prompt.css b/src/components/prompt/prompt.css
index c12afc6b7..71fb7fac7 100644
--- a/src/components/prompt/prompt.css
+++ b/src/components/prompt/prompt.css
@@ -64,8 +64,8 @@
}
.button-row button.ok-button {
- background: $motion-primary;
- border: $motion-primary;
+ background: $looks-secondary;
+ border: $looks-secondary;
color: white;
}
diff --git a/src/components/question/question.css b/src/components/question/question.css
index a0f8116ed..ca5c65913 100644
--- a/src/components/question/question.css
+++ b/src/components/question/question.css
@@ -34,7 +34,7 @@
border-radius: 100%;
color: white;
- background: $motion-primary;
+ background: $looks-secondary;
}
[dir="ltr"] .question-submit-button {
@@ -60,7 +60,7 @@
}
.question-input > input:focus {
- box-shadow: 0px 0px 0px 3px $motion-transparent;
+ box-shadow: 0px 0px 0px 3px $looks-transparent;
}
.question-submit-button-icon {
diff --git a/src/components/record-modal/icon--back.svg b/src/components/record-modal/icon--back.svg
index 47d09bc7f..aff0c4f68 100644
--- a/src/components/record-modal/icon--back.svg
+++ b/src/components/record-modal/icon--back.svg
@@ -5,7 +5,7 @@
Created with Sketch.
-
+
diff --git a/src/components/record-modal/icon--play.svg b/src/components/record-modal/icon--play.svg
index 9494fccb8..14ddbbaf2 100644
--- a/src/components/record-modal/icon--play.svg
+++ b/src/components/record-modal/icon--play.svg
@@ -18,7 +18,7 @@
-
+
diff --git a/src/components/record-modal/icon--stop-playback.svg b/src/components/record-modal/icon--stop-playback.svg
index bf7bb64be..c72c363e3 100644
--- a/src/components/record-modal/icon--stop-playback.svg
+++ b/src/components/record-modal/icon--stop-playback.svg
@@ -18,11 +18,11 @@
-
-
+
+
-
\ No newline at end of file
+
diff --git a/src/components/record-modal/record-modal.css b/src/components/record-modal/record-modal.css
index 41fc90a96..e59df3e91 100644
--- a/src/components/record-modal/record-modal.css
+++ b/src/components/record-modal/record-modal.css
@@ -47,7 +47,7 @@
}
.playing-text {
- color: $motion-primary;
+ color: $looks-secondary;
}
.recording-text {
@@ -89,12 +89,12 @@
border: 1px solid $ui-black-transparent;
font-weight: 600;
font-size: 0.85rem;
- color: $motion-primary;
+ color: $looks-secondary;
}
.button-row button.ok-button {
- background: $motion-primary;
- border: $motion-primary;
+ background: $looks-secondary;
+ border: $looks-secondary;
color: white;
}
@@ -106,7 +106,8 @@
text-align: center;
}
-.record-button {
+/* Increase specificity to make sure this wins out over normalize.css */
+svg.record-button {
overflow: visible;
}
diff --git a/src/components/slider-prompt/slider-prompt.css b/src/components/slider-prompt/slider-prompt.css
index c1ae4854a..146b11551 100644
--- a/src/components/slider-prompt/slider-prompt.css
+++ b/src/components/slider-prompt/slider-prompt.css
@@ -41,8 +41,8 @@
}
.button-row button.ok-button {
- background: $motion-primary;
- border: $motion-primary;
+ background: $looks-secondary;
+ border: $looks-secondary;
color: white;
}
diff --git a/src/components/sound-editor/icon--copy-to-new.svg b/src/components/sound-editor/icon--copy-to-new.svg
index d5f462117..4d228a177 100644
--- a/src/components/sound-editor/icon--copy-to-new.svg
+++ b/src/components/sound-editor/icon--copy-to-new.svg
@@ -4,6 +4,6 @@
Sound/General/Copy to New
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sound-editor/icon--copy.svg b/src/components/sound-editor/icon--copy.svg
index 7bac62c1b..b905e4342 100644
--- a/src/components/sound-editor/icon--copy.svg
+++ b/src/components/sound-editor/icon--copy.svg
@@ -4,11 +4,11 @@
Sound/General/Copy
Created with Sketch.
-
+
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sound-editor/icon--delete.svg b/src/components/sound-editor/icon--delete.svg
index 8f662201e..c7e096962 100644
--- a/src/components/sound-editor/icon--delete.svg
+++ b/src/components/sound-editor/icon--delete.svg
@@ -11,8 +11,8 @@
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sound-editor/icon--paste.svg b/src/components/sound-editor/icon--paste.svg
index 53ca104c1..51027762f 100644
--- a/src/components/sound-editor/icon--paste.svg
+++ b/src/components/sound-editor/icon--paste.svg
@@ -4,6 +4,6 @@
Sound/General/Paste
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sound-editor/icon--redo.svg b/src/components/sound-editor/icon--redo.svg
index fdc3b1780..c32f94494 100644
--- a/src/components/sound-editor/icon--redo.svg
+++ b/src/components/sound-editor/icon--redo.svg
@@ -5,7 +5,7 @@
Created with Sketch.
-
+
diff --git a/src/components/sound-editor/icon--undo.svg b/src/components/sound-editor/icon--undo.svg
index 54ff88ee4..44c9517b7 100644
--- a/src/components/sound-editor/icon--undo.svg
+++ b/src/components/sound-editor/icon--undo.svg
@@ -5,7 +5,7 @@
Created with Sketch.
-
+
diff --git a/src/components/sound-editor/sound-editor.css b/src/components/sound-editor/sound-editor.css
index 7a791e6ab..e2e38a020 100644
--- a/src/components/sound-editor/sound-editor.css
+++ b/src/components/sound-editor/sound-editor.css
@@ -79,9 +79,24 @@ $border-radius: 0.25rem;
border: 1px solid $ui-black-transparent;
cursor: pointer;
font-size: 0.85rem;
- transition: 0.2s;
user-select: none;
margin: 0px;
+ position: relative;
+}
+
+.button:active {
+ background: $looks-transparent;
+}
+
+.button:focus::before {
+ content: "";
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+ border: 1px solid $looks-secondary;
+ z-index: 1;
}
.button > img {
@@ -95,7 +110,7 @@ $border-radius: 0.25rem;
height: 3rem;
width: 3rem;
outline: none;
- background: $motion-primary;
+ background: $looks-secondary;
border-radius: 100%;
border: 4px solid $ui-white-dim;
cursor: pointer;
@@ -191,33 +206,27 @@ $border-radius: 0.25rem;
border-radius: 0;
}
-[dir="ltr"] .button-group .button {
- border-left: none;
-}
-[dir="rtl"] .button-group .button {
+.button:not(:last-child) {
border-right: none;
}
-[dir="ltr"] .button-group .button:last-of-type {
- border-top-right-radius: $border-radius;
- border-bottom-right-radius: $border-radius;
-}
-
-[dir="ltr"] .button-group .button:first-of-type {
- border-left: 1px solid $ui-black-transparent;
- border-top-left-radius: $border-radius;
- border-bottom-left-radius: $border-radius;
-}
-
-[dir="rtl"] .button-group .button:last-of-type {
- border-top-left-radius: $border-radius;
- border-bottom-left-radius: $border-radius;
-}
-
-[dir="rtl"] .button-group .button:first-of-type {
+[dir="rtl"] .button:not(:last-child) {
+ border-left: none;
border-right: 1px solid $ui-black-transparent;
- border-top-right-radius: $border-radius;
- border-bottom-right-radius: $border-radius;
+}
+
+.button-group .button:first-child,
+[dir="rtl"] .button-group .button:last-child,
+.button-group .button:first-child:focus::before,
+[dir="rtl"] .button-group .button:last-child:focus::before {
+ border-radius: $form-radius 0 0 $form-radius;
+}
+
+.button-group .button:last-child,
+[dir="rtl"] .button-group .button:first-child,
+.button-group .button:last-child:focus::before,
+[dir="rtl"] .button-group .button:first-child:focus::before {
+ border-radius: 0 $form-radius $form-radius 0;
}
.button:disabled > img {
diff --git a/src/components/spinner/spinner.css b/src/components/spinner/spinner.css
index 16d323f5f..48601b034 100644
--- a/src/components/spinner/spinner.css
+++ b/src/components/spinner/spinner.css
@@ -83,8 +83,8 @@
}
.spinner.primary {
- border-color: $motion-transparent;
+ border-color: $looks-transparent;
}
.spinner.primary::after {
- border-top-color: $motion-primary;
+ border-top-color: $looks-secondary;
}
diff --git a/src/components/sprite-info/icon--hide.svg b/src/components/sprite-info/icon--hide.svg
index 2fd66b830..347b4db5b 100644
--- a/src/components/sprite-info/icon--hide.svg
+++ b/src/components/sprite-info/icon--hide.svg
@@ -5,10 +5,10 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sprite-info/icon--show.svg b/src/components/sprite-info/icon--show.svg
index fe54ed568..d6fd9280d 100644
--- a/src/components/sprite-info/icon--show.svg
+++ b/src/components/sprite-info/icon--show.svg
@@ -5,11 +5,11 @@
Created with Sketch.
-
+
-
\ No newline at end of file
+
diff --git a/src/components/sprite-info/sprite-info.css b/src/components/sprite-info/sprite-info.css
index 93ca30f40..364df9fce 100644
--- a/src/components/sprite-info/sprite-info.css
+++ b/src/components/sprite-info/sprite-info.css
@@ -47,70 +47,6 @@
user-select: none;
}
-.icon {
- width: 100%;
- height: 100%;
-}
-
-/* @todo: refactor radio divs to input */
-.radio-wrapper {
- white-space: nowrap; /* make sure visibilty buttons don't wrap */
-}
-
-.radio {
- filter: saturate(0);
- cursor: pointer;
-}
-.radio.is-active {
- filter: none;
-}
-.radio.is-disabled {
- cursor: default;
-}
-
-.radio-first {
- border: 1px solid $ui-black-transparent;
-}
-
-[dir="ltr"] .radio-first {
- border-top-left-radius: $form-radius;
- border-bottom-left-radius: $form-radius;
-}
-
-[dir="rtl"] .radio-first {
- border-top-right-radius: $form-radius;
- border-bottom-right-radius: $form-radius;
-}
-
-.radio-first:focus {
- border-color: $motion-primary;
- box-shadow: inset 0 0 0 -2px $ui-black-transparent;
-}
-
-.radio-last {
- border-bottom: 1px solid $ui-black-transparent;
- border-top: 1px solid $ui-black-transparent;
-}
-
-[dir="ltr"] .radio-last {
- border-right: 1px solid $ui-black-transparent;
- border-left: 1px solid $ui-black-transparent;
- border-top-right-radius: $form-radius;
- border-bottom-right-radius: $form-radius;
-}
-
-[dir="rtl"] .radio-last {
- border-left: 1px solid $ui-black-transparent;
- border-right: 1px solid $ui-black-transparent;
- border-top-left-radius: $form-radius;
- border-bottom-left-radius: $form-radius;
-}
-
-.radio-last:focus {
- border-color: $motion-primary;
- box-shadow: inset 0 0 0 -2px $ui-black-transparent;
-}
-
.icon {
width: 100%;
height: 100%;
@@ -126,7 +62,7 @@
}
.rotation-select:focus {
- border-color: $motion-primary;
+ border-color: $looks-secondary;
box-shadow: inset 0 0 0 1px $ui-black-transparent);
}
diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx
index 09c94d016..6f0fcd1db 100644
--- a/src/components/sprite-info/sprite-info.jsx
+++ b/src/components/sprite-info/sprite-info.jsx
@@ -19,6 +19,7 @@ import xIcon from './icon--x.svg';
import yIcon from './icon--y.svg';
import showIcon from './icon--show.svg';
import hideIcon from './icon--hide.svg';
+import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';
const BufferedInput = BufferedInputHOC(Input);
@@ -27,6 +28,16 @@ const messages = defineMessages({
id: 'gui.SpriteInfo.spritePlaceholder',
defaultMessage: 'Name',
description: 'Placeholder text for sprite name'
+ },
+ showSpriteAction: {
+ id: 'gui.SpriteInfo.showSpriteAction',
+ defaultMessage: 'Show sprite',
+ description: 'Tooltip for show sprite button'
+ },
+ hideSpriteAction: {
+ id: 'gui.SpriteInfo.hideSpriteAction',
+ defaultMessage: 'Hide sprite',
+ description: 'Tooltip for hide sprite button'
}
});
@@ -185,46 +196,23 @@ class SpriteInfo extends React.Component {
/> :
null
}
-
-
-

-
-
-

-
-
+
) : (
-
+
+
+
);
header = (
@@ -110,40 +112,24 @@ const StageHeaderComponent = function (props) {
[]
) : (
-
-
-
-
-
-
+
);
header = (
diff --git a/src/components/stage-selector/stage-selector.css b/src/components/stage-selector/stage-selector.css
index 742898bd3..f93a0c344 100644
--- a/src/components/stage-selector/stage-selector.css
+++ b/src/components/stage-selector/stage-selector.css
@@ -26,12 +26,12 @@ $header-height: calc($stage-menu-height - 2px);
.stage-selector.is-selected {
border-top-left-radius: .625rem;
border-top-right-radius: .625rem;
- border-color: $motion-primary;
- box-shadow: 0px 0px 0px 4px $motion-transparent;
+ border-color: $looks-secondary;
+ box-shadow: 0px 0px 0px 4px $looks-transparent;
}
.stage-selector:hover {
- border-color: $motion-primary;
+ border-color: $looks-secondary;
}
.header {
@@ -60,7 +60,7 @@ $header-height: calc($stage-menu-height - 2px);
}
.stage-selector.is-selected .header {
- background-color: $motion-primary;
+ background-color: $looks-secondary;
}
.stage-selector.is-selected .header-title {
diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css
index cb8b4f630..a799e05ce 100644
--- a/src/components/stage/stage.css
+++ b/src/components/stage/stage.css
@@ -108,8 +108,8 @@ stage doesn't "spill" out from under its rounded corners. instead, shift these o
}
.frame {
- background: $motion-transparent;
- border: 2px solid $motion-primary;
+ background: $looks-transparent;
+ border: 2px solid $looks-secondary;
border-radius: 0.5rem;
animation-name: flash;
animation-duration: 0.75s;
diff --git a/src/components/stop-all/stop-all.css b/src/components/stop-all/stop-all.css
index da0bcc7b3..ab89a5c70 100644
--- a/src/components/stop-all/stop-all.css
+++ b/src/components/stop-all/stop-all.css
@@ -10,7 +10,7 @@
}
.stop-all:hover {
- background-color: $motion-light-transparent;
+ background-color: $looks-light-transparent;
}
.stop-all {
diff --git a/src/components/tag-button/tag-button.css b/src/components/tag-button/tag-button.css
index b77004513..83c9ee9ca 100644
--- a/src/components/tag-button/tag-button.css
+++ b/src/components/tag-button/tag-button.css
@@ -3,7 +3,7 @@
.tag-button {
padding: .625rem 1rem;
- background: $motion-primary;
+ background: $looks-secondary;
border-radius: 1.375rem;
color: $ui-white;
height: $library-filter-bar-height;
diff --git a/src/components/telemetry-modal/telemetry-modal.css b/src/components/telemetry-modal/telemetry-modal.css
index 73f2d6a1c..d94f2f00c 100644
--- a/src/components/telemetry-modal/telemetry-modal.css
+++ b/src/components/telemetry-modal/telemetry-modal.css
@@ -35,7 +35,7 @@ $ui-gray: hsla(0, 0%, 95%, 1);
.illustration {
width: 100%;
height: 123px;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
background-image: url('./telemetry-modal-header.png');
background-size: cover;
}
@@ -46,7 +46,7 @@ $ui-gray: hsla(0, 0%, 95%, 1);
}
.privacy-policy-link {
- color: $motion-primary;
+ color: $looks-secondary;
text-decoration: none;
}
@@ -92,14 +92,14 @@ $ui-gray: hsla(0, 0%, 95%, 1);
.radio-buttons input[type="radio"]:checked {
transition: all .25s ease;
background-color: $ui-white;
- border: 1px solid $motion-primary;
+ border: 1px solid $looks-secondary;
}
.radio-buttons input[type="radio"]:checked::after {
display: block;
margin: 0.125rem;
border-radius: 50%;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
width: .625rem;
height: .625rem;
content: "";
@@ -127,11 +127,11 @@ $ui-gray: hsla(0, 0%, 95%, 1);
}
.button-row button {
- border: 1px solid $motion-primary;
+ border: 1px solid $looks-secondary;
border-radius: 0.25rem;
padding: 0.5rem 1.5rem;
color: white;
- background: $motion-primary;
+ background: $looks-secondary;
font-weight: bold;
font-size: .875rem;
cursor: pointer;
@@ -139,7 +139,7 @@ $ui-gray: hsla(0, 0%, 95%, 1);
.button-row button:hover {
background: $extensions-primary;
- box-shadow: 0 0 0 6px $motion-transparent;
+ box-shadow: 0 0 0 6px $looks-transparent;
}
.button-row button:disabled {
diff --git a/src/components/toggle-buttons/toggle-buttons.css b/src/components/toggle-buttons/toggle-buttons.css
new file mode 100644
index 000000000..d5b54e51b
--- /dev/null
+++ b/src/components/toggle-buttons/toggle-buttons.css
@@ -0,0 +1,85 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
+.row {
+ display: flex;
+ flex-direction: row;
+}
+
+.button {
+ margin: 0;
+ border: 1px solid $ui-black-transparent;
+ background: none;
+ outline: none;
+ cursor: pointer;
+ user-select: none;
+ position: relative;
+ width: 34px;
+ height: 34px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.button > img {
+ display: block;
+ transition: transform 0.1s;
+ filter: grayscale(100%) opacity(.5);
+}
+
+.button:not(:last-child) {
+ border-right: none;
+}
+
+[dir="rtl"] .button:not(:last-child) {
+ border-left: none;
+ border-right: 1px solid $ui-black-transparent;
+}
+
+.button:focus::before {
+ content: "";
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+ border: 1px solid $looks-secondary;
+}
+
+/* round corners for first and last buttons */
+
+.button:first-child,
+[dir="rtl"] .button:last-child,
+.button:first-child:focus::before,
+[dir="rtl"] .button:last-child:focus::before {
+ border-radius: $form-radius 0 0 $form-radius;
+}
+
+.button:last-child,
+[dir="rtl"] .button:first-child,
+.button:last-child:focus::before,
+[dir="rtl"] .button:first-child:focus::before {
+ border-radius: 0 $form-radius $form-radius 0;
+}
+
+/* selected button styling */
+
+.button[aria-pressed='true'] {
+ background-color: $looks-light-transparent;
+}
+
+.button[aria-pressed='true'] > img {
+ filter: none;
+}
+
+/* pressed button styling */
+
+.button:active {
+ background-color: $looks-transparent;
+}
+
+/* disabled styling */
+
+.disabled .button {
+ cursor: default;
+}
diff --git a/src/components/toggle-buttons/toggle-buttons.jsx b/src/components/toggle-buttons/toggle-buttons.jsx
new file mode 100644
index 000000000..f09f60e86
--- /dev/null
+++ b/src/components/toggle-buttons/toggle-buttons.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import styles from './toggle-buttons.css';
+
+const ToggleButtons = ({buttons, className, disabled}) => (
+
+ {buttons.map((button, index) => (
+
+ ))}
+
+);
+
+ToggleButtons.propTypes = {
+ buttons: PropTypes.arrayOf(PropTypes.shape({
+ title: PropTypes.string.isRequired,
+ handleClick: PropTypes.func.isRequired,
+ icon: PropTypes.string.isRequired,
+ iconClassName: PropTypes.string,
+ isSelected: PropTypes.bool
+ })),
+ className: PropTypes.string,
+ disabled: PropTypes.bool
+};
+
+ToggleButtons.defaultProps = {
+ disabled: false
+};
+
+export default ToggleButtons;
diff --git a/src/components/webgl-modal/webgl-modal.css b/src/components/webgl-modal/webgl-modal.css
index 85fcc0d01..43327f211 100644
--- a/src/components/webgl-modal/webgl-modal.css
+++ b/src/components/webgl-modal/webgl-modal.css
@@ -29,7 +29,7 @@
.illustration {
width: 100%;
height: 208px;
- background-color: $motion-primary;
+ background-color: $looks-secondary;
background-image: url('./unsupported.png');
background-size: cover;
}
@@ -54,10 +54,10 @@
}
.button-row button {
- border: 1px solid $motion-primary;
+ border: 1px solid $looks-secondary;
border-radius: 0.25rem;
padding: 0.5rem 2rem;
- background: $motion-primary;
+ background: $looks-secondary;
color: white;
font-weight: bold;
font-size: 0.875rem;
@@ -70,6 +70,6 @@
}
.faq-link {
- color: $motion-primary;
+ color: $looks-secondary;
text-decoration: none;
}
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 616d2e090..750be83fc 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -18,6 +18,8 @@ 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 {connect} from 'react-redux';
import {updateToolbox} from '../reducers/toolbox';
@@ -100,7 +102,7 @@ class Blocks extends React.Component {
const workspaceConfig = defaultsDeep({},
Blocks.defaultOptions,
this.props.options,
- {rtl: this.props.isRtl, toolbox: this.props.toolboxXML}
+ {rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme)}
);
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
@@ -353,11 +355,15 @@ class Blocks extends React.Component {
const stageCostumes = stage.getCostumes();
const targetCostumes = target.getCostumes();
const targetSounds = target.getSounds();
- const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(target);
+ const dynamicBlocksXML = injectExtensionCategoryTheme(
+ this.props.vm.runtime.getBlocksXML(target),
+ this.props.theme
+ );
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 : ''
+ targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '',
+ getColorsForTheme(this.props.theme)
);
} catch {
return null;
@@ -436,7 +442,7 @@ class Blocks extends React.Component {
if (blockInfo.info && blockInfo.info.isDynamic) {
dynamicBlocksInfo.push(blockInfo);
} else if (blockInfo.json) {
- staticBlocksJson.push(blockInfo.json);
+ staticBlocksJson.push(injectExtensionBlockTheme(blockInfo.json, this.props.theme));
}
// otherwise it's a non-block entry such as '---'
});
@@ -627,22 +633,11 @@ Blocks.propTypes = {
wheel: PropTypes.bool,
startScale: PropTypes.number
}),
- colours: PropTypes.shape({
- workspace: PropTypes.string,
- flyout: PropTypes.string,
- toolbox: PropTypes.string,
- toolboxSelected: PropTypes.string,
- scrollbar: PropTypes.string,
- scrollbarHover: PropTypes.string,
- insertionMarker: PropTypes.string,
- insertionMarkerOpacity: PropTypes.number,
- fieldShadow: PropTypes.string,
- dragShadowOpacity: PropTypes.number
- }),
comments: PropTypes.bool,
collapse: PropTypes.bool
}),
stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
+ theme: PropTypes.oneOf(Object.keys(themeMap)),
toolboxXML: PropTypes.string,
updateMetrics: PropTypes.func,
updateToolboxState: PropTypes.func,
@@ -664,18 +659,6 @@ Blocks.defaultOptions = {
length: 2,
colour: '#ddd'
},
- colours: {
- workspace: '#F9F9F9',
- flyout: '#F9F9F9',
- toolbox: '#FFFFFF',
- toolboxSelected: '#E9EEF2',
- scrollbar: '#CECDCE',
- scrollbarHover: '#CECDCE',
- insertionMarker: '#000000',
- insertionMarkerOpacity: 0.2,
- fieldShadow: 'rgba(255, 255, 255, 0.3)',
- dragShadowOpacity: 0.6
- },
comments: true,
collapse: false,
sounds: false
@@ -683,7 +666,8 @@ Blocks.defaultOptions = {
Blocks.defaultProps = {
isVisible: true,
- options: Blocks.defaultOptions
+ options: Blocks.defaultOptions,
+ theme: DEFAULT_THEME
};
const mapStateToProps = state => ({
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 0081c83c5..55055f0f6 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -36,6 +36,7 @@ import storage from '../lib/storage';
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
import vmManagerHOC from '../lib/vm-manager-hoc.jsx';
import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
+import systemPreferencesHOC from '../lib/system-preferences-hoc.jsx';
import GUIComponent from '../components/gui/gui.jsx';
import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
@@ -185,7 +186,8 @@ const WrappedGui = compose(
vmListenerHOC,
vmManagerHOC,
SBFileUploaderHOC,
- cloudManagerHOC
+ cloudManagerHOC,
+ systemPreferencesHOC
)(ConnectedGUI);
WrappedGui.setAppElement = ReactModal.setAppElement;
diff --git a/src/containers/monitor.jsx b/src/containers/monitor.jsx
index b952a1836..f45c2d1ef 100644
--- a/src/containers/monitor.jsx
+++ b/src/containers/monitor.jsx
@@ -9,6 +9,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 SliderPrompt from './slider-prompt.jsx';
import {connect} from 'react-redux';
@@ -213,6 +214,7 @@ class Monitor extends React.Component {
min={this.props.min}
mode={this.props.mode}
targetId={this.props.targetId}
+ theme={this.props.theme}
width={this.props.width}
onDragEnd={this.handleDragEnd}
onExport={isList ? this.handleExport : null}
@@ -250,6 +252,7 @@ Monitor.propTypes = {
resizeMonitorRect: PropTypes.func.isRequired,
spriteName: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
targetId: PropTypes.string,
+ theme: PropTypes.string,
toolboxXML: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
value: PropTypes.oneOfType([
PropTypes.string,
@@ -264,8 +267,12 @@ Monitor.propTypes = {
x: PropTypes.number,
y: PropTypes.number
};
+Monitor.defaultProps = {
+ theme: DEFAULT_THEME
+};
const mapStateToProps = state => ({
monitorLayout: state.scratchGui.monitorLayout,
+ theme: state.scratchGui.theme.theme,
// 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/src/containers/sprite-info.jsx b/src/containers/sprite-info.jsx
index fe0a7267e..2499ed104 100644
--- a/src/containers/sprite-info.jsx
+++ b/src/containers/sprite-info.jsx
@@ -9,9 +9,7 @@ class SpriteInfo extends React.Component {
super(props);
bindAll(this, [
'handleClickVisible',
- 'handleClickNotVisible',
- 'handlePressVisible',
- 'handlePressNotVisible'
+ 'handleClickNotVisible'
]);
}
handleClickVisible (e) {
@@ -22,26 +20,12 @@ class SpriteInfo extends React.Component {
e.preventDefault();
this.props.onChangeVisibility(false);
}
- handlePressVisible (e) {
- if (e.key === ' ' || e.key === 'Enter') {
- e.preventDefault();
- this.props.onChangeVisibility(true);
- }
- }
- handlePressNotVisible (e) {
- if (e.key === ' ' || e.key === 'Enter') {
- e.preventDefault();
- this.props.onChangeVisibility(false);
- }
- }
render () {
return (
);
}
diff --git a/src/css/colors.css b/src/css/colors.css
index 6f38510e2..13a861788 100644
--- a/src/css/colors.css
+++ b/src/css/colors.css
@@ -16,8 +16,11 @@ $text-primary-transparent: hsla(225, 15%, 40%, 0.75);
$motion-primary: hsla(215, 100%, 65%, 1); /* #4C97FF */
$motion-tertiary: hsla(215, 60%, 50%, 1); /* #3373CC */
-$motion-transparent: hsla(215, 100%, 65%, 0.35); /* 35% transparent version of motion-primary */
-$motion-light-transparent: hsla(215, 100%, 65%, 0.15); /* 15% transparent version of motion-primary */
+
+$looks-secondary: hsla(260, 60%, 60%, 1); /* #855CD6 */
+$looks-transparent: hsla(260, 60%, 60%, 0.35); /* 35% transparent version of looks-tertiary */
+$looks-light-transparent: hsla(260, 60%, 60%, 0.15); /* 15% transparent version of looks-tertiary */
+$looks-secondary-dark: hsla(260, 42%, 51%, 1); /* #714EB6 */
$red-primary: hsla(20, 100%, 55%, 1); /* #FF661A */
$red-tertiary: hsla(20, 100%, 45%, 1); /* #E64D00 */
diff --git a/src/css/units.css b/src/css/units.css
index 0b00f3234..587385fc0 100644
--- a/src/css/units.css
+++ b/src/css/units.css
@@ -6,7 +6,6 @@ $space: 0.5rem;
$sprites-per-row: 5;
$menu-bar-height: 3rem;
-$language-selector-width: 3rem;
$sprite-info-height: 6rem;
$stage-menu-height: 2.75rem;
diff --git a/src/lib/blocks.js b/src/lib/blocks.js
index bb9a94db3..4412f1ad4 100644
--- a/src/lib/blocks.js
+++ b/src/lib/blocks.js
@@ -23,6 +23,7 @@ export default function (vm, useCatBlocks) {
colour: colors.secondary,
colourSecondary: colors.secondary,
colourTertiary: colors.tertiary,
+ colourQuaternary: colors.quaternary,
outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND
};
};
@@ -42,6 +43,7 @@ export default function (vm, useCatBlocks) {
colour: colors.primary,
colourSecondary: colors.secondary,
colourTertiary: colors.tertiary,
+ colourQuaternary: colors.quaternary,
extensions: ['shape_hat']
};
};
@@ -68,6 +70,7 @@ export default function (vm, useCatBlocks) {
colour: ScratchBlocks.Colours.sensing.primary,
colourSecondary: ScratchBlocks.Colours.sensing.secondary,
colourTertiary: ScratchBlocks.Colours.sensing.tertiary,
+ colourQuaternary: ScratchBlocks.Colours.sensing.quaternary,
outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND
};
};
diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js
index 0b618979d..8d356e442 100644
--- a/src/lib/make-toolbox-xml.js
+++ b/src/lib/make-toolbox-xml.js
@@ -1,17 +1,19 @@
import ScratchBlocks from 'scratch-blocks';
+import {defaultColors} from './themes';
const categorySeparator = '';
const blockSeparator = ''; // At default scale, about 28px
/* eslint-disable no-unused-vars */
-const motion = function (isInitialSetup, isStage, targetId) {
+const motion = function (isInitialSetup, isStage, targetId, colors) {
const stageSelected = ScratchBlocks.ScratchMsgs.translate(
'MOTION_STAGE_SELECTED',
'Stage selected: no motion blocks'
);
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
${isStage ? `
` : `
@@ -151,11 +153,12 @@ const xmlEscape = function (unsafe) {
});
};
-const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName) {
+const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName, colors) {
const hello = ScratchBlocks.ScratchMsgs.translate('LOOKS_HELLO', 'Hello!');
const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...');
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
${isStage ? '' : `
@@ -288,9 +291,10 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop
`;
};
-const sound = function (isInitialSetup, isStage, targetId, soundName) {
+const sound = function (isInitialSetup, isStage, targetId, soundName, colors) {
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
@@ -343,9 +347,10 @@ const sound = function (isInitialSetup, isStage, targetId, soundName) {
`;
};
-const events = function (isInitialSetup, isStage) {
+const events = function (isInitialSetup, isStage, targetId, colors) {
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
@@ -382,9 +387,14 @@ const events = function (isInitialSetup, isStage) {
`;
};
-const control = function (isInitialSetup, isStage) {
+const control = function (isInitialSetup, isStage, targetId, colors) {
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
@@ -429,10 +439,15 @@ const control = function (isInitialSetup, isStage) {
`;
};
-const sensing = function (isInitialSetup, isStage) {
+const sensing = function (isInitialSetup, isStage, targetId, colors) {
const name = ScratchBlocks.ScratchMsgs.translate('SENSING_ASK_TEXT', 'What\'s your name?');
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
${isStage ? '' : `
@@ -504,12 +519,17 @@ const sensing = function (isInitialSetup, isStage) {
`;
};
-const operators = function (isInitialSetup) {
+const operators = function (isInitialSetup, isStage, targetId, colors) {
const apple = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_APPLE', 'apple');
const banana = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_BANANA', 'banana');
const letter = ScratchBlocks.ScratchMsgs.translate('OPERATORS_LETTEROF_APPLE', 'a');
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
-
+
@@ -691,25 +711,27 @@ const operators = function (isInitialSetup) {
`;
};
-const variables = function () {
+const variables = function (isInitialSetup, isStage, targetId, colors) {
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
`;
};
-const myBlocks = function () {
+const myBlocks = function (isInitialSetup, isStage, targetId, colors) {
+ // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
`;
@@ -732,10 +754,11 @@ 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.
* @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
*/
const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categoriesXML = [],
- costumeName = '', backdropName = '', soundName = '') {
+ costumeName = '', backdropName = '', soundName = '', colors = defaultColors) {
isStage = isInitialSetup || isStage;
const gap = [categorySeparator];
@@ -753,15 +776,16 @@ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categ
}
// return `undefined`
};
- const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId);
- const looksXML = moveCategory('looks') || looks(isInitialSetup, isStage, targetId, costumeName, backdropName);
- const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName);
- const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId);
- const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId);
- const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId);
- const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId);
- const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId);
- const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId);
+ const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId, colors.motion);
+ const looksXML = moveCategory('looks') ||
+ looks(isInitialSetup, isStage, targetId, costumeName, backdropName, colors.looks);
+ const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName, colors.sounds);
+ const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId, colors.event);
+ const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId, colors.control);
+ const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId, colors.sensing);
+ const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId, colors.operators);
+ const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId, colors.data);
+ const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId, colors.more);
const everything = [
xmlOpen,
diff --git a/src/lib/system-preferences-hoc.jsx b/src/lib/system-preferences-hoc.jsx
new file mode 100644
index 000000000..c38cd9f37
--- /dev/null
+++ b/src/lib/system-preferences-hoc.jsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {connect} from 'react-redux';
+
+import {setTheme} from '../reducers/theme';
+import {detectTheme} from './themes/themePersistance';
+
+// Dark mode is not yet supported
+// const prefersDarkQuery = '(prefers-color-scheme: dark)';
+const prefersHighContrastQuery = '(prefers-contrast: more)';
+
+const systemPreferencesHOC = function (WrappedComponent) {
+ class SystemPreferences extends React.Component {
+ componentDidMount () {
+ this.preferencesListener = () => this.props.onSetTheme(detectTheme());
+
+ this.highContrastMatchMedia = window.matchMedia(prefersHighContrastQuery);
+ this.highContrastMatchMedia.addEventListener('change', this.preferencesListener);
+ }
+
+ componentWillUnmount () {
+ this.highContrastMatchMedia.removeEventListener('change', this.preferencesListener);
+ }
+
+ render () {
+ const {
+ /* eslint-disable no-unused-vars */
+ onSetTheme,
+ /* eslint-enable no-unused-vars */
+ ...props
+ } = this.props;
+ return ;
+ }
+ }
+
+ SystemPreferences.propTypes = {
+ onSetTheme: PropTypes.func
+ };
+
+ const mapDispatchToProps = dispatch => ({
+ onSetTheme: theme => dispatch(setTheme(theme))
+ });
+
+ return connect(
+ null,
+ mapDispatchToProps
+ )(SystemPreferences);
+};
+
+export default systemPreferencesHOC;
diff --git a/src/lib/themes/blockHelpers.js b/src/lib/themes/blockHelpers.js
new file mode 100644
index 000000000..b201241ee
--- /dev/null
+++ b/src/lib/themes/blockHelpers.js
@@ -0,0 +1,104 @@
+import {DEFAULT_THEME, getColorsForTheme, themeMap} from '.';
+
+const getBlockIconURI = extensionIcons => {
+ if (!extensionIcons) return null;
+
+ return extensionIcons.blockIconURI || extensionIcons.menuIconURI;
+};
+
+const getCategoryIconURI = extensionIcons => {
+ if (!extensionIcons) return null;
+
+ return extensionIcons.menuIconURI || extensionIcons.blockIconURI;
+};
+
+// scratch-blocks colours has a pen property that scratch-gui uses for all extensions
+const getExtensionColors = theme => getColorsForTheme(theme).pen;
+
+/**
+ * Applies extension color theme to categories.
+ * No changes are applied if called with the default theme, allowing extensions to provide their own colors.
+ * These colors are not seen if the category provides a blockIconURI.
+ * @param {Array.