mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 09:35:56 -05:00
Merge branch 'develop' into studio-comments-off
This commit is contained in:
commit
64f9d80c2a
48 changed files with 866 additions and 360 deletions
109
package-lock.json
generated
109
package-lock.json
generated
|
@ -225,17 +225,17 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz",
|
||||||
"integrity": "sha512-RN/AwP2DJmQTZSfiDaD+JQQ/J99KsIpOCfBE5pL+5jJSt7nI3nYGoAXZu+ffYSQ029NLs2DstZb+eR81uuARgg==",
|
"integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.14.5",
|
"@babel/code-frame": "^7.14.5",
|
||||||
"@babel/generator": "^7.14.5",
|
"@babel/generator": "^7.14.5",
|
||||||
"@babel/helper-compilation-targets": "^7.14.5",
|
"@babel/helper-compilation-targets": "^7.14.5",
|
||||||
"@babel/helper-module-transforms": "^7.14.5",
|
"@babel/helper-module-transforms": "^7.14.5",
|
||||||
"@babel/helpers": "^7.14.5",
|
"@babel/helpers": "^7.14.6",
|
||||||
"@babel/parser": "^7.14.5",
|
"@babel/parser": "^7.14.6",
|
||||||
"@babel/template": "^7.14.5",
|
"@babel/template": "^7.14.5",
|
||||||
"@babel/traverse": "^7.14.5",
|
"@babel/traverse": "^7.14.5",
|
||||||
"@babel/types": "^7.14.5",
|
"@babel/types": "^7.14.5",
|
||||||
|
@ -308,9 +308,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||||
"integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==",
|
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
|
@ -695,9 +695,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||||
"integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==",
|
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
|
@ -914,9 +914,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||||
"integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==",
|
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
|
@ -1076,9 +1076,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helpers": {
|
"@babel/helpers": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz",
|
||||||
"integrity": "sha512-xtcWOuN9VL6nApgVHtq3PPcQv5qFBJzoSZzJ/2c0QK/IP/gxVcoWSNQwFEGvmbQsuS9rhYqjILDGGXcTkA705Q==",
|
"integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/template": "^7.14.5",
|
"@babel/template": "^7.14.5",
|
||||||
|
@ -1147,9 +1147,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||||
"integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==",
|
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
|
@ -17223,9 +17223,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "7.0.35",
|
"version": "7.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
|
||||||
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
|
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
@ -17883,9 +17883,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "7.0.35",
|
"version": "7.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
|
||||||
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
|
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
@ -20873,9 +20873,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-blocks": {
|
"scratch-blocks": {
|
||||||
"version": "0.1.0-prerelease.20210609211941",
|
"version": "0.1.0-prerelease.20210615035054",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210609211941.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210615035054.tgz",
|
||||||
"integrity": "sha512-lVPmB4DMpM9RxEXcWMBPsm8qdT63Ef90A9HBZnUmh+eCdIc8bylJyNKLNfbiDHItVuEhA0KE/UBMMCy7fanEBA==",
|
"integrity": "sha512-/YGZN3QiMa41gtcyHUUUVTfhztoT7zEb0+N+FeBmtbkZfeESOtEugJ0y0ftttUY7WJRga35TTDp1UdEyo7sxAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"exports-loader": "0.6.3",
|
"exports-loader": "0.6.3",
|
||||||
|
@ -20883,9 +20883,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-gui": {
|
"scratch-gui": {
|
||||||
"version": "0.1.0-prerelease.20210610000654",
|
"version": "0.1.0-prerelease.20210615041617",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210610000654.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210615041617.tgz",
|
||||||
"integrity": "sha512-0XqUnKAii9mJ9HGhfQE88bi8WBuRr5pG3JuuGLWL4KLI6qrMtDAcefp8GgU1PGjtRgdeAL7IDwLeaPAiQBR7BQ==",
|
"integrity": "sha512-HVmqbo9MLfV5tvc5owt7BT+fJKj5QRrz+c4S2vc8WyAsxMEerAYKfaNCHI89LmA5CRb42sgb0Z6YnwlsNVsaiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"arraybuffer-loader": "^1.0.6",
|
"arraybuffer-loader": "^1.0.6",
|
||||||
|
@ -20936,14 +20936,14 @@
|
||||||
"redux": "3.7.2",
|
"redux": "3.7.2",
|
||||||
"redux-throttle": "0.1.1",
|
"redux-throttle": "0.1.1",
|
||||||
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
||||||
"scratch-blocks": "0.1.0-prerelease.20210609211941",
|
"scratch-blocks": "0.1.0-prerelease.20210615035054",
|
||||||
"scratch-l10n": "3.11.20210609031630",
|
"scratch-l10n": "3.12.20210615031544",
|
||||||
"scratch-paint": "0.2.0-prerelease.20210407203313",
|
"scratch-paint": "0.2.0-prerelease.20210615011117",
|
||||||
"scratch-render": "0.1.0-prerelease.20210325231800",
|
"scratch-render": "0.1.0-prerelease.20210325231800",
|
||||||
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
|
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
|
||||||
"scratch-storage": "1.3.5",
|
"scratch-storage": "1.3.5",
|
||||||
"scratch-svg-renderer": "0.2.0-prerelease.20210511195415",
|
"scratch-svg-renderer": "0.2.0-prerelease.20210511195415",
|
||||||
"scratch-vm": "0.2.0-prerelease.20210601191643",
|
"scratch-vm": "0.2.0-prerelease.20210615010833",
|
||||||
"startaudiocontext": "1.2.1",
|
"startaudiocontext": "1.2.1",
|
||||||
"style-loader": "^0.23.0",
|
"style-loader": "^0.23.0",
|
||||||
"text-encoding": "0.7.0",
|
"text-encoding": "0.7.0",
|
||||||
|
@ -21199,9 +21199,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "7.0.35",
|
"version": "7.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
|
||||||
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
|
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
@ -21328,19 +21328,6 @@
|
||||||
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
|
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"scratch-l10n": {
|
|
||||||
"version": "3.11.20210609031630",
|
|
||||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210609031630.tgz",
|
|
||||||
"integrity": "sha512-gqBAjoWNYPm6KY5TlFIdkxnWQO3cjuwO89EQj9DeX16iS9B3n+l8E7NrO0uvZPSs2MTOjGXLkD0sWS8iyI18iA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@babel/cli": "^7.1.2",
|
|
||||||
"@babel/core": "^7.1.2",
|
|
||||||
"babel-plugin-react-intl": "^3.0.1",
|
|
||||||
"react-intl": "^2.8.0",
|
|
||||||
"transifex": "1.6.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scratch-storage": {
|
"scratch-storage": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-1.3.5.tgz",
|
||||||
|
@ -21407,9 +21394,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-l10n": {
|
"scratch-l10n": {
|
||||||
"version": "3.11.20210610031613",
|
"version": "3.12.20210615031544",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210610031613.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.12.20210615031544.tgz",
|
||||||
"integrity": "sha512-1OKFX+E408wGP8KEImdtnHIT2QIMJd7ovqeT3i8CsOIH7+BCYw8lDewqRO5X7XjUWVQUEDi0x8jRmLm7IH/G8g==",
|
"integrity": "sha512-8U2y0wu+xy29ayND5bY4odklo9D/5mVW1XQ+YrBx7rykUPHiJOzPUYHvTUQVXC0CI8khAtW98Lt3SI9m4MxBuw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/cli": "^7.1.2",
|
"@babel/cli": "^7.1.2",
|
||||||
|
@ -21420,9 +21407,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-paint": {
|
"scratch-paint": {
|
||||||
"version": "0.2.0-prerelease.20210407203313",
|
"version": "0.2.0-prerelease.20210615011117",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-0.2.0-prerelease.20210407203313.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-0.2.0-prerelease.20210615011117.tgz",
|
||||||
"integrity": "sha512-dAg+7Bh8X4PxukXnIXN1NVDELSCmPsTRh2a2taM1MGIl9zqZLTo3nxz95qZ2aC6tnVZYY/oJRjl9UAnm47Fe4g==",
|
"integrity": "sha512-rgJwmtKXhbfZLtsR5jurxcFLNCUkNiDHPFYJq2bjA1n0HYYfNoG/r68fe+WfBQ6jppsOqFCNmRaNDqoF1VgsTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@scratch/paper": "0.11.20200728195508",
|
"@scratch/paper": "0.11.20200728195508",
|
||||||
|
@ -21676,9 +21663,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"scratch-vm": {
|
"scratch-vm": {
|
||||||
"version": "0.2.0-prerelease.20210601191643",
|
"version": "0.2.0-prerelease.20210615010833",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20210601191643.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20210615010833.tgz",
|
||||||
"integrity": "sha512-SWXa176Ymo2EER+dEF5yJXGOaq7xekHcmggEJ2p+8vt3LZUlBpmUlL/U1FTY65wjaYLxQWMi7q+d+IpnO/vkEg==",
|
"integrity": "sha512-xft3AjqII4j/mbDNs89/CUyjB82i0r20QW/xiGRKmKwRUVSyccfHwjJ6XGcxseSUkvjRqNtl4PXcSjYVL2vN8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vernier/godirect": "1.5.0",
|
"@vernier/godirect": "1.5.0",
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
||||||
"test:integration": "npm run test:integration:jest && npm run test:smoke",
|
"test:integration": "npm run test:integration:jest && npm run test:smoke",
|
||||||
"test:integration:jest": "jest ./test/integration/*.test.js --reporters=default",
|
"test:integration:jest": "jest ./test/integration/*.test.js --reporters=default --runInBand",
|
||||||
"test:integration:remote": "npm run test:integration:jest:remote && npm run test:smoke:sauce",
|
"test:integration:remote": "npm run test:integration:jest:remote && npm run test:smoke:sauce",
|
||||||
"test:integration:jest:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js --reporters=default",
|
"test:integration:jest:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js --reporters=default --runInBand",
|
||||||
"test:smoke": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
"test:smoke": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||||
"test:smoke:verbose": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
"test:smoke:verbose": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||||
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration-legacy/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration-legacy/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||||
|
@ -126,8 +126,8 @@
|
||||||
"redux-mock-store": "^1.2.3",
|
"redux-mock-store": "^1.2.3",
|
||||||
"redux-thunk": "2.0.1",
|
"redux-thunk": "2.0.1",
|
||||||
"sass-loader": "6.0.6",
|
"sass-loader": "6.0.6",
|
||||||
"scratch-gui": "0.1.0-prerelease.20210610000654",
|
"scratch-gui": "0.1.0-prerelease.20210615041617",
|
||||||
"scratch-l10n": "3.11.20210610031613",
|
"scratch-l10n": "3.12.20210615031544",
|
||||||
"selenium-webdriver": "3.6.0",
|
"selenium-webdriver": "3.6.0",
|
||||||
"slick-carousel": "1.6.0",
|
"slick-carousel": "1.6.0",
|
||||||
"style-loader": "0.12.3",
|
"style-loader": "0.12.3",
|
||||||
|
|
|
@ -145,8 +145,8 @@ const ConferenceFooter = props => (
|
||||||
</div>
|
</div>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
<LanguageChooser locale={props.intl.locale} />
|
<LanguageChooser locale={props.intl.locale} />
|
||||||
<div className="conf2020-organized">
|
<div className="conf2021-organized">
|
||||||
<FormattedMessage id="conference-2020.organizedBy" />
|
<FormattedMessage id="conference-2021.organizedBy" />
|
||||||
</div>
|
</div>
|
||||||
</FooterBox>
|
</FooterBox>
|
||||||
);
|
);
|
|
@ -6,9 +6,9 @@ require('./navigation.scss');
|
||||||
|
|
||||||
const Navigation = () => (
|
const Navigation = () => (
|
||||||
<NavigationBox>
|
<NavigationBox>
|
||||||
<ul className="ul mod-2020">
|
<ul className="ul mod-2021">
|
||||||
<li className="li-left mod-logo mod-2020">
|
<li className="li-left mod-logo mod-2021">
|
||||||
<ul className="li-left-ul mod-2020">
|
<ul className="li-left-ul mod-2021">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
className="logo-a"
|
className="logo-a"
|
|
@ -2,8 +2,8 @@
|
||||||
@import "../../../../frameless";
|
@import "../../../../frameless";
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
.ul.mod-2020,
|
.ul.mod-2021,
|
||||||
.li-left-ul.mod-2020 {
|
.li-left-ul.mod-2021 {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.li-left-ul.mod-2020 {
|
.li-left-ul.mod-2021 {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
@media #{$medium-and-smaller} {
|
@media #{$medium-and-smaller} {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
|
||||||
.ul.mod-2020 {
|
.ul.mod-2021 {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
const Navigation = require('../../../navigation/conference/2020/navigation.jsx');
|
const Navigation = require('../../../navigation/conference/2021/navigation.jsx');
|
||||||
const Footer = require('../../../footer/conference/2020/footer.jsx');
|
const Footer = require('../../../footer/conference/2021/footer.jsx');
|
||||||
|
|
||||||
require('../page.scss');
|
require('../page.scss');
|
||||||
|
|
|
@ -147,6 +147,8 @@ module.exports.selectStudioCommentsGloballyEnabled = state =>
|
||||||
module.exports.selectMuteStatus = state => get(state, ['session', 'session', 'permissions', 'mute_status'],
|
module.exports.selectMuteStatus = state => get(state, ['session', 'session', 'permissions', 'mute_status'],
|
||||||
{muteExpiresAt: 0, offenses: [], showWarning: false});
|
{muteExpiresAt: 0, offenses: [], showWarning: false});
|
||||||
module.exports.selectIsMuted = state => (module.exports.selectMuteStatus(state).muteExpiresAt || 0) * 1000 > Date.now();
|
module.exports.selectIsMuted = state => (module.exports.selectMuteStatus(state).muteExpiresAt || 0) * 1000 > Date.now();
|
||||||
|
module.exports.selectNewStudiosLaunched = state => get(state, ['session', 'session', 'flags', 'new_studios_launched'],
|
||||||
|
false);
|
||||||
|
|
||||||
module.exports.selectHasFetchedSession = state => state.session.status === module.exports.Status.FETCHED;
|
module.exports.selectHasFetchedSession = state => state.session.status === module.exports.Status.FETCHED;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ const Errors = keyMirror({
|
||||||
THUMBNAIL_INVALID: null,
|
THUMBNAIL_INVALID: null,
|
||||||
TEXT_TOO_LONG: null,
|
TEXT_TOO_LONG: null,
|
||||||
REQUIRED_FIELD: null,
|
REQUIRED_FIELD: null,
|
||||||
UNHANDLED: null
|
UNHANDLED: null,
|
||||||
|
USER_MUTED: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const MAX_IMAGE_BYTES = 524288;
|
const MAX_IMAGE_BYTES = 524288;
|
||||||
|
@ -89,9 +90,7 @@ const selectFollowingMutationError = state => state.studioMutations.mutationErro
|
||||||
const selectIsMutatingImage = state => state.studioMutations.isMutating.image;
|
const selectIsMutatingImage = state => state.studioMutations.isMutating.image;
|
||||||
const selectImageMutationError = state => state.studioMutations.mutationErrors.image;
|
const selectImageMutationError = state => state.studioMutations.mutationErrors.image;
|
||||||
const selectIsMutatingOpenToAll = state => state.studioMutations.isMutating.openToAll;
|
const selectIsMutatingOpenToAll = state => state.studioMutations.isMutating.openToAll;
|
||||||
const selectOpenToAllMutationError = state => state.studioMutations.mutationErrors.openToAll;
|
|
||||||
const selectIsMutatingCommentsAllowed = state => state.studioMutations.isMutating.commentsAllowed;
|
const selectIsMutatingCommentsAllowed = state => state.studioMutations.isMutating.commentsAllowed;
|
||||||
const selectCommentsAllowedMutationError = state => state.studioMutations.mutationErrors.commentsAllowed;
|
|
||||||
|
|
||||||
// Thunks
|
// Thunks
|
||||||
/**
|
/**
|
||||||
|
@ -104,6 +103,7 @@ const selectCommentsAllowedMutationError = state => state.studioMutations.mutati
|
||||||
*/
|
*/
|
||||||
const normalizeError = (err, body, res) => {
|
const normalizeError = (err, body, res) => {
|
||||||
if (err) return Errors.NETWORK;
|
if (err) return Errors.NETWORK;
|
||||||
|
if (res.statusCode === 403 && body.mute_status) return Errors.USER_MUTED;
|
||||||
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
||||||
if (res.statusCode !== 200) return Errors.SERVER;
|
if (res.statusCode !== 200) return Errors.SERVER;
|
||||||
try {
|
try {
|
||||||
|
@ -221,6 +221,8 @@ const mutateStudioCommentsAllowed = shouldAllow => ((dispatch, getState) => {
|
||||||
}, (err, body, res) => {
|
}, (err, body, res) => {
|
||||||
const error = normalizeError(err, body, res);
|
const error = normalizeError(err, body, res);
|
||||||
const wasAllowed = selectStudioCommentsAllowed(state);
|
const wasAllowed = selectStudioCommentsAllowed(state);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Error mutating commentsAllowed: ${error}`);
|
||||||
dispatch(completeMutation('commentsAllowed', error ? wasAllowed : shouldAllow, error));
|
dispatch(completeMutation('commentsAllowed', error ? wasAllowed : shouldAllow, error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -237,6 +239,8 @@ const mutateStudioOpenToAll = shouldBeOpen => ((dispatch, getState) => {
|
||||||
}, (err, body, res) => {
|
}, (err, body, res) => {
|
||||||
const error = normalizeError(err, body, res);
|
const error = normalizeError(err, body, res);
|
||||||
const wasOpen = selectStudioOpenToAll(getState());
|
const wasOpen = selectStudioOpenToAll(getState());
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Error mutating openToAll: ${error}`);
|
||||||
dispatch(completeMutation('openToAll', error ? wasOpen : shouldBeOpen, error));
|
dispatch(completeMutation('openToAll', error ? wasOpen : shouldBeOpen, error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -264,7 +268,5 @@ module.exports = {
|
||||||
selectIsMutatingImage,
|
selectIsMutatingImage,
|
||||||
selectImageMutationError,
|
selectImageMutationError,
|
||||||
selectIsMutatingCommentsAllowed,
|
selectIsMutatingCommentsAllowed,
|
||||||
selectCommentsAllowedMutationError,
|
selectIsMutatingOpenToAll
|
||||||
selectIsMutatingOpenToAll,
|
|
||||||
selectOpenToAllMutationError
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,9 @@ const Status = keyMirror({
|
||||||
ERROR: null
|
ERROR: null
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const STUDIO_MANAGER_LIMIT = 40;
|
||||||
|
const STUDIO_MANAGER_THRESHOLD = 30;
|
||||||
|
|
||||||
const getInitialState = () => ({
|
const getInitialState = () => ({
|
||||||
infoStatus: Status.FETCHING,
|
infoStatus: Status.FETCHING,
|
||||||
title: '',
|
title: '',
|
||||||
|
@ -20,6 +23,7 @@ const getInitialState = () => ({
|
||||||
commentsAllowed: false,
|
commentsAllowed: false,
|
||||||
image: '',
|
image: '',
|
||||||
followers: 0,
|
followers: 0,
|
||||||
|
managers: 0,
|
||||||
owner: null,
|
owner: null,
|
||||||
|
|
||||||
// BEWARE: classroomId is only loaded if the user is an educator
|
// BEWARE: classroomId is only loaded if the user is an educator
|
||||||
|
@ -98,6 +102,9 @@ const selectStudioLastUpdated = state => state.studio.updated;
|
||||||
const selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR;
|
const selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR;
|
||||||
const selectStudioCommentCount = state => state.studio.commentCount;
|
const selectStudioCommentCount = state => state.studio.commentCount;
|
||||||
const selectStudioFollowerCount = state => state.studio.followers;
|
const selectStudioFollowerCount = state => state.studio.followers;
|
||||||
|
const selectStudioManagerCount = state => state.studio.managers;
|
||||||
|
const selectStudioHasReachedManagerThreshold = state => state.studio.managers >= STUDIO_MANAGER_THRESHOLD;
|
||||||
|
const selectStudioHasReachedManagerLimit = state => state.studio.managers >= STUDIO_MANAGER_LIMIT;
|
||||||
const selectStudioProjectCount = state => state.studio.projectCount;
|
const selectStudioProjectCount = state => state.studio.projectCount;
|
||||||
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
|
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
|
||||||
const selectIsFollowing = state => state.studio.following;
|
const selectIsFollowing = state => state.studio.following;
|
||||||
|
@ -121,6 +128,7 @@ const getInfo = () => ((dispatch, getState) => {
|
||||||
updated: new Date(body.history.modified),
|
updated: new Date(body.history.modified),
|
||||||
commentCount: body.stats.comments,
|
commentCount: body.stats.comments,
|
||||||
followers: body.stats.followers,
|
followers: body.stats.followers,
|
||||||
|
managers: body.stats.managers,
|
||||||
projectCount: body.stats.projects,
|
projectCount: body.stats.projects,
|
||||||
owner: body.owner
|
owner: body.owner
|
||||||
}));
|
}));
|
||||||
|
@ -169,6 +177,10 @@ module.exports = {
|
||||||
setInfo,
|
setInfo,
|
||||||
setRoles,
|
setRoles,
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
STUDIO_MANAGER_LIMIT,
|
||||||
|
STUDIO_MANAGER_THRESHOLD,
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
selectStudioId,
|
selectStudioId,
|
||||||
selectStudioTitle,
|
selectStudioTitle,
|
||||||
|
@ -180,6 +192,9 @@ module.exports = {
|
||||||
selectStudioLoadFailed,
|
selectStudioLoadFailed,
|
||||||
selectStudioCommentCount,
|
selectStudioCommentCount,
|
||||||
selectStudioFollowerCount,
|
selectStudioFollowerCount,
|
||||||
|
selectStudioManagerCount,
|
||||||
|
selectStudioHasReachedManagerThreshold,
|
||||||
|
selectStudioHasReachedManagerLimit,
|
||||||
selectStudioProjectCount,
|
selectStudioProjectCount,
|
||||||
selectIsFetchingInfo,
|
selectIsFetchingInfo,
|
||||||
selectIsFetchingRoles,
|
selectIsFetchingRoles,
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"name": "conference-index",
|
"name": "conference-index",
|
||||||
"pattern": "^/conference/?(\\?.*)?$",
|
"pattern": "^/conference/?(\\?.*)?$",
|
||||||
"routeAlias": "/conference(?!/201[4-9])",
|
"routeAlias": "/conference(?!/201[4-9])",
|
||||||
"redirect": "/conference/2020"
|
"redirect": "/conference/2021"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "conference-index-2017",
|
"name": "conference-index-2017",
|
||||||
|
@ -83,7 +83,13 @@
|
||||||
"name": "conference-index-2020",
|
"name": "conference-index-2020",
|
||||||
"pattern": "^/conference/2020/?$",
|
"pattern": "^/conference/2020/?$",
|
||||||
"routeAlias": "/conference(?!/201[4-9])",
|
"routeAlias": "/conference(?!/201[4-9])",
|
||||||
"view": "conference/2020/index/index",
|
"redirect": "/conference/2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "conference-index-2021",
|
||||||
|
"pattern": "^/conference/2021/?$",
|
||||||
|
"routeAlias": "/conference(?!/201[4-9])",
|
||||||
|
"view": "conference/2021/index/index",
|
||||||
"title": "Scratch Conferences",
|
"title": "Scratch Conferences",
|
||||||
"viewportWidth": "device-width"
|
"viewportWidth": "device-width"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"conference-2020.title": "Scratch Around the World:",
|
|
||||||
"conference-2020.subtitle": "An Online Conference",
|
|
||||||
"conference-2020.dateDesc": "July 22, 2021",
|
|
||||||
"conference-2020.locationDetails": "Online",
|
|
||||||
|
|
||||||
"conference-2020.date": "When:",
|
|
||||||
"conference-2020.location": "Where:",
|
|
||||||
|
|
||||||
"conference-2020.desc1": "Join us for Scratch Around the World, an online conference for educators interested in creative learning with Scratch.",
|
|
||||||
"conference-2020.desc1a": "Although we are not able to meet in person this year, we are excited to find ways to connect and share with others in the global Scratch educator community.",
|
|
||||||
"conference-2020.desc3": "The conference will be free of charge.",
|
|
||||||
|
|
||||||
"conference-2020.register": "Go to registration page",
|
|
||||||
|
|
||||||
"conference-2020.stayDesc2": "For additional questions, contact the Scratch Conference Team at {emailLink}",
|
|
||||||
"conference-2020.organizedBy": "The Scratch Conference is organized by the Lifelong Kindergarten group at the MIT Media Lab in collaboration with the Scratch Foundation."
|
|
||||||
}
|
|
|
@ -3,47 +3,47 @@ const FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const render = require('../../../../lib/render.jsx');
|
const render = require('../../../../lib/render.jsx');
|
||||||
|
|
||||||
const Page = require('../../../../components/page/conference/2020/page.jsx');
|
const Page = require('../../../../components/page/conference/2021/page.jsx');
|
||||||
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
||||||
|
|
||||||
require('../../../../components/forms/button.scss');
|
require('../../../../components/forms/button.scss');
|
||||||
require('./index.scss');
|
require('./index.scss');
|
||||||
|
|
||||||
const ConferenceSplash = () => (
|
const ConferenceSplash = () => (
|
||||||
<div className="index mod-2020">
|
<div className="index mod-2021">
|
||||||
<TitleBanner className="mod-conference mod-2020">
|
<TitleBanner className="mod-conference mod-2021">
|
||||||
<div className="title-banner-image mod-2020" />
|
<div className="title-banner-image mod-2021" />
|
||||||
<h1 className="title-banner-h1 mod-2020">
|
<h1 className="title-banner-h1 mod-2021">
|
||||||
<center>
|
<center>
|
||||||
<FormattedMessage id="conference-2020.title" />
|
<FormattedMessage id="conference-2021.title" />
|
||||||
<br />
|
<br />
|
||||||
<FormattedMessage id="conference-2020.subtitle" />
|
<FormattedMessage id="conference-2021.subtitle" />
|
||||||
</center>
|
</center>
|
||||||
</h1>
|
</h1>
|
||||||
<h3 className="title-banner-h3 mod-2020">
|
<h3 className="title-banner-h3 mod-2021">
|
||||||
<FormattedMessage id="conference-2020.dateDesc" />
|
<FormattedMessage id="conference-2021.dateDesc" />
|
||||||
</h3>
|
</h3>
|
||||||
</TitleBanner>
|
</TitleBanner>
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<section className="conf2020-panel mod-desc">
|
<section className="conf2021-panel mod-desc">
|
||||||
<p className="conf2020-panel-desc">
|
<p className="conf2021-panel-desc">
|
||||||
<FormattedMessage id="conference-2020.desc1" />{' '}
|
<FormattedMessage id="conference-2021.desc1" />{' '}
|
||||||
<strong><FormattedMessage id="conference-2020.desc1a" /></strong>
|
<strong><FormattedMessage id="conference-2021.desc1a" /></strong>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<FormattedMessage id="conference-2020.desc3" />
|
<FormattedMessage id="conference-2021.desc3" />
|
||||||
</p>
|
</p>
|
||||||
<table className="conf2020-panel-details">
|
<table className="conf2021-panel-details">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr className="conf2020-panel-row">
|
<tr className="conf2021-panel-row">
|
||||||
<td className="conf2020-panel-row-icon">
|
<td className="conf2021-panel-row-icon">
|
||||||
<img
|
<img
|
||||||
alt="Calendar Icon"
|
alt="Calendar Icon"
|
||||||
className="conf2020-panel-row-icon-image"
|
className="conf2021-panel-row-icon-image"
|
||||||
src="/svgs/conference/index/calendar-icon-solid.svg"
|
src="/svgs/conference/index/calendar-icon-solid.svg"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td><FormattedMessage id="conference-2020.date" /></td>
|
<td><FormattedMessage id="conference-2021.date" /></td>
|
||||||
<td>
|
<td>
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
day="2-digit"
|
day="2-digit"
|
||||||
|
@ -53,31 +53,31 @@ const ConferenceSplash = () => (
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="conf2020-panel-row">
|
<tr className="conf2021-panel-row">
|
||||||
<td className="conf2020-panel-row-icon">
|
<td className="conf2021-panel-row-icon">
|
||||||
<img
|
<img
|
||||||
alt="Map Icon"
|
alt="Map Icon"
|
||||||
className="conf2020-panel-row-icon-image"
|
className="conf2021-panel-row-icon-image"
|
||||||
src="/svgs/conference/index/map-icon-solid.svg"
|
src="/svgs/conference/index/map-icon-solid.svg"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td><FormattedMessage id="conference-2020.location" /></td>
|
<td><FormattedMessage id="conference-2021.location" /></td>
|
||||||
<td><FormattedMessage id="conference-2020.locationDetails" /></td>
|
<td><FormattedMessage id="conference-2021.locationDetails" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<a
|
<a
|
||||||
className="button mod-2020-panel"
|
className="button mod-2021-panel"
|
||||||
href="http://scratch2021.eventbrite.com/"
|
href="http://scratch2021.eventbrite.com/"
|
||||||
>
|
>
|
||||||
<FormattedMessage id="conference-2020.register" />
|
<FormattedMessage id="conference-2021.register" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section className="conf2020-panel mod-stay">
|
<section className="conf2021-panel mod-stay">
|
||||||
<p className="conf2020-panel-desc">
|
<p className="conf2021-panel-desc">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="conference-2020.stayDesc2"
|
id="conference-2021.stayDesc2"
|
||||||
values={{
|
values={{
|
||||||
emailLink: <a href="mailto:conference@scratch.mit.edu">
|
emailLink: <a href="mailto:conference@scratch.mit.edu">
|
||||||
conference@scratch.mit.edu
|
conference@scratch.mit.edu
|
||||||
|
@ -86,7 +86,7 @@ const ConferenceSplash = () => (
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<FormattedMessage id="conference-2020.organizedBy" />
|
<FormattedMessage id="conference-2021.organizedBy" />
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
|
@ -1,53 +1,53 @@
|
||||||
@import "../../../../colors";
|
@import "../../../../colors";
|
||||||
@import "../../../../frameless";
|
@import "../../../../frameless";
|
||||||
|
|
||||||
.title-banner.mod-conference.mod-2020 {
|
.title-banner.mod-conference.mod-2021 {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-image.mod-2020 {
|
.title-banner-image.mod-2021 {
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
margin-bottom: 1.75rem;
|
margin-bottom: 1.75rem;
|
||||||
background-image: url("/images/conference/index/2020/title-banner.jpg");
|
background-image: url("/images/conference/index/2021/title-banner.jpg");
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20rem;
|
height: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h1.mod-2020 {
|
.title-banner-h1.mod-2021 {
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel,
|
.conf2021-panel,
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
width: 48.75rem;
|
width: 48.75rem;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
margin: 2rem auto 0;
|
margin: 2rem auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $type-white;
|
color: $type-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel {
|
.conf2021-panel {
|
||||||
border-bottom: 1px solid $ui-border;
|
border-bottom: 1px solid $ui-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel.mod-last {
|
.conf2021-panel.mod-last {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-row.conf2020-panel-title {
|
.flex-row.conf2021-panel-title {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel-desc {
|
.conf2021-panel-desc {
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +60,14 @@ td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel-row-icon-image {
|
.conf2021-panel-row-icon-image {
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
.mod-registration .conf2020-panel-desc {
|
.mod-registration .conf2021-panel-desc {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.button.mod-2020-panel {
|
.button.mod-2021-panel {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 2rem auto 0;
|
margin: 2rem auto 0;
|
||||||
background-color: $ui-orange;
|
background-color: $ui-orange;
|
||||||
|
@ -76,7 +76,7 @@ td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $type-white;
|
color: $type-white;
|
||||||
}
|
}
|
||||||
.conf2020-organized {
|
.conf2021-organized {
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -85,93 +85,93 @@ td {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $mobile - 1) {
|
@media only screen and (max-width: $mobile - 1) {
|
||||||
.index.mod-2020 {
|
.index.mod-2021 {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-image.mod-2020 {
|
.title-banner-image.mod-2021 {
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel,
|
.conf2021-panel,
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
width: initial;
|
width: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel {
|
.conf2021-panel {
|
||||||
margin: auto .5rem;
|
margin: auto .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
margin: 1rem .5rem .5rem;
|
margin: 1rem .5rem .5rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-row.conf2020-panel-title {
|
.flex-row.conf2021-panel-title {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel-title-text {
|
.conf2021-panel-title-text {
|
||||||
max-width: 14rem;
|
max-width: 14rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel-row > td {
|
.conf2021-panel-row > td {
|
||||||
padding: .75rem .375rem .75rem 0;
|
padding: .75rem .375rem .75rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: $mobile) and (max-width: $tabletPortrait - 1) {
|
@media only screen and (min-width: $mobile) and (max-width: $tabletPortrait - 1) {
|
||||||
.index.mod-2020 {
|
.index.mod-2021 {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-image.mod-2020 {
|
.title-banner-image.mod-2021 {
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel,
|
.conf2021-panel,
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
margin: auto .5rem ;
|
margin: auto .5rem ;
|
||||||
width: initial;
|
width: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-row.conf2020-panel-title {
|
.flex-row.conf2021-panel-title {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel-title-text {
|
.conf2021-panel-title-text {
|
||||||
max-width: 18.75rem;
|
max-width: 18.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.mod-2020-panel {
|
.button.mod-2021-panel {
|
||||||
width: 5.75rem;
|
width: 5.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: $tabletPortrait) and (max-width: $desktop - 1) {
|
@media only screen and (min-width: $tabletPortrait) and (max-width: $desktop - 1) {
|
||||||
.index.mod-2020 {
|
.index.mod-2021 {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-image.mod-2020 {
|
.title-banner-image.mod-2021 {
|
||||||
height: 15rem;
|
height: 15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conf2020-panel,
|
.conf2021-panel,
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 38.75rem;
|
width: 38.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-banner-h3.mod-2020 {
|
.title-banner-h3.mod-2021 {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.mod-2020-panel {
|
.button.mod-2021-panel {
|
||||||
width: 8.75rem;
|
width: 8.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
18
src/views/conference/2021/index/l10n.json
Normal file
18
src/views/conference/2021/index/l10n.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"conference-2021.title": "Scratch Around the World:",
|
||||||
|
"conference-2021.subtitle": "An Online Conference",
|
||||||
|
"conference-2021.dateDesc": "July 22, 2021",
|
||||||
|
"conference-2021.locationDetails": "Online",
|
||||||
|
|
||||||
|
"conference-2021.date": "When:",
|
||||||
|
"conference-2021.location": "Where:",
|
||||||
|
|
||||||
|
"conference-2021.desc1": "Join us for Scratch Around the World, an online conference for educators interested in creative learning with Scratch.",
|
||||||
|
"conference-2021.desc1a": "Although we are not able to meet in person this year, we are excited to find ways to connect and share with others in the global Scratch educator community.",
|
||||||
|
"conference-2021.desc3": "The conference will be free of charge.",
|
||||||
|
|
||||||
|
"conference-2021.register": "Go to registration page",
|
||||||
|
|
||||||
|
"conference-2021.stayDesc2": "For additional questions, contact the Scratch Conference Team at {emailLink}",
|
||||||
|
"conference-2021.organizedBy": "The Scratch Conference is organized by the Lifelong Kindergarten group at the MIT Media Lab in collaboration with the Scratch Foundation."
|
||||||
|
}
|
|
@ -46,5 +46,6 @@
|
||||||
"project.cloudVariables": "Cloud Variables",
|
"project.cloudVariables": "Cloud Variables",
|
||||||
"project.cloudDataLink": "See Data",
|
"project.cloudDataLink": "See Data",
|
||||||
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.",
|
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.",
|
||||||
"project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful."
|
"project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful.",
|
||||||
|
"project.mutedAddToStudio": "You will be able to add to studios again {inDuration}."
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,6 @@ $stage-width: 480px;
|
||||||
margin-top: $arrow-border-width;
|
margin-top: $arrow-border-width;
|
||||||
border: 1px solid $active-gray;
|
border: 1px solid $active-gray;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: $ui-orange;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
max-width: 18.75rem;
|
max-width: 18.75rem;
|
||||||
min-height: 1rem;
|
min-height: 1rem;
|
||||||
|
@ -185,7 +184,6 @@ $stage-width: 480px;
|
||||||
border-left: 1px solid $active-gray;
|
border-left: 1px solid $active-gray;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
background-color: $ui-orange;
|
|
||||||
width: $arrow-border-width;
|
width: $arrow-border-width;
|
||||||
height: $arrow-border-width;
|
height: $arrow-border-width;
|
||||||
|
|
||||||
|
|
|
@ -8,90 +8,133 @@ const Button = require('../../components/forms/button.jsx');
|
||||||
const AddToStudioModal = require('./add-to-studio.jsx');
|
const AddToStudioModal = require('./add-to-studio.jsx');
|
||||||
const SocialModal = require('../../components/modal/social/container.jsx');
|
const SocialModal = require('../../components/modal/social/container.jsx');
|
||||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||||
|
const {connect} = require('react-redux');
|
||||||
|
const {selectShowProjectMuteError} = require('../../redux/studio-permissions.js');
|
||||||
|
const {useState} = require('react');
|
||||||
const projectShape = require('./projectshape.jsx').projectShape;
|
const projectShape = require('./projectshape.jsx').projectShape;
|
||||||
|
|
||||||
|
import {selectNewStudiosLaunched} from '../../redux/session.js';
|
||||||
|
import StudioMuteEditMessage from '../studio/studio-mute-edit-message.jsx';
|
||||||
|
|
||||||
require('./subactions.scss');
|
require('./subactions.scss');
|
||||||
|
|
||||||
const Subactions = props => (
|
const Subactions = ({
|
||||||
<FlexRow className="subactions">
|
addToStudioOpen,
|
||||||
<div className="share-date">
|
canAddToStudio,
|
||||||
<div className="copyleft">©</div>
|
canReport,
|
||||||
{' '}
|
isAdmin,
|
||||||
{/* eslint-disable react/jsx-sort-props */}
|
isShared,
|
||||||
{props.shareDate ? (
|
onAddToStudioClicked,
|
||||||
<FormattedDate
|
onAddToStudioClosed,
|
||||||
value={Date.parse(props.shareDate)}
|
onReportClicked,
|
||||||
day="2-digit"
|
onReportClose,
|
||||||
month="short"
|
onReportSubmit,
|
||||||
year="numeric"
|
onSocialClicked,
|
||||||
/>
|
onSocialClosed,
|
||||||
) : 'Unshared'}
|
onToggleStudio,
|
||||||
{/* eslint-enable react/jsx-sort-props */}
|
projectInfo,
|
||||||
</div>
|
reportOpen,
|
||||||
<FlexRow className="action-buttons">
|
shareDate,
|
||||||
{(props.canReport) &&
|
showAddToStudioMuteError,
|
||||||
<React.Fragment>
|
socialOpen,
|
||||||
<Button
|
userOwnsProject
|
||||||
className="action-button report-button"
|
}) => {
|
||||||
key="report-button"
|
const [showMuteMessage, setShowMuteMessage] = useState(false);
|
||||||
onClick={props.onReportClicked}
|
|
||||||
>
|
return (
|
||||||
<FormattedMessage id="general.report" />
|
<FlexRow className="subactions">
|
||||||
</Button>
|
<div className="share-date">
|
||||||
{props.reportOpen && (
|
<div className="copyleft">©</div>
|
||||||
<ReportModal
|
{' '}
|
||||||
isOpen
|
{/* eslint-disable react/jsx-sort-props */}
|
||||||
key="report-modal"
|
{shareDate ? (
|
||||||
type="project"
|
<FormattedDate
|
||||||
onReport={props.onReportSubmit}
|
value={Date.parse(shareDate)}
|
||||||
onRequestClose={props.onReportClose}
|
day="2-digit"
|
||||||
/>
|
month="short"
|
||||||
)}
|
year="numeric"
|
||||||
</React.Fragment>
|
/>
|
||||||
}
|
) : 'Unshared'}
|
||||||
{props.canAddToStudio &&
|
{/* eslint-enable react/jsx-sort-props */}
|
||||||
<React.Fragment>
|
</div>
|
||||||
<Button
|
<FlexRow className="action-buttons">
|
||||||
className="action-button studio-button"
|
{(canReport) &&
|
||||||
key="add-to-studio-button"
|
<React.Fragment>
|
||||||
onClick={props.onAddToStudioClicked}
|
<Button
|
||||||
>
|
className="action-button report-button"
|
||||||
<FormattedMessage id="addToStudio.title" />
|
key="report-button"
|
||||||
</Button>
|
onClick={onReportClicked}
|
||||||
{props.addToStudioOpen && (
|
>
|
||||||
<AddToStudioModal
|
<FormattedMessage id="general.report" />
|
||||||
isOpen
|
</Button>
|
||||||
isAdmin={props.isAdmin}
|
{reportOpen && (
|
||||||
key="add-to-studio-modal"
|
<ReportModal
|
||||||
userOwnsProject={props.userOwnsProject}
|
isOpen
|
||||||
onRequestClose={props.onAddToStudioClosed}
|
key="report-modal"
|
||||||
onToggleStudio={props.onToggleStudio}
|
type="project"
|
||||||
/>
|
onReport={onReportSubmit}
|
||||||
)}
|
onRequestClose={onReportClose}
|
||||||
</React.Fragment>
|
/>
|
||||||
}
|
)}
|
||||||
{/* only show copy link button, modal if project is shared */}
|
</React.Fragment>
|
||||||
{props.isShared && props.projectInfo && props.projectInfo.id && (
|
}
|
||||||
<React.Fragment>
|
{canAddToStudio &&
|
||||||
<Button
|
<React.Fragment>
|
||||||
className="action-button copy-link-button"
|
<div
|
||||||
onClick={props.onSocialClicked}
|
style={{position: 'relative'}}
|
||||||
>
|
/* eslint-disable react/jsx-no-bind */
|
||||||
<FormattedMessage id="general.copyLink" />
|
onMouseEnter={() => showAddToStudioMuteError && setShowMuteMessage(true)}
|
||||||
</Button>
|
onMouseLeave={() => showAddToStudioMuteError && setShowMuteMessage(false)}
|
||||||
{props.socialOpen && (
|
/* eslint-enable react/jsx-no-bind */
|
||||||
<SocialModal
|
>
|
||||||
isOpen
|
<Button
|
||||||
key="social-modal"
|
className="action-button studio-button"
|
||||||
projectId={props.projectInfo && props.projectInfo.id}
|
disabled={showAddToStudioMuteError}
|
||||||
onRequestClose={props.onSocialClosed}
|
key="add-to-studio-button"
|
||||||
/>
|
onClick={showMuteMessage ? null : onAddToStudioClicked}
|
||||||
)}
|
>
|
||||||
</React.Fragment>
|
<FormattedMessage id="addToStudio.title" />
|
||||||
)}
|
</Button>
|
||||||
|
{showMuteMessage && <StudioMuteEditMessage
|
||||||
|
className="studio-button-error"
|
||||||
|
messageId="project.mutedAddToStudio"
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
{addToStudioOpen && (
|
||||||
|
<AddToStudioModal
|
||||||
|
isOpen
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
key="add-to-studio-modal"
|
||||||
|
userOwnsProject={userOwnsProject}
|
||||||
|
onRequestClose={onAddToStudioClosed}
|
||||||
|
onToggleStudio={onToggleStudio}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
{/* only show copy link button, modal if project is shared */}
|
||||||
|
{isShared && projectInfo && projectInfo.id && (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button
|
||||||
|
className="action-button copy-link-button"
|
||||||
|
onClick={onSocialClicked}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="general.copyLink" />
|
||||||
|
</Button>
|
||||||
|
{socialOpen && (
|
||||||
|
<SocialModal
|
||||||
|
isOpen
|
||||||
|
key="social-modal"
|
||||||
|
projectId={projectInfo && projectInfo.id}
|
||||||
|
onRequestClose={onSocialClosed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</FlexRow>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</FlexRow>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
Subactions.propTypes = {
|
Subactions.propTypes = {
|
||||||
addToStudioOpen: PropTypes.bool,
|
addToStudioOpen: PropTypes.bool,
|
||||||
|
@ -110,8 +153,13 @@ Subactions.propTypes = {
|
||||||
projectInfo: projectShape,
|
projectInfo: projectShape,
|
||||||
reportOpen: PropTypes.bool,
|
reportOpen: PropTypes.bool,
|
||||||
shareDate: PropTypes.string,
|
shareDate: PropTypes.string,
|
||||||
|
showAddToStudioMuteError: PropTypes.bool,
|
||||||
socialOpen: PropTypes.bool,
|
socialOpen: PropTypes.bool,
|
||||||
userOwnsProject: PropTypes.bool
|
userOwnsProject: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Subactions;
|
module.exports = connect(
|
||||||
|
state => ({
|
||||||
|
showAddToStudioMuteError: selectShowProjectMuteError(state) && selectNewStudiosLaunched(state)
|
||||||
|
})
|
||||||
|
)(Subactions);
|
||||||
|
|
|
@ -109,3 +109,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-button-error {
|
||||||
|
top: auto;
|
||||||
|
transform: none;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,11 @@
|
||||||
|
|
||||||
"studio.openToAll": "Anyone can add projects",
|
"studio.openToAll": "Anyone can add projects",
|
||||||
|
|
||||||
|
"studio.addProjects.noSharedYet": "You don’t have shared projects that you can add to this studio yet.",
|
||||||
|
"studio.addProjects.noFavoritedYet": "You don’t have favorite projects that you can add to this studio yet.",
|
||||||
|
"studio.addProjects.noRecentYet": "You don’t have recently viewed projects that you can add to this studio yet.",
|
||||||
|
"studio.addProjects.noStudentsYet": "You don’t have student projects that you can add to this studio yet.",
|
||||||
|
|
||||||
"studio.projectsEmptyCanAdd1": "Your studio is looking a little empty.",
|
"studio.projectsEmptyCanAdd1": "Your studio is looking a little empty.",
|
||||||
"studio.projectsEmptyCanAdd2": "Add your first project!",
|
"studio.projectsEmptyCanAdd2": "Add your first project!",
|
||||||
"studio.projectsEmpty1": "This studio has no projects yet.",
|
"studio.projectsEmpty1": "This studio has no projects yet.",
|
||||||
|
@ -65,9 +70,18 @@
|
||||||
"studio.curatorAddAndDeleteProjects": "add and delete projects",
|
"studio.curatorAddAndDeleteProjects": "add and delete projects",
|
||||||
"studio.curatorIfYouTrust": "If you trust this person and you’re sure you want to give them extra permissions, click Promote.",
|
"studio.curatorIfYouTrust": "If you trust this person and you’re sure you want to give them extra permissions, click Promote.",
|
||||||
|
|
||||||
|
"studio.managerLimitReachedHeader": "This studio has reached the limit of {managerLimit} managers.",
|
||||||
|
"studio.managerLimitMessageCollaborative": "It’s great to see that this studio is collaborative!",
|
||||||
|
"studio.managerLimitMessageRemoveManagers": "Before you can add another manager, you will need to remove an existing manager.",
|
||||||
|
|
||||||
|
"studio.managerCountInfo": "{numberOfManagers} of {managerLimit}",
|
||||||
|
"studio.managerThresholdInfo": "This studio has {numberOfManagers} managers. Studios can have a maximum of {managerLimit} managers.",
|
||||||
|
"studio.managerThresholdRemoveManagers": "Before you can add another manager, you will need to remove managers until there are fewer than {managerLimit}.",
|
||||||
|
|
||||||
"studio.remove": "Remove",
|
"studio.remove": "Remove",
|
||||||
"studio.promote": "Promote",
|
"studio.promote": "Promote",
|
||||||
"studio.cancel": "Cancel",
|
"studio.cancel": "Cancel",
|
||||||
|
"studio.okay": "Okay",
|
||||||
|
|
||||||
"studio.commentsHeader": "Comments",
|
"studio.commentsHeader": "Comments",
|
||||||
"studio.commentsNotAllowed": "Commenting for this studio has been turned off.",
|
"studio.commentsNotAllowed": "Commenting for this studio has been turned off.",
|
||||||
|
@ -98,10 +112,11 @@
|
||||||
"studio.reportThanksForLettingUsKnow": "Thanks for letting us know!",
|
"studio.reportThanksForLettingUsKnow": "Thanks for letting us know!",
|
||||||
"studio.reportYourFeedback": "Your feedback will help us make Scratch better.",
|
"studio.reportYourFeedback": "Your feedback will help us make Scratch better.",
|
||||||
|
|
||||||
"studios.mutedCurators": "You will be able to invite curators and add managers again {inDuration}.",
|
"studio.mutedCurators": "You will be able to invite curators and add managers again {inDuration}.",
|
||||||
"studios.mutedProjects": "You will be able to add and remove projects again {inDuration}.",
|
"studio.mutedProjects": "You will be able to add and remove projects again {inDuration}.",
|
||||||
"studios.mutedEdit": "You will be able to edit studios again {inDuration}.",
|
"studio.mutedEdit": "You will be able to edit studios again {inDuration}.",
|
||||||
"studios.mutedPaused": "Your account has been paused from using studios until then.",
|
"studio.mutedPaused": "Your account has been paused from using studios until then.",
|
||||||
|
"studio.mutedError": "Your account has been paused from using studios. Refresh for more information.",
|
||||||
|
|
||||||
"studio.alertProjectAdded": "\"{title}\" added to studio",
|
"studio.alertProjectAdded": "\"{title}\" added to studio",
|
||||||
"studio.alertProjectAlreadyAdded": "That project is already in this studio",
|
"studio.alertProjectAlreadyAdded": "That project is already in this studio",
|
||||||
|
|
|
@ -3,19 +3,25 @@ import keyMirror from 'keymirror';
|
||||||
import api from '../../../lib/api';
|
import api from '../../../lib/api';
|
||||||
import {curators, managers} from './redux-modules';
|
import {curators, managers} from './redux-modules';
|
||||||
import {selectUsername} from '../../../redux/session';
|
import {selectUsername} from '../../../redux/session';
|
||||||
import {selectStudioId, setRoles} from '../../../redux/studio';
|
import {selectStudioId, setRoles, setInfo} from '../../../redux/studio';
|
||||||
|
|
||||||
const Errors = keyMirror({
|
const Errors = keyMirror({
|
||||||
NETWORK: null,
|
NETWORK: null,
|
||||||
SERVER: null,
|
SERVER: null,
|
||||||
PERMISSION: null,
|
PERMISSION: null,
|
||||||
DUPLICATE: null,
|
DUPLICATE: null,
|
||||||
|
USER_MUTED: null,
|
||||||
UNKNOWN_USERNAME: null,
|
UNKNOWN_USERNAME: null,
|
||||||
RATE_LIMIT: null
|
RATE_LIMIT: null,
|
||||||
|
MANAGER_LIMIT: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeError = (err, body, res) => {
|
const normalizeError = (err, body, res) => {
|
||||||
if (err) return Errors.NETWORK;
|
if (err) return Errors.NETWORK;
|
||||||
|
if (res.statusCode === 400 && body.message === 'too many owners') {
|
||||||
|
return Errors.MANAGER_LIMIT;
|
||||||
|
}
|
||||||
|
if (res.statusCode === 403 && body.mute_status) return Errors.USER_MUTED;
|
||||||
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
||||||
if (res.statusCode === 404) return Errors.UNKNOWN_USERNAME;
|
if (res.statusCode === 404) return Errors.UNKNOWN_USERNAME;
|
||||||
if (res.statusCode === 429) return Errors.RATE_LIMIT;
|
if (res.statusCode === 429) return Errors.RATE_LIMIT;
|
||||||
|
@ -81,6 +87,7 @@ const removeManager = username => ((dispatch, getState) => new Promise((resolve,
|
||||||
if (selectUsername(state) === username) {
|
if (selectUsername(state) === username) {
|
||||||
dispatch(setRoles({manager: false}));
|
dispatch(setRoles({manager: false}));
|
||||||
}
|
}
|
||||||
|
dispatch(setInfo({managers: state.studio.managers - 1}));
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -143,6 +150,7 @@ const promoteCurator = username => ((dispatch, getState) => new Promise((resolve
|
||||||
const curatorItem = curatorList[index];
|
const curatorItem = curatorList[index];
|
||||||
if (index !== -1) dispatch(curators.actions.remove(index));
|
if (index !== -1) dispatch(curators.actions.remove(index));
|
||||||
dispatch(managers.actions.create(curatorItem, true));
|
dispatch(managers.actions.create(curatorItem, true));
|
||||||
|
dispatch(setInfo({managers: state.studio.managers + 1}));
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -12,11 +12,13 @@ const Errors = keyMirror({
|
||||||
PERMISSION: null,
|
PERMISSION: null,
|
||||||
UNKNOWN_PROJECT: null,
|
UNKNOWN_PROJECT: null,
|
||||||
RATE_LIMIT: null,
|
RATE_LIMIT: null,
|
||||||
DUPLICATE: null
|
DUPLICATE: null,
|
||||||
|
USER_MUTED: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeError = (err, body, res) => {
|
const normalizeError = (err, body, res) => {
|
||||||
if (err) return Errors.NETWORK;
|
if (err) return Errors.NETWORK;
|
||||||
|
if (res.statusCode === 403 && body.mute_status) return Errors.USER_MUTED;
|
||||||
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
||||||
if (res.statusCode === 404) return Errors.UNKNOWN_PROJECT;
|
if (res.statusCode === 404) return Errors.UNKNOWN_PROJECT;
|
||||||
if (res.statusCode === 409) return Errors.DUPLICATE;
|
if (res.statusCode === 409) return Errors.DUPLICATE;
|
||||||
|
|
63
src/views/studio/modals/manager-limit-modal.jsx
Normal file
63
src/views/studio/modals/manager-limit-modal.jsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import Modal from '../../../components/modal/base/modal.jsx';
|
||||||
|
import ModalTitle from '../../../components/modal/base/modal-title.jsx';
|
||||||
|
import ModalInnerContent from '../../../components/modal/base/modal-inner-content.jsx';
|
||||||
|
|
||||||
|
import './manager-limit-modal.scss';
|
||||||
|
import {STUDIO_MANAGER_LIMIT} from '../../../redux/studio.js';
|
||||||
|
|
||||||
|
|
||||||
|
const ManagerLimitModal = ({
|
||||||
|
handleClose
|
||||||
|
}) => (
|
||||||
|
<Modal
|
||||||
|
isOpen
|
||||||
|
className="manager-limit-modal"
|
||||||
|
onRequestClose={handleClose}
|
||||||
|
>
|
||||||
|
<ModalTitle
|
||||||
|
className="manager-limit-title"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="manager-limit-content"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/svgs/studio/manager-limit-illustration.svg"
|
||||||
|
className="manager-limit-image"
|
||||||
|
/>
|
||||||
|
<ModalInnerContent
|
||||||
|
className="manager-limit-inner"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="studio.managerLimitReachedHeader"
|
||||||
|
values={{
|
||||||
|
managerLimit: STUDIO_MANAGER_LIMIT
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
<p><FormattedMessage id="studio.managerLimitMessageCollaborative" /></p>
|
||||||
|
<p><FormattedMessage id="studio.managerLimitMessageRemoveManagers" /></p>
|
||||||
|
<div
|
||||||
|
className="manager-limit-button-row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="studio.okay" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalInnerContent>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
ManagerLimitModal.propTypes = {
|
||||||
|
handleClose: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManagerLimitModal;
|
44
src/views/studio/modals/manager-limit-modal.scss
Normal file
44
src/views/studio/modals/manager-limit-modal.scss
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
@import "../../../colors";
|
||||||
|
|
||||||
|
.manager-limit-modal {
|
||||||
|
width: 520px;
|
||||||
|
|
||||||
|
.manager-limit-title {
|
||||||
|
background: $ui-blue;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
padding-top: .75rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
line-height: 2rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-limit-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-limit-image {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-limit-inner {
|
||||||
|
padding: 2.5rem 1.5rem 1.5rem 2.5rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-limit-button-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {FormattedMessage} from 'react-intl';
|
||||||
import Modal from '../../../components/modal/base/modal.jsx';
|
import Modal from '../../../components/modal/base/modal.jsx';
|
||||||
import ModalTitle from '../../../components/modal/base/modal-title.jsx';
|
import ModalTitle from '../../../components/modal/base/modal-title.jsx';
|
||||||
import ModalInnerContent from '../../../components/modal/base/modal-inner-content.jsx';
|
import ModalInnerContent from '../../../components/modal/base/modal-inner-content.jsx';
|
||||||
|
import Alert from '../../../components/alert/alert.jsx';
|
||||||
|
|
||||||
import './promote-modal.scss';
|
import './promote-modal.scss';
|
||||||
|
|
||||||
|
@ -43,24 +44,27 @@ const PromoteModal = ({
|
||||||
<li><FormattedMessage id="studio.curatorAddAndDeleteProjects" /></li>
|
<li><FormattedMessage id="studio.curatorAddAndDeleteProjects" /></li>
|
||||||
</ul>
|
</ul>
|
||||||
<span><FormattedMessage id="studio.curatorIfYouTrust" /></span>
|
<span><FormattedMessage id="studio.curatorIfYouTrust" /></span>
|
||||||
<div
|
|
||||||
className="promote-button-row"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="button cancel-button"
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="studio.cancel" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="button"
|
|
||||||
onClick={handlePromote}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="studio.promote" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ModalInnerContent>
|
</ModalInnerContent>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="promote-alert-and-button-row">
|
||||||
|
<Alert className="studio-alert promote-alert" />
|
||||||
|
<div
|
||||||
|
className="promote-button-row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="button cancel-button"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="studio.cancel" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
onClick={handlePromote}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="studio.promote" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,24 @@
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.promote-alert-and-button-row {
|
||||||
|
padding: 0 1.5rem 1.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override alert-wrapper positioning for this modal
|
||||||
|
.alert-wrapper {
|
||||||
|
position: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promote-alert {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.promote-button-row {
|
.promote-button-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
padding-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
|
@ -75,9 +75,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-report-tile-image {
|
.studio-report-tile-image {
|
||||||
border-radius: 0.5rem;
|
width: 150px;
|
||||||
max-width: 130px;
|
height: 98px;
|
||||||
max-height: 100px;
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-report-selected {
|
.studio-report-selected {
|
||||||
|
|
|
@ -77,33 +77,61 @@ const UserProjectsModal = ({
|
||||||
<AlertProvider>
|
<AlertProvider>
|
||||||
{error && <div>Error loading {filter}: {error}</div>}
|
{error && <div>Error loading {filter}: {error}</div>}
|
||||||
<Alert className="studio-alert" />
|
<Alert className="studio-alert" />
|
||||||
<div className="user-projects-modal-grid">
|
{items.length > 0 ? (
|
||||||
{items.map(project => (
|
<React.Fragment>
|
||||||
<UserProjectsTile
|
<div className="user-projects-modal-grid">
|
||||||
key={project.id}
|
{items.map(project => (
|
||||||
id={project.id}
|
<UserProjectsTile
|
||||||
title={project.title}
|
key={project.id}
|
||||||
image={project.image}
|
id={project.id}
|
||||||
inStudio={project.inStudio}
|
title={project.title}
|
||||||
onAdd={onAdd}
|
image={project.image}
|
||||||
onRemove={onRemove}
|
inStudio={project.inStudio}
|
||||||
|
onAdd={onAdd}
|
||||||
|
onRemove={onRemove}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{moreToLoad &&
|
||||||
|
<div className="studio-projects-load-more">
|
||||||
|
<button
|
||||||
|
className={classNames('button', {
|
||||||
|
'mod-mutating': loading
|
||||||
|
})}
|
||||||
|
onClick={() => onLoadMore(filter)}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="general.loadMore" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
) :
|
||||||
|
<div className="studio-projects-empty">
|
||||||
|
<img
|
||||||
|
src="/svgs/studio/add-to-studio-empty.svg"
|
||||||
/>
|
/>
|
||||||
))}
|
<div className="studio-projects-empty-text">
|
||||||
</div>
|
{filter === Filters.SHARED &&
|
||||||
{moreToLoad &&
|
<FormattedMessage id="studio.addProjects.noSharedYet" />}
|
||||||
<div className="studio-projects-load-more">
|
{filter === Filters.FAVORITED &&
|
||||||
<button
|
<FormattedMessage id="studio.addProjects.noFavoritedYet" />}
|
||||||
className={classNames('button', {
|
{filter === Filters.RECENT &&
|
||||||
'mod-mutating': loading
|
<FormattedMessage id="studio.addProjects.noRecentYet" />}
|
||||||
})}
|
{filter === Filters.STUDENTS &&
|
||||||
onClick={() => onLoadMore(filter)}
|
<FormattedMessage id="studio.addProjects.noStudentsYet" />}
|
||||||
>
|
</div>
|
||||||
<FormattedMessage id="general.loadMore" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</AlertProvider>
|
</AlertProvider>
|
||||||
</ModalInnerContent>
|
</ModalInnerContent>
|
||||||
|
<div className="studio-projects-done-row">
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
onClick={onRequestClose}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="general.done" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,19 +22,39 @@
|
||||||
.user-projects-modal-content {
|
.user-projects-modal-content {
|
||||||
padding: 0 30px 30px;
|
padding: 0 30px 30px;
|
||||||
background: #E9F1FC;
|
background: #E9F1FC;
|
||||||
max-height: calc(100vh - 200px);
|
max-height: calc(100vh - 270px);
|
||||||
|
min-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
border-bottom-left-radius: 12px;
|
|
||||||
border-bottom-right-radius: 12px;
|
|
||||||
@media #{$intermediate-and-smaller} {
|
@media #{$intermediate-and-smaller} {
|
||||||
& { max-height: calc(100vh - 105px); }
|
& { max-height: calc(100vh - 175px); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-projects-load-more {
|
.studio-projects-load-more {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-projects-done-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-projects-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.studio-projects-empty-text {
|
||||||
|
color: hsla(215, 100, 65, .75);
|
||||||
|
width: 325px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-tile-added {
|
.studio-tile-added {
|
||||||
|
|
|
@ -7,13 +7,13 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import {selectStudioCommentsAllowed, selectIsFetchingInfo} from '../../redux/studio';
|
import {selectStudioCommentsAllowed, selectIsFetchingInfo} from '../../redux/studio';
|
||||||
import {
|
import {
|
||||||
mutateStudioCommentsAllowed, selectIsMutatingCommentsAllowed, selectCommentsAllowedMutationError
|
mutateStudioCommentsAllowed, selectIsMutatingCommentsAllowed
|
||||||
} from '../../redux/studio-mutations';
|
} from '../../redux/studio-mutations';
|
||||||
|
|
||||||
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
||||||
|
|
||||||
const StudioCommentsAllowed = ({
|
const StudioCommentsAllowed = ({
|
||||||
commentsAllowedError, isFetching, isMutating, commentsAllowed, handleUpdate
|
isFetching, isMutating, commentsAllowed, handleUpdate
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
|
@ -33,14 +33,12 @@ const StudioCommentsAllowed = ({
|
||||||
})}
|
})}
|
||||||
onChange={e => handleUpdate(e.target.checked)}
|
onChange={e => handleUpdate(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
{commentsAllowedError && <div>Error mutating commentsAllowed: {commentsAllowedError}</div>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
StudioCommentsAllowed.propTypes = {
|
StudioCommentsAllowed.propTypes = {
|
||||||
commentsAllowedError: PropTypes.string,
|
|
||||||
isFetching: PropTypes.bool,
|
isFetching: PropTypes.bool,
|
||||||
isMutating: PropTypes.bool,
|
isMutating: PropTypes.bool,
|
||||||
commentsAllowed: PropTypes.bool,
|
commentsAllowed: PropTypes.bool,
|
||||||
|
@ -51,8 +49,7 @@ export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
commentsAllowed: selectStudioCommentsAllowed(state),
|
commentsAllowed: selectStudioCommentsAllowed(state),
|
||||||
isFetching: selectIsFetchingInfo(state),
|
isFetching: selectIsFetchingInfo(state),
|
||||||
isMutating: selectIsMutatingCommentsAllowed(state),
|
isMutating: selectIsMutatingCommentsAllowed(state)
|
||||||
commentsAllowedError: selectCommentsAllowedMutationError(state)
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
handleUpdate: mutateStudioCommentsAllowed
|
handleUpdate: mutateStudioCommentsAllowed
|
||||||
|
|
|
@ -64,7 +64,7 @@ const StudioCuratorInvite = ({showCuratorInvite, onSubmit}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
StudioCuratorInvite.propTypes = {
|
StudioCuratorInvite.propTypes = {
|
||||||
showCuratorInvite: PropTypes.func,
|
showCuratorInvite: PropTypes.bool,
|
||||||
onSubmit: PropTypes.func
|
onSubmit: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ const errorToMessageId = error => {
|
||||||
case Errors.PERMISSION: return 'studio.curatorErrors.generic';
|
case Errors.PERMISSION: return 'studio.curatorErrors.generic';
|
||||||
case Errors.DUPLICATE: return 'studio.curatorErrors.alreadyCurator';
|
case Errors.DUPLICATE: return 'studio.curatorErrors.alreadyCurator';
|
||||||
case Errors.UNKNOWN_USERNAME: return 'studio.curatorErrors.unknownUsername';
|
case Errors.UNKNOWN_USERNAME: return 'studio.curatorErrors.unknownUsername';
|
||||||
|
case Errors.USER_MUTED: return 'studio.mutedError';
|
||||||
case Errors.RATE_LIMIT: return 'studio.curatorErrors.tooFast';
|
case Errors.RATE_LIMIT: return 'studio.curatorErrors.tooFast';
|
||||||
default: return 'studio.curatorErrors.generic';
|
default: return 'studio.curatorErrors.generic';
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ const errorToMessageId = error => {
|
||||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||||
|
case Errors.USER_MUTED: return 'studio.mutedError';
|
||||||
default: return 'studio.updateErrors.generic';
|
default: return 'studio.updateErrors.generic';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@ const errorToMessageId = error => {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case Errors.THUMBNAIL_INVALID: return 'studio.updateErrors.thumbnailInvalid';
|
case Errors.THUMBNAIL_INVALID: return 'studio.updateErrors.thumbnailInvalid';
|
||||||
case Errors.THUMBNAIL_TOO_LARGE: return 'studio.updateErrors.thumbnailTooLarge';
|
case Errors.THUMBNAIL_TOO_LARGE: return 'studio.updateErrors.thumbnailTooLarge';
|
||||||
|
case Errors.USER_MUTED: return 'studio.mutedError';
|
||||||
default: return 'studio.updateErrors.generic';
|
default: return 'studio.updateErrors.generic';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
26
src/views/studio/studio-info-box.jsx
Normal file
26
src/views/studio/studio-info-box.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Button from '../../components/forms/button.jsx';
|
||||||
|
|
||||||
|
const StudioInfoBox = ({showInfoBox, onClose, ...props}) => {
|
||||||
|
if (!showInfoBox) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="studio-invitation studio-info-box"> {/* TODO move more styling into studio-info-box? */}
|
||||||
|
{props.children}
|
||||||
|
<Button
|
||||||
|
className="studio-info-close-button"
|
||||||
|
isCloseType
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StudioInfoBox.propTypes = {
|
||||||
|
showInfoBox: PropTypes.bool,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudioInfoBox;
|
|
@ -1,28 +1,89 @@
|
||||||
import React, {useEffect} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import {managers} from './lib/redux-modules';
|
import {managers} from './lib/redux-modules';
|
||||||
|
import {
|
||||||
|
STUDIO_MANAGER_LIMIT,
|
||||||
|
selectStudioManagerCount,
|
||||||
|
selectStudioHasReachedManagerLimit,
|
||||||
|
selectStudioHasReachedManagerThreshold
|
||||||
|
} from '../../redux/studio.js';
|
||||||
import {loadManagers} from './lib/studio-member-actions';
|
import {loadManagers} from './lib/studio-member-actions';
|
||||||
import Debug from './debug.jsx';
|
import Debug from './debug.jsx';
|
||||||
import {ManagerTile} from './studio-member-tile.jsx';
|
import {ManagerTile} from './studio-member-tile.jsx';
|
||||||
|
import StudioInfoBox from './studio-info-box.jsx';
|
||||||
import AlertProvider from '../../components/alert/alert-provider.jsx';
|
import AlertProvider from '../../components/alert/alert-provider.jsx';
|
||||||
import Alert from '../../components/alert/alert.jsx';
|
import Alert from '../../components/alert/alert.jsx';
|
||||||
|
import {
|
||||||
|
selectCanRemoveManager, selectCanPromoteCurators
|
||||||
|
} from '../../redux/studio-permissions';
|
||||||
|
|
||||||
|
|
||||||
const StudioManagers = ({items, error, loading, moreToLoad, onLoadMore}) => {
|
const StudioManagers = ({
|
||||||
|
canPromoteCurators,
|
||||||
|
canRemoveManagers,
|
||||||
|
managerCount,
|
||||||
|
hasReachedManagerLimit,
|
||||||
|
hasReachedManagerThreshold,
|
||||||
|
items,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
moreToLoad,
|
||||||
|
onLoadMore
|
||||||
|
}) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (items.length === 0) onLoadMore();
|
if (items.length === 0) onLoadMore();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [infoBoxDismissed, setInfoBoxDismissed] = useState(false);
|
||||||
|
|
||||||
|
const showManagerLimitInfoBox =
|
||||||
|
!infoBoxDismissed && canPromoteCurators &&
|
||||||
|
canRemoveManagers && hasReachedManagerLimit;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertProvider>
|
<AlertProvider>
|
||||||
<div className="studio-members">
|
<div className="studio-members">
|
||||||
<Alert className="studio-alert" />
|
<Alert className="studio-alert" />
|
||||||
<div className="studio-header-container">
|
<StudioInfoBox
|
||||||
|
showInfoBox={showManagerLimitInfoBox}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClose={() => setInfoBoxDismissed(true)}
|
||||||
|
>
|
||||||
|
<div className="manager-threshold-message">
|
||||||
|
<span className="manager-threshold-info">
|
||||||
|
<FormattedMessage
|
||||||
|
id="studio.managerThresholdInfo"
|
||||||
|
values={{
|
||||||
|
numberOfManagers: managerCount,
|
||||||
|
managerLimit: STUDIO_MANAGER_LIMIT
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<FormattedMessage
|
||||||
|
id="studio.managerThresholdRemoveManagers"
|
||||||
|
values={{
|
||||||
|
managerLimit: STUDIO_MANAGER_LIMIT
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StudioInfoBox>
|
||||||
|
<div className="studio-header-container studio-managers-header">
|
||||||
<h2><FormattedMessage id="studio.managersHeader" /></h2>
|
<h2><FormattedMessage id="studio.managersHeader" /></h2>
|
||||||
|
{canPromoteCurators && canRemoveManagers && hasReachedManagerThreshold &&
|
||||||
|
<div className="studio-manager-count">
|
||||||
|
<FormattedMessage
|
||||||
|
id="studio.managerCountInfo"
|
||||||
|
values={{
|
||||||
|
numberOfManagers: managerCount,
|
||||||
|
managerLimit: STUDIO_MANAGER_LIMIT
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{error && <Debug
|
{error && <Debug
|
||||||
label="Error"
|
label="Error"
|
||||||
|
@ -56,6 +117,11 @@ const StudioManagers = ({items, error, loading, moreToLoad, onLoadMore}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
StudioManagers.propTypes = {
|
StudioManagers.propTypes = {
|
||||||
|
canPromoteCurators: PropTypes.bool,
|
||||||
|
canRemoveManagers: PropTypes.bool,
|
||||||
|
managerCount: PropTypes.number,
|
||||||
|
hasReachedManagerLimit: PropTypes.bool,
|
||||||
|
hasReachedManagerThreshold: PropTypes.bool,
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
items: PropTypes.arrayOf(PropTypes.shape({
|
||||||
id: PropTypes.id,
|
id: PropTypes.id,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
|
@ -72,7 +138,14 @@ StudioManagers.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => managers.selector(state),
|
state => ({
|
||||||
|
canPromoteCurators: selectCanPromoteCurators(state),
|
||||||
|
canRemoveManagers: selectCanRemoveManager(state),
|
||||||
|
managerCount: selectStudioManagerCount(state),
|
||||||
|
hasReachedManagerLimit: selectStudioHasReachedManagerLimit(state),
|
||||||
|
hasReachedManagerThreshold: selectStudioHasReachedManagerThreshold(state),
|
||||||
|
...managers.selector(state)
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
onLoadMore: loadManagers
|
onLoadMore: loadManagers
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,19 @@ import classNames from 'classnames';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import PromoteModal from './modals/promote-modal.jsx';
|
import PromoteModal from './modals/promote-modal.jsx';
|
||||||
|
import ManagerLimitModal from './modals/manager-limit-modal.jsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectCanRemoveCurator, selectCanRemoveManager, selectCanPromoteCurators
|
selectCanRemoveCurator, selectCanRemoveManager, selectCanPromoteCurators
|
||||||
} from '../../redux/studio-permissions';
|
} from '../../redux/studio-permissions';
|
||||||
import {
|
import {
|
||||||
|
Errors,
|
||||||
promoteCurator,
|
promoteCurator,
|
||||||
removeCurator,
|
removeCurator,
|
||||||
removeManager
|
removeManager
|
||||||
} from './lib/studio-member-actions';
|
} from './lib/studio-member-actions';
|
||||||
|
|
||||||
|
import {selectStudioHasReachedManagerLimit} from '../../redux/studio';
|
||||||
import {useAlertContext} from '../../components/alert/alert-context';
|
import {useAlertContext} from '../../components/alert/alert-context';
|
||||||
|
|
||||||
import OverflowMenu from '../../components/overflow-menu/overflow-menu.jsx';
|
import OverflowMenu from '../../components/overflow-menu/overflow-menu.jsx';
|
||||||
|
@ -22,11 +26,12 @@ import removeIcon from './icons/remove-icon.svg';
|
||||||
import promoteIcon from './icons/curator-icon.svg';
|
import promoteIcon from './icons/curator-icon.svg';
|
||||||
|
|
||||||
const StudioMemberTile = ({
|
const StudioMemberTile = ({
|
||||||
canRemove, canPromote, onRemove, onPromote, isCreator, // mapState props
|
canRemove, canPromote, onRemove, onPromote, isCreator, hasReachedManagerLimit, // mapState props
|
||||||
username, image // own props
|
username, image // own props
|
||||||
}) => {
|
}) => {
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [managerLimitReached, setManagerLimitReached] = useState(false);
|
||||||
const {errorAlert, successAlert} = useAlertContext();
|
const {errorAlert, successAlert} = useAlertContext();
|
||||||
const userUrl = `/users/${username}`;
|
const userUrl = `/users/${username}`;
|
||||||
return (
|
return (
|
||||||
|
@ -80,25 +85,35 @@ const StudioMemberTile = ({
|
||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
}
|
}
|
||||||
{modalOpen &&
|
{modalOpen &&
|
||||||
<PromoteModal
|
((hasReachedManagerLimit || managerLimitReached) ?
|
||||||
handleClose={() => setModalOpen(false)}
|
<ManagerLimitModal
|
||||||
handlePromote={() => {
|
handleClose={() => setModalOpen(false)}
|
||||||
onPromote(username)
|
/> :
|
||||||
.then(() => {
|
<PromoteModal
|
||||||
successAlert({
|
handleClose={() => setModalOpen(false)}
|
||||||
id: 'studio.alertManagerPromote',
|
handlePromote={() => {
|
||||||
values: {name: username}
|
onPromote(username)
|
||||||
|
.then(() => {
|
||||||
|
successAlert({
|
||||||
|
id: 'studio.alertManagerPromote',
|
||||||
|
values: {name: username}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error === Errors.MANAGER_LIMIT) {
|
||||||
|
setManagerLimitReached(true);
|
||||||
|
setModalOpen(true);
|
||||||
|
} else {
|
||||||
|
errorAlert({
|
||||||
|
id: 'studio.alertManagerPromoteError',
|
||||||
|
values: {name: username}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
}}
|
||||||
.catch(() => {
|
username={username}
|
||||||
errorAlert({
|
/>
|
||||||
id: 'studio.alertManagerPromoteError',
|
)
|
||||||
values: {name: username}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
username={username}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -111,7 +126,8 @@ StudioMemberTile.propTypes = {
|
||||||
onPromote: PropTypes.func,
|
onPromote: PropTypes.func,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
image: PropTypes.string,
|
image: PropTypes.string,
|
||||||
isCreator: PropTypes.bool
|
isCreator: PropTypes.bool,
|
||||||
|
hasReachedManagerLimit: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
const ManagerTile = connect(
|
const ManagerTile = connect(
|
||||||
|
@ -128,7 +144,8 @@ const ManagerTile = connect(
|
||||||
const CuratorTile = connect(
|
const CuratorTile = connect(
|
||||||
(state, ownProps) => ({
|
(state, ownProps) => ({
|
||||||
canRemove: selectCanRemoveCurator(state, ownProps.username),
|
canRemove: selectCanRemoveCurator(state, ownProps.username),
|
||||||
canPromote: selectCanPromoteCurators(state)
|
canPromote: selectCanPromoteCurators(state),
|
||||||
|
hasReachedManagerLimit: selectStudioHasReachedManagerLimit(state)
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
onRemove: removeCurator,
|
onRemove: removeCurator,
|
||||||
|
|
|
@ -9,12 +9,15 @@ import {selectMuteStatus} from '../../redux/session';
|
||||||
import {formatRelativeTime} from '../../lib/format-time.js';
|
import {formatRelativeTime} from '../../lib/format-time.js';
|
||||||
|
|
||||||
const StudioMuteEditMessage = ({
|
const StudioMuteEditMessage = ({
|
||||||
|
className,
|
||||||
|
messageId,
|
||||||
muteExpiresAtMs
|
muteExpiresAtMs
|
||||||
}) => (
|
}) => (
|
||||||
<ValidationMessage
|
<ValidationMessage
|
||||||
|
className={className}
|
||||||
mode="info"
|
mode="info"
|
||||||
message={<FormattedMessage
|
message={<FormattedMessage
|
||||||
id="studios.mutedEdit"
|
id={messageId}
|
||||||
values={{
|
values={{
|
||||||
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
||||||
}}
|
}}
|
||||||
|
@ -24,9 +27,15 @@ const StudioMuteEditMessage = ({
|
||||||
|
|
||||||
|
|
||||||
StudioMuteEditMessage.propTypes = {
|
StudioMuteEditMessage.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
messageId: PropTypes.string,
|
||||||
muteExpiresAtMs: PropTypes.number
|
muteExpiresAtMs: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StudioMuteEditMessage.defaultProps = {
|
||||||
|
messageId: 'studio.mutedEdit'
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
|
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
|
||||||
|
|
|
@ -7,13 +7,13 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import {selectStudioOpenToAll, selectIsFetchingInfo} from '../../redux/studio';
|
import {selectStudioOpenToAll, selectIsFetchingInfo} from '../../redux/studio';
|
||||||
import {
|
import {
|
||||||
mutateStudioOpenToAll, selectIsMutatingOpenToAll, selectOpenToAllMutationError
|
mutateStudioOpenToAll, selectIsMutatingOpenToAll
|
||||||
} from '../../redux/studio-mutations';
|
} from '../../redux/studio-mutations';
|
||||||
|
|
||||||
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
||||||
|
|
||||||
const StudioOpenToAll = ({
|
const StudioOpenToAll = ({
|
||||||
openToAllError, isFetching, isMutating, openToAll, handleUpdate
|
isFetching, isMutating, openToAll, handleUpdate
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
|
@ -29,14 +29,12 @@ const StudioOpenToAll = ({
|
||||||
})}
|
})}
|
||||||
onChange={e => handleUpdate(e.target.checked)}
|
onChange={e => handleUpdate(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
{openToAllError && <div>Error mutating openToAll: {openToAllError}</div>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
StudioOpenToAll.propTypes = {
|
StudioOpenToAll.propTypes = {
|
||||||
openToAllError: PropTypes.string,
|
|
||||||
isFetching: PropTypes.bool,
|
isFetching: PropTypes.bool,
|
||||||
isMutating: PropTypes.bool,
|
isMutating: PropTypes.bool,
|
||||||
openToAll: PropTypes.bool,
|
openToAll: PropTypes.bool,
|
||||||
|
@ -47,8 +45,7 @@ export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
openToAll: selectStudioOpenToAll(state),
|
openToAll: selectStudioOpenToAll(state),
|
||||||
isFetching: selectIsFetchingInfo(state),
|
isFetching: selectIsFetchingInfo(state),
|
||||||
isMutating: selectIsMutatingOpenToAll(state),
|
isMutating: selectIsMutatingOpenToAll(state)
|
||||||
openToAllError: selectOpenToAllMutationError(state)
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
handleUpdate: mutateStudioOpenToAll
|
handleUpdate: mutateStudioOpenToAll
|
||||||
|
|
|
@ -18,6 +18,7 @@ const errorToMessageId = error => {
|
||||||
case Errors.DUPLICATE: return 'studio.projectErrors.duplicate';
|
case Errors.DUPLICATE: return 'studio.projectErrors.duplicate';
|
||||||
case Errors.RATE_LIMIT: return 'studio.projectErrors.tooFast';
|
case Errors.RATE_LIMIT: return 'studio.projectErrors.tooFast';
|
||||||
case Errors.UNKNOWN_PROJECT: return 'studio.projectErrors.checkUrl';
|
case Errors.UNKNOWN_PROJECT: return 'studio.projectErrors.checkUrl';
|
||||||
|
case Errors.USER_MUTED: return 'studio.mutedError';
|
||||||
default: return 'studio.projectErrors.generic';
|
default: return 'studio.projectErrors.generic';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,13 +38,13 @@ const StudioProjects = ({
|
||||||
<p>
|
<p>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="studios.mutedProjects"
|
id="studio.mutedProjects"
|
||||||
values={{
|
values={{
|
||||||
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div><FormattedMessage id="studios.mutedPaused" /></div>
|
<div><FormattedMessage id="studio.mutedPaused" /></div>
|
||||||
</p>
|
</p>
|
||||||
</CommentingStatus>
|
</CommentingStatus>
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ const StudioTabNav = ({isFetchingInfo, commentCount, projectCount}) => {
|
||||||
>
|
>
|
||||||
<NavLink
|
<NavLink
|
||||||
activeClassName="active"
|
activeClassName="active"
|
||||||
|
className="nav_link"
|
||||||
to={base}
|
to={base}
|
||||||
exact
|
exact
|
||||||
>
|
>
|
||||||
|
@ -67,6 +68,7 @@ const StudioTabNav = ({isFetchingInfo, commentCount, projectCount}) => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
activeClassName="active"
|
activeClassName="active"
|
||||||
|
className="nav_link"
|
||||||
to={`${base}/comments`}
|
to={`${base}/comments`}
|
||||||
>
|
>
|
||||||
<li><img
|
<li><img
|
||||||
|
@ -84,6 +86,7 @@ const StudioTabNav = ({isFetchingInfo, commentCount, projectCount}) => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
activeClassName="active"
|
activeClassName="active"
|
||||||
|
className="nav_link"
|
||||||
to={`${base}/curators`}
|
to={`${base}/curators`}
|
||||||
>
|
>
|
||||||
<li><img
|
<li><img
|
||||||
|
@ -92,6 +95,7 @@ const StudioTabNav = ({isFetchingInfo, commentCount, projectCount}) => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
activeClassName="active"
|
activeClassName="active"
|
||||||
|
className="nav_link"
|
||||||
to={`${base}/activity`}
|
to={`${base}/activity`}
|
||||||
>
|
>
|
||||||
<li><img
|
<li><img
|
||||||
|
|
|
@ -17,6 +17,7 @@ const errorToMessageId = error => {
|
||||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||||
|
case Errors.USER_MUTED: return 'studio.mutedError';
|
||||||
default: return 'studio.updateErrors.generic';
|
default: return 'studio.updateErrors.generic';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,7 +48,6 @@ import {selectShowCuratorMuteError} from '../../redux/studio-permissions.js';
|
||||||
|
|
||||||
const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed}) => {
|
const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed}) => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
studioLoadFailed ?
|
studioLoadFailed ?
|
||||||
<NotAvailable /> :
|
<NotAvailable /> :
|
||||||
|
@ -68,13 +67,13 @@ const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed})
|
||||||
<p>
|
<p>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="studios.mutedCurators"
|
id="studio.mutedCurators"
|
||||||
values={{
|
values={{
|
||||||
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div><FormattedMessage id="studios.mutedPaused" /></div>
|
<div><FormattedMessage id="studio.mutedPaused" /></div>
|
||||||
</p>
|
</p>
|
||||||
</CommentingStatus>
|
</CommentingStatus>
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,9 @@ $radius: 8px;
|
||||||
.studio-title {
|
.studio-title {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-height: 8rem;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-description {
|
.studio-description {
|
||||||
|
@ -147,9 +150,11 @@ $radius: 8px;
|
||||||
|
|
||||||
.studio-image {
|
.studio-image {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 225px;
|
height: 195px;
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-follow-button {
|
.studio-follow-button {
|
||||||
|
@ -167,26 +172,65 @@ $radius: 8px;
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(0, 0, 0, 0.15);
|
background: white;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
color: #575e75;
|
||||||
padding: 0.5em 0.75em 0.5em 0.5em;
|
padding: 0.5em 0.75em 0.5em 0.5em;
|
||||||
&:active {
|
&:active {
|
||||||
padding: calc(0.5em + 1px) calc(0.75em + 1px) calc(0.5em + 1px) calc(0.5em + 1px);
|
padding: calc(0.5em) calc(0.75em) calc(0.5em) calc(0.5em);
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
|
filter: invert(0.55);
|
||||||
}
|
}
|
||||||
.tab-count {
|
.tab-count {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.active > li { background: $ui-blue; }
|
.active > li {
|
||||||
|
background: $ui-blue;
|
||||||
|
color: white;
|
||||||
|
img {
|
||||||
|
filter: invert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.nav_link:hover > li {
|
||||||
|
background: $ui-blue-25percent;
|
||||||
|
border: 1px solid $ui-blue-10percent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-projects, .studio-members {
|
.studio-projects, .studio-members {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-manager-count {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
width: 4.875rem;
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
border: 1px solid $ui-blue;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
color: $ui-blue;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-threshold-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.manager-threshold-info {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.studio-projects-grid {
|
.studio-projects-grid {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -425,6 +469,10 @@ $radius: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-managers-header {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.studio-compose-container {
|
.studio-compose-container {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
@ -463,10 +511,6 @@ $radius: 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: 85px; /* So the box doesn't change height after being accepted */
|
min-height: 85px; /* So the box doesn't change height after being accepted */
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media #{$intermediate-and-smaller} {
|
@media #{$intermediate-and-smaller} {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.studio-invitation-msg {
|
.studio-invitation-msg {
|
||||||
|
@ -477,6 +521,10 @@ $radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-info-box {
|
.studio-info-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: $ui-blue-10percent;
|
background: $ui-blue-10percent;
|
||||||
border: 1px solid $ui-blue-25percent;
|
border: 1px solid $ui-blue-25percent;
|
||||||
|
@ -489,6 +537,10 @@ $radius: 8px;
|
||||||
background: #FFF0DF;
|
background: #FFF0DF;
|
||||||
border: 1px solid $ui-dark-orange;
|
border: 1px solid $ui-dark-orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio-info-close-button {
|
||||||
|
position: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-thumb-edit-button {
|
.studio-thumb-edit-button {
|
||||||
|
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
46
static/svgs/studio/add-to-studio-empty.svg
Normal file
46
static/svgs/studio/add-to-studio-empty.svg
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<svg width="108" height="100" viewBox="0 0 108 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d)">
|
||||||
|
<rect x="4" y="4" width="61.0169" height="53.6164" rx="8" fill="#E5F0FF"/>
|
||||||
|
<rect x="6" y="6" width="57.0169" height="49.6164" rx="6" stroke="#4D97FF" stroke-opacity="0.4" stroke-width="4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M4 12C4 7.58172 7.58172 4 12 4H57.0169C61.4352 4 65.0169 7.58172 65.0169 12V38.5912H4V12Z" fill="#4D97FF" fill-opacity="0.4"/>
|
||||||
|
<g filter="url(#filter1_d)">
|
||||||
|
<rect x="23.4912" y="23.0249" width="61.0169" height="53.6164" rx="8" fill="#E5F0FF"/>
|
||||||
|
<rect x="25.4912" y="25.0249" width="57.0169" height="49.6164" rx="6" stroke="#4D97FF" stroke-opacity="0.4" stroke-width="4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M23.4912 31.0249C23.4912 26.6066 27.0729 23.0249 31.4912 23.0249H76.5082C80.9264 23.0249 84.5082 26.6066 84.5082 31.0249V57.6161H23.4912V31.0249Z" fill="#4D97FF" fill-opacity="0.4"/>
|
||||||
|
<g filter="url(#filter2_d)">
|
||||||
|
<rect x="42.9834" y="42.0505" width="61.0169" height="53.6164" rx="8" fill="#E5F0FF"/>
|
||||||
|
<rect x="44.9834" y="44.0505" width="57.0169" height="49.6164" rx="6" stroke="#4D97FF" stroke-opacity="0.4" stroke-width="4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M42.9834 50.0505C42.9834 45.6323 46.5651 42.0505 50.9834 42.0505H96.0003C100.419 42.0505 104 45.6323 104 50.0505V76.6417H42.9834V50.0505Z" fill="#4D97FF" fill-opacity="0.4"/>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d" x="0" y="0" width="69.0169" height="61.6164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feMorphology radius="4" operator="dilate" in="SourceAlpha" result="effect1_dropShadow"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.92549 0 0 0 0 0.956863 0 0 0 0 1 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter1_d" x="19.4912" y="19.0249" width="69.0169" height="61.6164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feMorphology radius="4" operator="dilate" in="SourceAlpha" result="effect1_dropShadow"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.92549 0 0 0 0 0.956863 0 0 0 0 1 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter2_d" x="38.9834" y="38.0505" width="69.0169" height="61.6164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feMorphology radius="4" operator="dilate" in="SourceAlpha" result="effect1_dropShadow"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.92549 0 0 0 0 0.956863 0 0 0 0 1 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
13
static/svgs/studio/manager-limit-illustration.svg
Normal file
13
static/svgs/studio/manager-limit-illustration.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<svg width="147" height="177" viewBox="0 0 147 177" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="147" height="177">
|
||||||
|
<rect width="147" height="177" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0)">
|
||||||
|
<path opacity="0.3" fill-rule="evenodd" clip-rule="evenodd" d="M33.9398 7.55682C-35.3557 20.2186 -47.9335 60.1599 -31.6109 105.026C-13.5055 154.792 26.3575 162 56.0398 162C112.261 162 147.912 115.074 144.813 75.0489C140.463 18.8722 97.1725 -8.51898 33.9398 7.55682Z" fill="#4C97FF"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M50.3484 85.0694C47.5164 82.1959 45.7738 78.2819 45.7738 73.9683C45.7738 65.1492 53.0579 58 62.0434 58C71.0288 58 78.3129 65.1492 78.3129 73.9683C78.3129 78.188 76.6453 82.0255 73.9215 84.8804C78.2202 87.4007 81.5395 91.7172 83.3202 98.3465C86.5106 110.224 86.6615 119 62.1493 119C35.4939 119 36.8136 112.094 39.1314 100.445C40.534 93.3955 44.8485 88.1292 50.3484 85.0694Z" fill="#4C97FF"/>
|
||||||
|
<path d="M88.5631 103.728C90.6639 104.007 93.3097 104.149 96.625 104.149C112.679 104.149 113.209 99.3602 111.12 91.9985C109.895 87.6813 107.34 84.537 104.136 82.6747C106.136 80.7799 107.379 78.1256 107.379 75.1875C107.379 69.4306 102.607 64.7637 96.7188 64.7637C90.831 64.7637 86.0581 69.4306 86.0581 75.1875C86.0581 78.1305 87.3055 80.7887 89.3115 82.6842C86.8981 84.0728 84.8312 86.1225 83.4243 88.6337C85.0253 91.094 86.2901 93.984 87.1832 97.3088C87.7501 99.4192 88.2693 101.593 88.5631 103.728Z" fill="#4C97FF"/>
|
||||||
|
<path d="M39.0426 90.2273C37.599 86.7062 35.2335 84.0812 32.3783 82.4219C34.4866 80.4247 35.7971 77.6269 35.7971 74.53C35.7971 68.4619 30.7662 63.5427 24.5602 63.5427C18.3542 63.5427 13.3232 68.4619 13.3232 74.53C13.3232 77.6321 14.6381 80.434 16.7526 82.4319C12.8602 84.6715 9.82293 88.5425 8.82444 93.2917L8.75077 93.6414C7.19305 101.03 6.34382 105.057 24.4614 105.057C28.7439 105.057 31.9784 104.734 34.3854 104.125C34.6067 102.687 34.8955 101.236 35.175 99.8319L35.2083 99.6644C35.9121 96.1269 37.2449 92.9667 39.0426 90.2273Z" fill="#4C97FF"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M97.1271 28.5975L97.4373 33.4696L93.592 36.5021C92.281 37.534 92.721 39.6134 94.3354 40.022L99.0767 41.2173L100.764 45.7967C101.339 47.3557 103.458 47.5748 104.35 46.1676L106.968 42.0329L111.858 41.8327C113.524 41.7635 114.392 39.8194 113.331 38.5415L110.208 34.7909L111.542 30.0879C111.996 28.4847 110.416 27.064 108.866 27.6834L104.316 29.498L100.253 26.7926C98.8684 25.8726 97.0229 26.9382 97.1271 28.5975Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M136.101 12.2555L133.655 12.9905L131.662 11.3929C130.983 10.848 129.975 11.3182 129.955 12.1881L129.9 14.7425L127.763 16.1439C127.036 16.621 127.172 17.7255 127.993 18.0124L130.405 18.855L131.077 21.3198C131.306 22.1593 132.398 22.371 132.925 21.6794L134.472 19.6457L137.023 19.7675C137.893 19.809 138.433 18.8362 137.936 18.1212L136.481 16.0206L137.385 13.6326C137.693 12.8188 136.934 12.0059 136.101 12.2555Z" fill="#4C97FF" fill-opacity="0.5"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -139,7 +139,7 @@ describe('www-integration footer links', () => {
|
||||||
await clickText('Scratch Conference');
|
await clickText('Scratch Conference');
|
||||||
let url = await driver.getCurrentUrl();
|
let url = await driver.getCurrentUrl();
|
||||||
let pathname = (new URL(url)).pathname;
|
let pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/conference\/2020\/?$/);
|
expect(pathname).toMatch(/^\/conference\/2021\/?$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue