mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-30 10:58:23 -05:00
Merge remote-tracking branch 'origin/develop' into release/2021-06-23
This commit is contained in:
commit
af02cb0736
49 changed files with 1386 additions and 553 deletions
141
package-lock.json
generated
141
package-lock.json
generated
|
@ -61,20 +61,20 @@
|
|||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
|
||||
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
|
@ -176,9 +176,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -219,9 +219,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz",
|
||||
"integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz",
|
||||
"integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
|
@ -308,9 +308,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -325,9 +325,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz",
|
||||
"integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
|
||||
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
|
@ -335,7 +335,7 @@
|
|||
"@babel/helper-function-name": "^7.14.5",
|
||||
"@babel/helper-hoist-variables": "^7.14.5",
|
||||
"@babel/helper-split-export-declaration": "^7.14.5",
|
||||
"@babel/parser": "^7.14.5",
|
||||
"@babel/parser": "^7.14.7",
|
||||
"@babel/types": "^7.14.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
|
@ -372,9 +372,9 @@
|
|||
}
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
|
||||
"integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
|
||||
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.1"
|
||||
|
@ -504,9 +504,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.752",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz",
|
||||
"integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==",
|
||||
"version": "1.3.755",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.755.tgz",
|
||||
"integrity": "sha512-BJ1s/kuUuOeo1bF/EM2E4yqW9te0Hpof3wgwBx40AWJE18zsD1Tqo0kr7ijnOc+lRsrlrqKPauJAHqaxOItoUA==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
|
@ -565,9 +565,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz",
|
||||
"integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz",
|
||||
"integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.5"
|
||||
|
@ -695,9 +695,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -712,9 +712,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz",
|
||||
"integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
|
||||
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
|
@ -722,7 +722,7 @@
|
|||
"@babel/helper-function-name": "^7.14.5",
|
||||
"@babel/helper-hoist-variables": "^7.14.5",
|
||||
"@babel/helper-split-export-declaration": "^7.14.5",
|
||||
"@babel/parser": "^7.14.5",
|
||||
"@babel/parser": "^7.14.7",
|
||||
"@babel/types": "^7.14.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
|
@ -914,9 +914,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -931,9 +931,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz",
|
||||
"integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
|
||||
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
|
@ -941,7 +941,7 @@
|
|||
"@babel/helper-function-name": "^7.14.5",
|
||||
"@babel/helper-hoist-variables": "^7.14.5",
|
||||
"@babel/helper-split-export-declaration": "^7.14.5",
|
||||
"@babel/parser": "^7.14.5",
|
||||
"@babel/parser": "^7.14.7",
|
||||
"@babel/types": "^7.14.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
|
@ -1147,9 +1147,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz",
|
||||
"integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -1164,9 +1164,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz",
|
||||
"integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==",
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
|
||||
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
|
@ -1174,7 +1174,7 @@
|
|||
"@babel/helper-function-name": "^7.14.5",
|
||||
"@babel/helper-hoist-variables": "^7.14.5",
|
||||
"@babel/helper-split-export-declaration": "^7.14.5",
|
||||
"@babel/parser": "^7.14.5",
|
||||
"@babel/parser": "^7.14.7",
|
||||
"@babel/types": "^7.14.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
|
@ -7613,6 +7613,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react-hooks": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
|
||||
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
|
||||
|
@ -20873,9 +20879,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-blocks": {
|
||||
"version": "0.1.0-prerelease.20210615035054",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210615035054.tgz",
|
||||
"integrity": "sha512-/YGZN3QiMa41gtcyHUUUVTfhztoT7zEb0+N+FeBmtbkZfeESOtEugJ0y0ftttUY7WJRga35TTDp1UdEyo7sxAg==",
|
||||
"version": "0.1.0-prerelease.20210620032544",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210620032544.tgz",
|
||||
"integrity": "sha512-lGid69E7uvW9XZyx1u7skwzBSD54AmFlG9Rvf1JxGxyuggt5LsIVLlyrnDUSLLDFfrpcnWgYUgICdx7OPbGIaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exports-loader": "0.6.3",
|
||||
|
@ -20883,9 +20889,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-gui": {
|
||||
"version": "0.1.0-prerelease.20210615041617",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210615041617.tgz",
|
||||
"integrity": "sha512-HVmqbo9MLfV5tvc5owt7BT+fJKj5QRrz+c4S2vc8WyAsxMEerAYKfaNCHI89LmA5CRb42sgb0Z6YnwlsNVsaiw==",
|
||||
"version": "0.1.0-prerelease.20210621040041",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210621040041.tgz",
|
||||
"integrity": "sha512-UtAPMLhTQWYLtIgHEVegOzQuHr4A3KqTWj31IGt5taaZxxpT8TsSC0A1stas/rqQB9/gXalKyX3LylogesfEnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arraybuffer-loader": "^1.0.6",
|
||||
|
@ -20936,8 +20942,8 @@
|
|||
"redux": "3.7.2",
|
||||
"redux-throttle": "0.1.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210615035054",
|
||||
"scratch-l10n": "3.12.20210615031544",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210620032544",
|
||||
"scratch-l10n": "3.13.20210621031558",
|
||||
"scratch-paint": "0.2.0-prerelease.20210615011117",
|
||||
"scratch-render": "0.1.0-prerelease.20210325231800",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
|
||||
|
@ -21328,6 +21334,18 @@
|
|||
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
|
||||
"dev": true
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.13.20210621031558",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.13.20210621031558.tgz",
|
||||
"integrity": "sha512-tZfvJkxiIfwMd996qfmZ8/m05gIEuVUx5UAs0QXAPfqNwyBYIHUaJBihTxN1K4SWffL4Z80iUnLJZiQGwMqtyw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
"@babel/core": "^7.1.2",
|
||||
"babel-plugin-react-intl": "^3.0.1",
|
||||
"transifex": "1.6.6"
|
||||
}
|
||||
},
|
||||
"scratch-storage": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-1.3.5.tgz",
|
||||
|
@ -21394,15 +21412,14 @@
|
|||
}
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.12.20210615031544",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.12.20210615031544.tgz",
|
||||
"integrity": "sha512-8U2y0wu+xy29ayND5bY4odklo9D/5mVW1XQ+YrBx7rykUPHiJOzPUYHvTUQVXC0CI8khAtW98Lt3SI9m4MxBuw==",
|
||||
"version": "3.13.20210623031509",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.13.20210623031509.tgz",
|
||||
"integrity": "sha512-TT5+0Gz20tZ3PLEspb5OmZwY23+OxbrSlJuMMQGM24HUxK7nyjMbncwuAzVnd7v9ef8BqH+nQ9nEItKQNcPTBQ==",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"eslint-config-scratch": "7.0.0",
|
||||
"eslint-plugin-json": "2.0.1",
|
||||
"eslint-plugin-react": "7.14.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"fastly": "1.2.1",
|
||||
"formik": "1.5.4",
|
||||
"formsy-react": "1.1.4",
|
||||
|
@ -126,8 +127,8 @@
|
|||
"redux-mock-store": "^1.2.3",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20210615041617",
|
||||
"scratch-l10n": "3.12.20210615031544",
|
||||
"scratch-gui": "0.1.0-prerelease.20210621040041",
|
||||
"scratch-l10n": "3.13.20210623031509",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
"style-loader": "0.12.3",
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
|||
globals: {
|
||||
process: true
|
||||
},
|
||||
plugins: ['json'],
|
||||
plugins: ['json', 'react-hooks'],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect'
|
||||
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
'camelcase': [2, {
|
||||
properties: 'never', // This is from the base `scratch` config
|
||||
allow: ['^UNSAFE_'] // Allow until migrated to new lifecycle methods
|
||||
}]
|
||||
}],
|
||||
'react-hooks/rules-of-hooks': 'error'
|
||||
}
|
||||
};
|
||||
|
|
23
src/lib/admin-requests.js
Normal file
23
src/lib/admin-requests.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {selectIsAdmin, selectToken} from '../redux/session';
|
||||
|
||||
/**
|
||||
* Augment an `options` object that will be used by api.js
|
||||
* to automatically include admin authentication token and
|
||||
* /admin url prefix if the user is an admin.
|
||||
*
|
||||
* @param {object} opts Object argument for api.js request
|
||||
* @param {string} opts.uri A uri that may be prefixed with /admin
|
||||
* @param {object} state The full redux state tree to use with session selectors
|
||||
* @returns {object} The augmented options object
|
||||
*/
|
||||
const withAdmin = (opts, state) => {
|
||||
if (selectIsAdmin(state)) {
|
||||
return Object.assign({}, opts, {
|
||||
uri: `/admin${opts.uri}`,
|
||||
authentication: selectToken(state)
|
||||
});
|
||||
}
|
||||
return opts;
|
||||
};
|
||||
|
||||
export {withAdmin};
|
|
@ -70,7 +70,17 @@ module.exports.commentsReducer = (state, action) => {
|
|||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
// Replies to comments go at the end of the thread
|
||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
||||
[action.topLevelCommentId]:
|
||||
(state.replies[action.topLevelCommentId] || [])
|
||||
.concat(action.comment)
|
||||
}),
|
||||
comments: state.comments.map(comment => {
|
||||
if (comment.id === action.topLevelCommentId) {
|
||||
return Object.assign({}, comment, {
|
||||
reply_count: comment.reply_count + 1
|
||||
});
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
@ -69,8 +69,10 @@ const getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (disp
|
|||
}
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments(body));
|
||||
dispatch(getReplies(id, body.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
||||
|
||||
const commentsWithReplies = body.filter(comment => comment.reply_count > 0);
|
||||
if (commentsWithReplies.length > 0) {
|
||||
dispatch(getReplies(id, commentsWithReplies.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
||||
}
|
||||
// If we loaded a full page of comments, assume there are more to load.
|
||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||
// any more server query complexity, so seems worth it. In the case of a project with
|
||||
|
@ -105,7 +107,9 @@ const getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) =>
|
|||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments([body]));
|
||||
if (body.reply_count > 0) {
|
||||
dispatch(getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -142,9 +142,13 @@ module.exports.selectIsSocial = state => get(state, ['session', 'session', 'perm
|
|||
module.exports.selectIsEducator = state => get(state, ['session', 'session', 'permissions', 'educator'], false);
|
||||
module.exports.selectProjectCommentsGloballyEnabled = state =>
|
||||
get(state, ['session', 'session', 'flags', 'project_comments_enabled'], false);
|
||||
module.exports.selectStudioCommentsGloballyEnabled = state =>
|
||||
get(state, ['session', 'session', 'flags', 'gallery_comments_enabled'], false);
|
||||
module.exports.selectMuteStatus = state => get(state, ['session', 'session', 'permissions', 'mute_status'],
|
||||
{muteExpiresAt: 0, offenses: [], showWarning: false});
|
||||
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;
|
||||
|
||||
|
|
|
@ -90,7 +90,10 @@ const getTopLevelComments = () => ((dispatch, getState) => {
|
|||
}
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments(body));
|
||||
dispatch(getReplies(body.map(comment => comment.id), 0));
|
||||
const commentsWithReplies = body.filter(comment => comment.reply_count > 0);
|
||||
if (commentsWithReplies.length > 0) {
|
||||
dispatch(getReplies(commentsWithReplies.map(comment => comment.id), 0));
|
||||
}
|
||||
|
||||
// If we loaded a full page of comments, assume there are more to load.
|
||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||
|
@ -130,7 +133,9 @@ const getCommentById = commentId => ((dispatch, getState) => {
|
|||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments([body]));
|
||||
if (body.reply_count > 0) {
|
||||
dispatch(getReplies(body.id, 0));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const {selectUserId, selectIsAdmin, selectIsSocial,
|
||||
selectIsLoggedIn, selectUsername, selectIsMuted} = require('./session');
|
||||
selectIsLoggedIn, selectUsername, selectIsMuted,
|
||||
selectHasFetchedSession, selectStudioCommentsGloballyEnabled} = require('./session');
|
||||
|
||||
// Fine-grain selector helpers - not exported, use the higher level selectors below
|
||||
const isCreator = state => selectUserId(state) === state.studio.owner;
|
||||
|
@ -17,12 +18,18 @@ const selectCanAddProjects = state =>
|
|||
// This isn't "canComment" since they could be muted, but comment composer handles that
|
||||
const selectShowCommentComposer = state => selectIsSocial(state);
|
||||
|
||||
const selectCanReportComment = state => selectIsSocial(state);
|
||||
const selectCanReportComment = (state, commentUsername) =>
|
||||
selectIsLoggedIn(state) && selectUsername(state) !== commentUsername;
|
||||
const selectCanRestoreComment = state => selectIsAdmin(state);
|
||||
// On the project page, project owners can delete comments with a confirmation,
|
||||
// and admins can delete comments without a confirmation. For now, only admins
|
||||
// can delete studio comments, so the following two are the same.
|
||||
const selectCanDeleteComment = state => selectIsAdmin(state);
|
||||
// and admins can delete comments without a confirmation.
|
||||
// On the studio page, studio creators and managers have the ability to delete *their own* comments with confirmation.
|
||||
// Admins can delete comments without a confirmation.
|
||||
const selectCanDeleteComment = (state, commentUsername) => {
|
||||
if (selectIsAdmin(state)) return true;
|
||||
if (isManager(state) && selectUsername(state) === commentUsername) return true;
|
||||
return false;
|
||||
};
|
||||
const selectCanDeleteCommentWithoutConfirm = state => selectIsAdmin(state);
|
||||
|
||||
const selectCanFollowStudio = state => selectIsLoggedIn(state);
|
||||
|
@ -71,7 +78,9 @@ const selectShowProjectMuteError = state => selectIsMuted(state) &&
|
|||
isCurator(state) ||
|
||||
(selectIsSocial(state) && state.studio.openToAll));
|
||||
const selectShowCuratorMuteError = state => selectIsMuted(state) && (isManager(state) || selectIsAdmin(state));
|
||||
|
||||
const selectShowCommentsGloballyOffError = state =>
|
||||
selectHasFetchedSession(state) && !selectStudioCommentsGloballyEnabled(state);
|
||||
const selectShowCommentsList = state => selectHasFetchedSession(state) && selectStudioCommentsGloballyEnabled(state);
|
||||
export {
|
||||
selectCanEditInfo,
|
||||
selectCanAddProjects,
|
||||
|
@ -89,6 +98,8 @@ export {
|
|||
selectCanRemoveManager,
|
||||
selectCanPromoteCurators,
|
||||
selectCanRemoveProject,
|
||||
selectShowCommentsList,
|
||||
selectShowCommentsGloballyOffError,
|
||||
selectShowEditMuteError,
|
||||
selectShowProjectMuteError,
|
||||
selectShowCuratorMuteError
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const keyMirror = require('keymirror');
|
||||
const {withAdmin} = require('../lib/admin-requests');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
@ -25,6 +26,7 @@ const getInitialState = () => ({
|
|||
followers: 0,
|
||||
managers: 0,
|
||||
owner: null,
|
||||
public: null,
|
||||
|
||||
// BEWARE: classroomId is only loaded if the user is an educator
|
||||
classroomId: null,
|
||||
|
@ -110,11 +112,14 @@ const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHIN
|
|||
const selectIsFollowing = state => state.studio.following;
|
||||
const selectIsFetchingRoles = state => state.studio.rolesStatus === Status.FETCHING;
|
||||
const selectClassroomId = state => state.studio.classroomId;
|
||||
const selectStudioPublic = state => state.studio.public;
|
||||
|
||||
// Thunks
|
||||
const getInfo = () => ((dispatch, getState) => {
|
||||
const studioId = selectStudioId(getState());
|
||||
api({uri: `/studios/${studioId}`}, (err, body, res) => {
|
||||
const state = getState();
|
||||
const studioId = selectStudioId(state);
|
||||
const opts = {uri: `/studios/${studioId}`};
|
||||
api(withAdmin(opts, state), (err, body, res) => {
|
||||
if (err || typeof body === 'undefined' || res.statusCode !== 200) {
|
||||
dispatch(setFetchStatus('infoStatus', Status.ERROR, err));
|
||||
return;
|
||||
|
@ -130,7 +135,8 @@ const getInfo = () => ((dispatch, getState) => {
|
|||
followers: body.stats.followers,
|
||||
managers: body.stats.managers,
|
||||
projectCount: body.stats.projects,
|
||||
owner: body.owner
|
||||
owner: body.owner,
|
||||
public: body.public
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -199,5 +205,6 @@ module.exports = {
|
|||
selectIsFetchingInfo,
|
||||
selectIsFetchingRoles,
|
||||
selectIsFollowing,
|
||||
selectClassroomId
|
||||
selectClassroomId,
|
||||
selectStudioPublic
|
||||
};
|
||||
|
|
|
@ -131,7 +131,7 @@ class TopLevelComment extends React.Component {
|
|||
|
||||
return (
|
||||
<FlexRow className="comment-container">
|
||||
<Comment
|
||||
<this.props.commentComponent
|
||||
highlighted={highlightedCommentId === id}
|
||||
postURI={postURI}
|
||||
onAddComment={this.handleAddComment}
|
||||
|
@ -173,7 +173,7 @@ class TopLevelComment extends React.Component {
|
|||
<React.Fragment
|
||||
key={`reply-and-status-${reply.id}`}
|
||||
>
|
||||
<Comment
|
||||
<this.props.commentComponent
|
||||
author={reply.author}
|
||||
canDelete={canDelete}
|
||||
canDeleteWithoutConfirm={canDeleteWithoutConfirm}
|
||||
|
@ -233,6 +233,7 @@ TopLevelComment.propTypes = {
|
|||
canReply: PropTypes.bool,
|
||||
canReport: PropTypes.bool,
|
||||
canRestore: PropTypes.bool,
|
||||
commentComponent: PropTypes.func,
|
||||
content: PropTypes.string,
|
||||
datetimeCreated: PropTypes.string,
|
||||
defaultExpanded: PropTypes.bool,
|
||||
|
@ -260,7 +261,8 @@ TopLevelComment.defaultProps = {
|
|||
defaultExpanded: false,
|
||||
hasThreadLimit: false,
|
||||
moreRepliesToLoad: false,
|
||||
threadHasReplyStatus: false
|
||||
threadHasReplyStatus: false,
|
||||
commentComponent: Comment
|
||||
};
|
||||
|
||||
module.exports = TopLevelComment;
|
||||
|
|
|
@ -46,5 +46,6 @@
|
|||
"project.cloudVariables": "Cloud Variables",
|
||||
"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.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;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
max-width: 18.75rem;
|
||||
min-height: 1rem;
|
||||
|
@ -185,7 +184,6 @@ $stage-width: 480px;
|
|||
border-left: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $ui-orange;
|
||||
width: $arrow-border-width;
|
||||
height: $arrow-border-width;
|
||||
|
||||
|
|
|
@ -8,19 +8,47 @@ const Button = require('../../components/forms/button.jsx');
|
|||
const AddToStudioModal = require('./add-to-studio.jsx');
|
||||
const SocialModal = require('../../components/modal/social/container.jsx');
|
||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||
const {connect} = require('react-redux');
|
||||
const {useState} = require('react');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
import {selectIsMuted, selectNewStudiosLaunched} from '../../redux/session.js';
|
||||
import StudioMuteEditMessage from '../studio/studio-mute-edit-message.jsx';
|
||||
|
||||
require('./subactions.scss');
|
||||
|
||||
const Subactions = props => (
|
||||
const Subactions = ({
|
||||
addToStudioOpen,
|
||||
canAddToStudio,
|
||||
canReport,
|
||||
isAdmin,
|
||||
isShared,
|
||||
onAddToStudioClicked,
|
||||
onAddToStudioClosed,
|
||||
onReportClicked,
|
||||
onReportClose,
|
||||
onReportSubmit,
|
||||
onSocialClicked,
|
||||
onSocialClosed,
|
||||
onToggleStudio,
|
||||
projectInfo,
|
||||
reportOpen,
|
||||
shareDate,
|
||||
showAddToStudioMuteError,
|
||||
socialOpen,
|
||||
userOwnsProject
|
||||
}) => {
|
||||
const [showMuteMessage, setShowMuteMessage] = useState(false);
|
||||
|
||||
return (
|
||||
<FlexRow className="subactions">
|
||||
<div className="share-date">
|
||||
<div className="copyleft">©</div>
|
||||
{' '}
|
||||
{/* eslint-disable react/jsx-sort-props */}
|
||||
{props.shareDate ? (
|
||||
{shareDate ? (
|
||||
<FormattedDate
|
||||
value={Date.parse(props.shareDate)}
|
||||
value={Date.parse(shareDate)}
|
||||
day="2-digit"
|
||||
month="short"
|
||||
year="numeric"
|
||||
|
@ -29,62 +57,75 @@ const Subactions = props => (
|
|||
{/* eslint-enable react/jsx-sort-props */}
|
||||
</div>
|
||||
<FlexRow className="action-buttons">
|
||||
{(props.canReport) &&
|
||||
{(canReport) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button report-button"
|
||||
key="report-button"
|
||||
onClick={props.onReportClicked}
|
||||
onClick={onReportClicked}
|
||||
>
|
||||
<FormattedMessage id="general.report" />
|
||||
</Button>
|
||||
{props.reportOpen && (
|
||||
{reportOpen && (
|
||||
<ReportModal
|
||||
isOpen
|
||||
key="report-modal"
|
||||
type="project"
|
||||
onReport={props.onReportSubmit}
|
||||
onRequestClose={props.onReportClose}
|
||||
onReport={onReportSubmit}
|
||||
onRequestClose={onReportClose}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
{props.canAddToStudio &&
|
||||
{canAddToStudio &&
|
||||
<React.Fragment>
|
||||
<div
|
||||
style={{position: 'relative'}}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onMouseEnter={() => showAddToStudioMuteError && setShowMuteMessage(true)}
|
||||
onMouseLeave={() => showAddToStudioMuteError && setShowMuteMessage(false)}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
>
|
||||
<Button
|
||||
className="action-button studio-button"
|
||||
disabled={showAddToStudioMuteError}
|
||||
key="add-to-studio-button"
|
||||
onClick={props.onAddToStudioClicked}
|
||||
onClick={showMuteMessage ? null : onAddToStudioClicked}
|
||||
>
|
||||
<FormattedMessage id="addToStudio.title" />
|
||||
</Button>
|
||||
{props.addToStudioOpen && (
|
||||
{showMuteMessage && <StudioMuteEditMessage
|
||||
className="studio-button-error"
|
||||
messageId="project.mutedAddToStudio"
|
||||
/>}
|
||||
</div>
|
||||
{addToStudioOpen && (
|
||||
<AddToStudioModal
|
||||
isOpen
|
||||
isAdmin={props.isAdmin}
|
||||
isAdmin={isAdmin}
|
||||
key="add-to-studio-modal"
|
||||
userOwnsProject={props.userOwnsProject}
|
||||
onRequestClose={props.onAddToStudioClosed}
|
||||
onToggleStudio={props.onToggleStudio}
|
||||
userOwnsProject={userOwnsProject}
|
||||
onRequestClose={onAddToStudioClosed}
|
||||
onToggleStudio={onToggleStudio}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
{/* only show copy link button, modal if project is shared */}
|
||||
{props.isShared && props.projectInfo && props.projectInfo.id && (
|
||||
{isShared && projectInfo && projectInfo.id && (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button copy-link-button"
|
||||
onClick={props.onSocialClicked}
|
||||
onClick={onSocialClicked}
|
||||
>
|
||||
<FormattedMessage id="general.copyLink" />
|
||||
</Button>
|
||||
{props.socialOpen && (
|
||||
{socialOpen && (
|
||||
<SocialModal
|
||||
isOpen
|
||||
key="social-modal"
|
||||
projectId={props.projectInfo && props.projectInfo.id}
|
||||
onRequestClose={props.onSocialClosed}
|
||||
projectId={projectInfo && projectInfo.id}
|
||||
onRequestClose={onSocialClosed}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@ -92,6 +133,7 @@ const Subactions = props => (
|
|||
</FlexRow>
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
Subactions.propTypes = {
|
||||
addToStudioOpen: PropTypes.bool,
|
||||
|
@ -110,8 +152,13 @@ Subactions.propTypes = {
|
|||
projectInfo: projectShape,
|
||||
reportOpen: PropTypes.bool,
|
||||
shareDate: PropTypes.string,
|
||||
showAddToStudioMuteError: PropTypes.bool,
|
||||
socialOpen: PropTypes.bool,
|
||||
userOwnsProject: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = Subactions;
|
||||
module.exports = connect(
|
||||
state => ({
|
||||
showAddToStudioMuteError: selectNewStudiosLaunched(state) && selectIsMuted(state)
|
||||
})
|
||||
)(Subactions);
|
||||
|
|
|
@ -109,3 +109,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.studio-button-error {
|
||||
top: auto;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Debug = ({label, data}) => (<div style={{padding: '2rem', border: '1px solid red', margin: '2rem'}}>
|
||||
<small>{label}</small>
|
||||
<code>
|
||||
<pre style={{fontSize: '0.75rem'}}>
|
||||
{JSON.stringify(data, null, ' ')}
|
||||
</pre>
|
||||
</code>
|
||||
</div>);
|
||||
|
||||
Debug.propTypes = {
|
||||
label: PropTypes.string,
|
||||
data: PropTypes.any // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Debug;
|
|
@ -6,6 +6,8 @@
|
|||
"studio.tabNavCommentsWithCount": "Comments {commentCount}",
|
||||
"studio.tabNavActivity": "Activity",
|
||||
|
||||
"studio.showingDeleted": "Showing Deleted Studio",
|
||||
|
||||
"studio.title": "Title",
|
||||
"studio.description": "Description",
|
||||
"studio.thumbnail": "Thumbnail",
|
||||
|
@ -16,6 +18,15 @@
|
|||
"studio.updateErrors.thumbnailTooLarge": "Maximum file size is 512 KB and less than 500x500 pixels.",
|
||||
"studio.updateErrors.thumbnailInvalid": "Upload a valid image. The file you uploaded was either not an image or a corrupted image.",
|
||||
|
||||
"studio.followErrors.confirmEmail": "Please confirm your email address first",
|
||||
"studio.followErrors.generic": "Something went wrong following the studio",
|
||||
|
||||
"studio.sectionLoadError.projectsHeadline": "Something went wrong loading projects",
|
||||
"studio.sectionLoadError.curatorsHeadline": "Something went wrong loading curators",
|
||||
"studio.sectionLoadError.managersHeadline": "Something went wrong loading managers",
|
||||
"studio.sectionLoadError.activityHeadline": "Something went wrong loading activity",
|
||||
"studio.sectionLoadError.tryAgain": "Try again",
|
||||
|
||||
"studio.projectsHeader": "Projects",
|
||||
"studio.addProjectsHeader": "Add Projects",
|
||||
"studio.addProject": "Add",
|
||||
|
@ -88,6 +99,7 @@
|
|||
"studio.comments.toggleOff": "Commenting off",
|
||||
"studio.comments.toggleOn": "Commenting on",
|
||||
"studio.comments.turnedOff": "Sorry, comment posting has been turned off for this studio.",
|
||||
"studio.comments.turnedOffGlobally" : "Studio comments across Scratch are turned off, but don't worry, your comments are saved and will be back soon.",
|
||||
|
||||
"studio.sharedFilter": "Shared",
|
||||
"studio.favoritedFilter": "Favorited",
|
||||
|
|
|
@ -3,6 +3,7 @@ import keyMirror from 'keymirror';
|
|||
import api from '../../../lib/api';
|
||||
import {activity} from './redux-modules';
|
||||
import {selectStudioId} from '../../../redux/studio';
|
||||
import {withAdmin} from '../../../lib/admin-requests';
|
||||
|
||||
const Errors = keyMirror({
|
||||
NETWORK: null,
|
||||
|
@ -27,10 +28,11 @@ const loadActivity = () => ((dispatch, getState) => {
|
|||
// the date of the oldest one we've already loaded
|
||||
params.dateLimit = items[items.length - 1].datetime_created;
|
||||
}
|
||||
api({
|
||||
const opts = {
|
||||
uri: `/studios/${studioId}/activity/`,
|
||||
params
|
||||
}, (err, body, res) => {
|
||||
};
|
||||
api(withAdmin(opts, state), (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(activity.actions.error(error));
|
||||
const ids = items.map(item => item.id);
|
||||
|
|
|
@ -4,6 +4,7 @@ import api from '../../../lib/api';
|
|||
import {curators, managers} from './redux-modules';
|
||||
import {selectUsername} from '../../../redux/session';
|
||||
import {selectStudioId, setRoles, setInfo} from '../../../redux/studio';
|
||||
import {withAdmin} from '../../../lib/admin-requests';
|
||||
|
||||
const Errors = keyMirror({
|
||||
NETWORK: null,
|
||||
|
@ -40,10 +41,11 @@ const loadManagers = () => ((dispatch, getState) => {
|
|||
const studioId = selectStudioId(state);
|
||||
const managerCount = managers.selector(state).items.length;
|
||||
const managersPerPage = 20;
|
||||
api({
|
||||
const opts = {
|
||||
uri: `/studios/${studioId}/managers/`,
|
||||
params: {limit: managersPerPage, offset: managerCount}
|
||||
}, (err, body, res) => {
|
||||
};
|
||||
api(withAdmin(opts, state), (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(managers.actions.error(error));
|
||||
dispatch(managers.actions.append(body, body.length === managersPerPage));
|
||||
|
@ -55,10 +57,11 @@ const loadCurators = () => ((dispatch, getState) => {
|
|||
const studioId = selectStudioId(state);
|
||||
const curatorCount = curators.selector(state).items.length;
|
||||
const curatorsPerPage = 20;
|
||||
api({
|
||||
const opts = {
|
||||
uri: `/studios/${studioId}/curators/`,
|
||||
params: {limit: curatorsPerPage, offset: curatorCount}
|
||||
}, (err, body, res) => {
|
||||
};
|
||||
api(withAdmin(opts, state), (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(curators.actions.error(error));
|
||||
dispatch(curators.actions.append(body, body.length === curatorsPerPage));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import keyMirror from 'keymirror';
|
||||
import {withAdmin} from '../../../lib/admin-requests';
|
||||
import api from '../../../lib/api';
|
||||
|
||||
import {selectToken} from '../../../redux/session';
|
||||
|
@ -32,10 +33,11 @@ const loadProjects = () => ((dispatch, getState) => {
|
|||
const studioId = selectStudioId(state);
|
||||
const projectCount = projects.selector(state).items.length;
|
||||
const projectsPerPage = 20;
|
||||
api({
|
||||
const opts = {
|
||||
uri: `/studios/${studioId}/projects/`,
|
||||
params: {limit: projectsPerPage, offset: projectCount}
|
||||
}, (err, body, res) => {
|
||||
};
|
||||
api(withAdmin(opts, state), (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(projects.actions.error(error));
|
||||
dispatch(projects.actions.append(body, body.length === projectsPerPage));
|
||||
|
|
|
@ -20,6 +20,7 @@ import './user-projects-modal.scss';
|
|||
import {selectIsEducator} from '../../../redux/session';
|
||||
import AlertProvider from '../../../components/alert/alert-provider.jsx';
|
||||
import Alert from '../../../components/alert/alert.jsx';
|
||||
import Spinner from '../../../components/spinner/spinner.jsx';
|
||||
|
||||
const UserProjectsModal = ({
|
||||
items, error, loading, moreToLoad, showStudentsFilter,
|
||||
|
@ -77,7 +78,7 @@ const UserProjectsModal = ({
|
|||
<AlertProvider>
|
||||
{error && <div>Error loading {filter}: {error}</div>}
|
||||
<Alert className="studio-alert" />
|
||||
{items.length > 0 ? (
|
||||
{items.length > 0 &&
|
||||
<React.Fragment>
|
||||
<div className="user-projects-modal-grid">
|
||||
{items.map(project => (
|
||||
|
@ -105,7 +106,8 @@ const UserProjectsModal = ({
|
|||
</div>
|
||||
}
|
||||
</React.Fragment>
|
||||
) :
|
||||
}
|
||||
{!loading && items.length === 0 &&
|
||||
<div className="studio-projects-empty">
|
||||
<img
|
||||
src="/svgs/studio/add-to-studio-empty.svg"
|
||||
|
@ -122,6 +124,12 @@ const UserProjectsModal = ({
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
{loading &&
|
||||
<Spinner
|
||||
className="studio-projects-spinner"
|
||||
color="blue"
|
||||
/>
|
||||
}
|
||||
</AlertProvider>
|
||||
</ModalInnerContent>
|
||||
<div className="studio-projects-done-row">
|
||||
|
|
|
@ -13,21 +13,34 @@
|
|||
}
|
||||
.user-projects-modal-nav {
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid $active-gray;
|
||||
width: unset;
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
&.active { background: $ui-blue; }
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
color: #575e75;
|
||||
&.active {
|
||||
background: $ui-blue;
|
||||
color: white;
|
||||
}
|
||||
&:active {
|
||||
padding: .75em 1.5em;
|
||||
}
|
||||
}
|
||||
button:hover {
|
||||
background: $ui-blue-25percent;
|
||||
border: 1px solid $ui-blue-10percent;
|
||||
}
|
||||
}
|
||||
.user-projects-modal-content {
|
||||
padding: 0 30px 30px;
|
||||
padding: 0 30px 8px;
|
||||
background: #E9F1FC;
|
||||
max-height: calc(100vh - 270px);
|
||||
min-height: 300px;
|
||||
height: calc(100vh - 300px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& { max-height: calc(100vh - 175px); }
|
||||
& { height: calc(100vh - 170px); }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +52,7 @@
|
|||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 6px 12px;
|
||||
border-top: 1px solid $active-gray;
|
||||
}
|
||||
|
||||
.studio-projects-empty {
|
||||
|
@ -46,15 +60,20 @@
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 4rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.studio-projects-empty-text {
|
||||
color: hsla(215, 100, 65, .75);
|
||||
width: 325px;
|
||||
max-width: 325px;
|
||||
text-align: center;
|
||||
line-height: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.studio-projects-spinner {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-tile-added {
|
||||
|
|
|
@ -6,7 +6,6 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {activity} from './lib/redux-modules';
|
||||
import {loadActivity} from './lib/studio-activity-actions';
|
||||
import Debug from './debug.jsx';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import SocialMessage from '../../components/social-message/social-message.jsx';
|
||||
|
@ -181,10 +180,15 @@ const StudioActivity = ({items, loading, error, moreToLoad, onLoadMore}) => {
|
|||
<h2><FormattedMessage id="studio.activityHeader" /></h2>
|
||||
</div>
|
||||
{loading && <div>Loading...</div>}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
{error && <div className="studio-section-load-error studio-info-box studio-info-box-error">
|
||||
<h3><FormattedMessage id="studio.sectionLoadError.activityHeadline" /></h3>
|
||||
<button
|
||||
className="button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="studio.sectionLoadError.tryAgain" />
|
||||
</button>
|
||||
</div>}
|
||||
<ul
|
||||
className="studio-messages-list"
|
||||
>
|
||||
|
|
|
@ -48,8 +48,11 @@ const StudioAdminPanel = ({studioId, showAdminPanel}) => {
|
|||
const [adminPanelOpen, setAdminPanelOpen] = useState(getAdminPanelOpen());
|
||||
|
||||
useEffect(() => {
|
||||
storeAdminPanelOpen(adminPanelOpen);
|
||||
}, [adminPanelOpen]);
|
||||
// This effect will both keep localStorage up-to-date AND cause
|
||||
// the spacing to change to allow for the open admin panel, so make
|
||||
// sure the admin panel should be visible at all before making any changes.
|
||||
if (showAdminPanel) storeAdminPanelOpen(adminPanelOpen);
|
||||
}, [showAdminPanel, adminPanelOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showAdminPanel) return;
|
||||
|
|
17
src/views/studio/studio-comment.js
Normal file
17
src/views/studio/studio-comment.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {connect} from 'react-redux';
|
||||
import Comment from '../preview/comment/comment.jsx';
|
||||
|
||||
import {
|
||||
selectCanDeleteComment,
|
||||
selectCanReportComment,
|
||||
selectShowCommentComposer
|
||||
} from '../../redux/studio-permissions';
|
||||
import {selectStudioCommentsAllowed} from '../../redux/studio.js';
|
||||
|
||||
export default connect(
|
||||
(state, ownProps) => ({
|
||||
canReport: selectCanReportComment(state, ownProps.author.username),
|
||||
canDelete: selectCanDeleteComment(state, ownProps.author.username),
|
||||
canReply: selectShowCommentComposer(state) && selectStudioCommentsAllowed(state)
|
||||
})
|
||||
)(Comment);
|
|
@ -4,22 +4,23 @@ import {connect} from 'react-redux';
|
|||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import Button from '../../components/forms/button.jsx';
|
||||
import CommentingStatus from '../../components/commenting-status/commenting-status.jsx';
|
||||
import ComposeComment from '../preview/comment/compose-comment.jsx';
|
||||
import TopLevelComment from '../preview/comment/top-level-comment.jsx';
|
||||
import studioCommentActions from '../../redux/studio-comment-actions.js';
|
||||
import StudioCommentsAllowed from './studio-comments-allowed.jsx';
|
||||
import StudioCommentsNotAllowed from './studio-comments-not-allowed.jsx';
|
||||
|
||||
import {selectIsAdmin, selectHasFetchedSession} from '../../redux/session';
|
||||
import {
|
||||
selectShowCommentComposer,
|
||||
selectCanDeleteComment,
|
||||
selectCanDeleteCommentWithoutConfirm,
|
||||
selectCanReportComment,
|
||||
selectCanRestoreComment,
|
||||
selectCanEditCommentsAllowed
|
||||
selectCanEditCommentsAllowed,
|
||||
selectShowCommentsList,
|
||||
selectShowCommentsGloballyOffError
|
||||
} from '../../redux/studio-permissions';
|
||||
import {selectStudioCommentsAllowed} from '../../redux/studio.js';
|
||||
import StudioComment from './studio-comment.js';
|
||||
|
||||
const StudioComments = ({
|
||||
comments,
|
||||
|
@ -32,20 +33,38 @@ const StudioComments = ({
|
|||
replies,
|
||||
postURI,
|
||||
shouldShowCommentComposer,
|
||||
canDeleteComment,
|
||||
shouldShowCommentsList,
|
||||
shouldShowCommentsGloballyOffError,
|
||||
canDeleteCommentWithoutConfirm,
|
||||
canEditCommentsAllowed,
|
||||
canReportComment,
|
||||
canRestoreComment,
|
||||
handleDeleteComment,
|
||||
handleRestoreComment,
|
||||
handleResetComments,
|
||||
handleReportComment,
|
||||
handleLoadMoreReplies
|
||||
handleLoadMoreReplies,
|
||||
handleLoadSingleComment
|
||||
}) => {
|
||||
const commentHashPrefix = '#comments-';
|
||||
const [singleCommentId, setSingleCommentId] = useState(
|
||||
window.location.hash.indexOf(commentHashPrefix) !== -1 &&
|
||||
parseInt(window.location.hash.replace(commentHashPrefix, ''), 10));
|
||||
|
||||
useEffect(() => {
|
||||
if (comments.length === 0 && hasFetchedSession) handleLoadMoreComments();
|
||||
}, [comments.length === 0, hasFetchedSession]);
|
||||
if (comments.length === 0 && hasFetchedSession) {
|
||||
if (singleCommentId) {
|
||||
handleLoadSingleComment(singleCommentId);
|
||||
} else {
|
||||
handleLoadMoreComments();
|
||||
}
|
||||
}
|
||||
}, [comments.length === 0, hasFetchedSession, singleCommentId]);
|
||||
|
||||
const handleSeeAllComments = () => {
|
||||
setSingleCommentId(false);
|
||||
history.pushState('', document.title, window.location.pathname + window.location.search);
|
||||
handleResetComments();
|
||||
};
|
||||
|
||||
// The comments you see depend on your admin status
|
||||
// so reset them if isAdmin changes.
|
||||
|
@ -74,6 +93,20 @@ const StudioComments = ({
|
|||
<h2><FormattedMessage id="studio.commentsHeader" /></h2>
|
||||
{canEditCommentsAllowed && <StudioCommentsAllowed />}
|
||||
</div>
|
||||
{shouldShowCommentsGloballyOffError &&
|
||||
<div>
|
||||
<CommentingStatus>
|
||||
<p>
|
||||
<FormattedMessage id="studio.comments.turnedOffGlobally" />
|
||||
</p>
|
||||
</CommentingStatus>
|
||||
<img
|
||||
className="studio-comment-placholder-img"
|
||||
src="/images/comments/comment-placeholder.png"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{shouldShowCommentsList &&
|
||||
<div>
|
||||
{shouldShowCommentComposer ?
|
||||
(commentsAllowed ?
|
||||
|
@ -88,13 +121,13 @@ const StudioComments = ({
|
|||
<TopLevelComment
|
||||
hasThreadLimit
|
||||
author={comment.author}
|
||||
canDelete={canDeleteComment}
|
||||
canDeleteWithoutConfirm={canDeleteCommentWithoutConfirm}
|
||||
canReply={shouldShowCommentComposer}
|
||||
canReport={canReportComment}
|
||||
canRestore={canRestoreComment}
|
||||
commentComponent={StudioComment}
|
||||
content={comment.content}
|
||||
datetimeCreated={comment.datetime_created}
|
||||
defaultExpanded={singleCommentId}
|
||||
highlightedCommentId={singleCommentId}
|
||||
id={comment.id}
|
||||
key={comment.id}
|
||||
moreRepliesToLoad={comment.moreRepliesToLoad}
|
||||
|
@ -113,6 +146,15 @@ const StudioComments = ({
|
|||
onLoadMoreReplies={handleLoadMoreReplies}
|
||||
/>
|
||||
))}
|
||||
{!!singleCommentId &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleSeeAllComments}
|
||||
>
|
||||
<FormattedMessage id="general.seeAllComments" />
|
||||
</Button>
|
||||
}
|
||||
{moreCommentsToLoad &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
|
@ -122,6 +164,7 @@ const StudioComments = ({
|
|||
</Button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -136,16 +179,17 @@ StudioComments.propTypes = {
|
|||
moreCommentsToLoad: PropTypes.bool,
|
||||
replies: PropTypes.shape({}),
|
||||
shouldShowCommentComposer: PropTypes.bool,
|
||||
canDeleteComment: PropTypes.bool,
|
||||
shouldShowCommentsGloballyOffError: PropTypes.bool,
|
||||
shouldShowCommentsList: PropTypes.bool,
|
||||
canDeleteCommentWithoutConfirm: PropTypes.bool,
|
||||
canEditCommentsAllowed: PropTypes.bool,
|
||||
canReportComment: PropTypes.bool,
|
||||
canRestoreComment: PropTypes.bool,
|
||||
handleDeleteComment: PropTypes.func,
|
||||
handleRestoreComment: PropTypes.func,
|
||||
handleReportComment: PropTypes.func,
|
||||
handleResetComments: PropTypes.func,
|
||||
handleLoadMoreReplies: PropTypes.func,
|
||||
handleLoadSingleComment: PropTypes.func,
|
||||
postURI: PropTypes.string
|
||||
};
|
||||
|
||||
|
@ -162,15 +206,16 @@ export default connect(
|
|||
replies: state.comments.replies,
|
||||
commentsAllowed: selectStudioCommentsAllowed(state),
|
||||
shouldShowCommentComposer: selectShowCommentComposer(state),
|
||||
canDeleteComment: selectCanDeleteComment(state),
|
||||
shouldShowCommentsGloballyOffError: selectShowCommentsGloballyOffError(state),
|
||||
shouldShowCommentsList: selectShowCommentsList(state),
|
||||
canDeleteCommentWithoutConfirm: selectCanDeleteCommentWithoutConfirm(state),
|
||||
canEditCommentsAllowed: selectCanEditCommentsAllowed(state),
|
||||
canReportComment: selectCanReportComment(state),
|
||||
canRestoreComment: selectCanRestoreComment(state),
|
||||
postURI: `/proxy/comments/studio/${state.studio.id}`
|
||||
}),
|
||||
{
|
||||
handleLoadMoreComments: studioCommentActions.getTopLevelComments,
|
||||
handleLoadSingleComment: studioCommentActions.getCommentById,
|
||||
handleNewComment: studioCommentActions.addNewComment,
|
||||
handleDeleteComment: studioCommentActions.deleteComment,
|
||||
handleRestoreComment: studioCommentActions.restoreComment,
|
||||
|
|
|
@ -5,7 +5,6 @@ import {FormattedMessage} from 'react-intl';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import {curators} from './lib/redux-modules';
|
||||
import Debug from './debug.jsx';
|
||||
import {CuratorTile} from './studio-member-tile.jsx';
|
||||
import CuratorInviter from './studio-curator-inviter.jsx';
|
||||
import {loadCurators} from './lib/studio-member-actions';
|
||||
|
@ -28,10 +27,15 @@ const StudioCurators = ({
|
|||
<h2><FormattedMessage id="studio.curatorsHeader" /></h2>
|
||||
</div>
|
||||
{canInviteCurators && <CuratorInviter />}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
{error && <div className="studio-section-load-error studio-info-box studio-info-box-error">
|
||||
<h3><FormattedMessage id="studio.sectionLoadError.curatorsHeadline" /></h3>
|
||||
<button
|
||||
className="button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="studio.sectionLoadError.tryAgain" />
|
||||
</button>
|
||||
</div>}
|
||||
<div className="studio-members-grid">
|
||||
{items.length === 0 && !loading ? (
|
||||
<div className="studio-empty">
|
||||
|
@ -39,7 +43,7 @@ const StudioCurators = ({
|
|||
width="179"
|
||||
height="111"
|
||||
className="studio-empty-img"
|
||||
src="/images/studios/curators-empty.png"
|
||||
src="/images/studios/curators-empty-image.svg"
|
||||
/>
|
||||
{canInviteCurators ? (
|
||||
<div className="studio-empty-msg">
|
||||
|
|
22
src/views/studio/studio-deleted.jsx
Normal file
22
src/views/studio/studio-deleted.jsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {selectStudioPublic} from '../../redux/studio';
|
||||
|
||||
const StudioDeleted = ({deleted}) => {
|
||||
if (!deleted) return null;
|
||||
return (<div className="studio-deleted studio-info-box studio-info-box-error">
|
||||
<FormattedMessage id="studio.showingDeleted" />
|
||||
</div>);
|
||||
};
|
||||
|
||||
StudioDeleted.propTypes = {
|
||||
deleted: PropTypes.bool
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
deleted: selectStudioPublic(state) === false
|
||||
})
|
||||
)(StudioDeleted);
|
|
@ -12,6 +12,7 @@ import {
|
|||
Errors, mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
|
||||
import '../../components/forms/inplace-input.scss';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
import decorateText from '../../lib/decorate-text.jsx';
|
||||
import StudioMuteEditMessage from './studio-mute-edit-message.jsx';
|
||||
|
@ -53,7 +54,7 @@ const StudioDescription = ({
|
|||
<React.Fragment>
|
||||
<textarea
|
||||
rows="20"
|
||||
className={fieldClassName}
|
||||
className={classNames('inplace-textarea', fieldClassName)}
|
||||
disabled={isMutating || isFetching || isMutedEditor}
|
||||
defaultValue={description}
|
||||
onBlur={e => {
|
||||
|
@ -68,7 +69,7 @@ const StudioDescription = ({
|
|||
{showMuteMessage && <StudioMuteEditMessage />}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={fieldClassName}>
|
||||
<div className={classNames('uneditable', fieldClassName)}>
|
||||
{decorateText(description, {
|
||||
usernames: true,
|
||||
hashtags: false,
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
/* eslint-disable react/jsx-no-bind */
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import onClickOutside from 'react-onclickoutside';
|
||||
|
||||
import {selectIsFollowing} from '../../redux/studio';
|
||||
import {selectCanFollowStudio} from '../../redux/studio-permissions';
|
||||
import {
|
||||
mutateFollowingStudio, selectIsMutatingFollowing, selectFollowingMutationError
|
||||
mutateFollowingStudio, selectIsMutatingFollowing, selectFollowingMutationError, Errors
|
||||
} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.PERMISSION: return 'studio.followErrors.confirmEmail';
|
||||
default: return 'studio.followErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioFollow = ({
|
||||
canFollow,
|
||||
|
@ -18,16 +27,24 @@ const StudioFollow = ({
|
|||
followingError,
|
||||
handleFollow
|
||||
}) => {
|
||||
if (!canFollow) return null;
|
||||
const fieldClassName = classNames('button', 'studio-follow-button', {
|
||||
'mod-mutating': isMutating
|
||||
});
|
||||
const [hideValidationMessage, setHideValidationMessage] = useState(false);
|
||||
|
||||
StudioFollow.handleClickOutside = () => {
|
||||
setHideValidationMessage(true);
|
||||
};
|
||||
if (!canFollow) return null;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="studio-info-section">
|
||||
<button
|
||||
className={fieldClassName}
|
||||
disabled={isMutating}
|
||||
onClick={() => handleFollow(!isFollowing)}
|
||||
onClick={() => {
|
||||
setHideValidationMessage(false);
|
||||
handleFollow(!isFollowing);
|
||||
}}
|
||||
>
|
||||
{isMutating ? '...' : (
|
||||
isFollowing ?
|
||||
|
@ -35,8 +52,11 @@ const StudioFollow = ({
|
|||
<FormattedMessage id="studio.followStudio" />
|
||||
)}
|
||||
</button>
|
||||
{followingError && <div>Error mutating following: {followingError}</div>}
|
||||
</React.Fragment >
|
||||
{followingError && !hideValidationMessage && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(followingError)} />}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -48,6 +68,10 @@ StudioFollow.propTypes = {
|
|||
handleFollow: PropTypes.func
|
||||
};
|
||||
|
||||
const clickOutsideConfig = {
|
||||
handleClickOutside: () => StudioFollow.handleClickOutside
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
canFollow: selectCanFollowStudio(state),
|
||||
|
@ -58,4 +82,4 @@ export default connect(
|
|||
{
|
||||
handleFollow: mutateFollowingStudio
|
||||
}
|
||||
)(StudioFollow);
|
||||
)(onClickOutside(StudioFollow, clickOutsideConfig));
|
||||
|
|
|
@ -10,15 +10,11 @@ import StudioStats from './studio-stats.jsx';
|
|||
import StudioTitle from './studio-title.jsx';
|
||||
|
||||
import {selectIsLoggedIn} from '../../redux/session';
|
||||
import {getInfo, getRoles} from '../../redux/studio';
|
||||
import {getRoles} from '../../redux/studio';
|
||||
|
||||
const StudioInfo = ({
|
||||
isLoggedIn, onLoadInfo, onLoadRoles
|
||||
isLoggedIn, onLoadRoles
|
||||
}) => {
|
||||
useEffect(() => { // Load studio info after first render
|
||||
onLoadInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() => { // Load roles info once the user is logged in is available
|
||||
if (isLoggedIn) onLoadRoles();
|
||||
}, [isLoggedIn]);
|
||||
|
@ -43,7 +39,6 @@ const StudioInfo = ({
|
|||
|
||||
StudioInfo.propTypes = {
|
||||
isLoggedIn: PropTypes.bool,
|
||||
onLoadInfo: PropTypes.func,
|
||||
onLoadRoles: PropTypes.func
|
||||
};
|
||||
|
||||
|
@ -52,7 +47,6 @@ export default connect(
|
|||
isLoggedIn: selectIsLoggedIn(state)
|
||||
}),
|
||||
{
|
||||
onLoadInfo: getInfo,
|
||||
onLoadRoles: getRoles
|
||||
}
|
||||
)(StudioInfo);
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
selectStudioHasReachedManagerThreshold
|
||||
} from '../../redux/studio.js';
|
||||
import {loadManagers} from './lib/studio-member-actions';
|
||||
import Debug from './debug.jsx';
|
||||
import {ManagerTile} from './studio-member-tile.jsx';
|
||||
import StudioInfoBox from './studio-info-box.jsx';
|
||||
import AlertProvider from '../../components/alert/alert-provider.jsx';
|
||||
|
@ -85,10 +84,15 @@ const StudioManagers = ({
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
{error && <div className="studio-section-load-error studio-info-box studio-info-box-error">
|
||||
<h3><FormattedMessage id="studio.sectionLoadError.managersHeadline" /></h3>
|
||||
<button
|
||||
className="button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="studio.sectionLoadError.tryAgain" />
|
||||
</button>
|
||||
</div>}
|
||||
<div className="studio-members-grid">
|
||||
{items.map(item =>
|
||||
(<ManagerTile
|
||||
|
|
|
@ -9,12 +9,15 @@ import {selectMuteStatus} from '../../redux/session';
|
|||
import {formatRelativeTime} from '../../lib/format-time.js';
|
||||
|
||||
const StudioMuteEditMessage = ({
|
||||
className,
|
||||
messageId,
|
||||
muteExpiresAtMs
|
||||
}) => (
|
||||
<ValidationMessage
|
||||
className={className}
|
||||
mode="info"
|
||||
message={<FormattedMessage
|
||||
id="studio.mutedEdit"
|
||||
id={messageId}
|
||||
values={{
|
||||
inDuration: formatRelativeTime(muteExpiresAtMs, window._locale)
|
||||
}}
|
||||
|
@ -24,9 +27,15 @@ const StudioMuteEditMessage = ({
|
|||
|
||||
|
||||
StudioMuteEditMessage.propTypes = {
|
||||
className: PropTypes.string,
|
||||
messageId: PropTypes.string,
|
||||
muteExpiresAtMs: PropTypes.number
|
||||
};
|
||||
|
||||
StudioMuteEditMessage.defaultProps = {
|
||||
messageId: 'studio.mutedEdit'
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
|
||||
|
|
|
@ -7,7 +7,6 @@ import classNames from 'classnames';
|
|||
|
||||
import {projects} from './lib/redux-modules';
|
||||
import {selectCanAddProjects, selectCanEditOpenToAll, selectShowProjectMuteError} from '../../redux/studio-permissions';
|
||||
import Debug from './debug.jsx';
|
||||
import StudioProjectAdder from './studio-project-adder.jsx';
|
||||
import StudioProjectTile from './studio-project-tile.jsx';
|
||||
import {loadProjects} from './lib/studio-project-actions.js';
|
||||
|
@ -49,12 +48,19 @@ const StudioProjects = ({
|
|||
</CommentingStatus>
|
||||
}
|
||||
{canAddProjects && <StudioProjectAdder />}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
|
||||
{error && <div className="studio-section-load-error studio-info-box studio-info-box-error">
|
||||
<h3><FormattedMessage id="studio.sectionLoadError.projectsHeadline" /></h3>
|
||||
<button
|
||||
className="button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="studio.sectionLoadError.tryAgain" />
|
||||
</button>
|
||||
</div>}
|
||||
|
||||
<div className="studio-projects-grid">
|
||||
{items.length === 0 && !loading ? (
|
||||
{items.length === 0 && !loading && !error ? (
|
||||
<div className="studio-empty">
|
||||
{canAddProjects ? (
|
||||
<React.Fragment>
|
||||
|
@ -62,7 +68,7 @@ const StudioProjects = ({
|
|||
width="388"
|
||||
height="265"
|
||||
className="studio-empty-img"
|
||||
src="/images/studios/projects-empty-can-add.png"
|
||||
src="/images/studios/empty-state-image.svg"
|
||||
/>
|
||||
<div className="studio-empty-msg">
|
||||
<div><FormattedMessage id="studio.projectsEmptyCanAdd1" /></div>
|
||||
|
@ -75,7 +81,7 @@ const StudioProjects = ({
|
|||
width="186"
|
||||
height="138"
|
||||
className="studio-empty-img"
|
||||
src="/images/studios/projects-empty.png"
|
||||
src="/images/studios/empty-state-image-mini.svg"
|
||||
/>
|
||||
<div className="studio-empty-msg">
|
||||
<div><FormattedMessage id="studio.projectsEmpty1" /></div>
|
||||
|
|
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
|||
import {FormattedMessage} from 'react-intl';
|
||||
import onClickOutside from 'react-onclickoutside';
|
||||
|
||||
import '../../components/forms/inplace-input.scss';
|
||||
import {selectStudioTitle, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo, selectShowEditMuteError} from '../../redux/studio-permissions';
|
||||
import {Errors, mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
|
||||
|
@ -48,7 +49,7 @@ const StudioTitle = ({
|
|||
{canEditInfo || isMutedEditor ? (
|
||||
<React.Fragment>
|
||||
<textarea
|
||||
className={fieldClassName}
|
||||
className={classNames('inplace-textarea', fieldClassName)}
|
||||
disabled={isMutating || !canEditInfo || isFetching}
|
||||
defaultValue={title}
|
||||
onKeyDown={e => e.key === 'Enter' && e.target.blur()}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
|
@ -25,6 +25,7 @@ import StudioActivity from './studio-activity.jsx';
|
|||
import StudioCuratorInvite from './studio-curator-invite.jsx';
|
||||
import StudioMeta from './studio-meta.jsx';
|
||||
import StudioAdminPanel from './studio-admin-panel.jsx';
|
||||
import StudioDeleted from './studio-deleted.jsx';
|
||||
|
||||
import {
|
||||
projects,
|
||||
|
@ -34,24 +35,30 @@ import {
|
|||
userProjects
|
||||
} from './lib/redux-modules';
|
||||
|
||||
const {getInitialState, studioReducer, selectStudioLoadFailed} = require('../../redux/studio');
|
||||
const {getInitialState, studioReducer, selectStudioLoadFailed, getInfo} = require('../../redux/studio');
|
||||
const {studioReportReducer} = require('../../redux/studio-report');
|
||||
const {commentsReducer} = require('../../redux/comments');
|
||||
const {studioMutationsReducer} = require('../../redux/studio-mutations');
|
||||
|
||||
import './studio.scss';
|
||||
import {selectMuteStatus} from '../../redux/session.js';
|
||||
import {selectIsAdmin, selectMuteStatus} from '../../redux/session.js';
|
||||
import {formatRelativeTime} from '../../lib/format-time.js';
|
||||
import CommentingStatus from '../../components/commenting-status/commenting-status.jsx';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {selectShowCuratorMuteError} from '../../redux/studio-permissions.js';
|
||||
|
||||
const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed}) => {
|
||||
const StudioShell = ({isAdmin, showCuratorMuteError, muteExpiresAtMs, studioLoadFailed, onLoadInfo}) => {
|
||||
const match = useRouteMatch();
|
||||
|
||||
useEffect(() => {
|
||||
onLoadInfo();
|
||||
}, [isAdmin]); // Reload any time isAdmin changes to allow admins to view deleted studios
|
||||
|
||||
return (
|
||||
studioLoadFailed ?
|
||||
<NotAvailable /> :
|
||||
<div className="studio-shell">
|
||||
<StudioDeleted />
|
||||
<StudioMeta />
|
||||
<div className="studio-info">
|
||||
<StudioInfo />
|
||||
|
@ -101,17 +108,23 @@ const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed})
|
|||
};
|
||||
|
||||
StudioShell.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
showCuratorMuteError: PropTypes.bool,
|
||||
muteExpiresAtMs: PropTypes.number,
|
||||
studioLoadFailed: PropTypes.bool
|
||||
studioLoadFailed: PropTypes.bool,
|
||||
onLoadInfo: PropTypes.func
|
||||
};
|
||||
|
||||
const ConnectedStudioShell = connect(
|
||||
state => ({
|
||||
showCuratorMuteError: selectShowCuratorMuteError(state),
|
||||
studioLoadFailed: selectStudioLoadFailed(state),
|
||||
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
|
||||
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0),
|
||||
isAdmin: selectIsAdmin(state)
|
||||
}),
|
||||
{
|
||||
onLoadInfo: getInfo
|
||||
}
|
||||
)(StudioShell);
|
||||
|
||||
render(
|
||||
|
|
|
@ -46,6 +46,13 @@ $radius: 8px;
|
|||
}
|
||||
}
|
||||
|
||||
.studio-deleted {
|
||||
/* Always make this box take up all the columns */
|
||||
grid-column: 1 / -1;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.studio-info {
|
||||
justify-self: center;
|
||||
width: 300px;
|
||||
|
@ -119,10 +126,6 @@ $radius: 8px;
|
|||
}
|
||||
|
||||
.studio-description {
|
||||
background: $ui-blue-10percent;
|
||||
padding: 15px 20px;
|
||||
border-color: transparent;
|
||||
border-radius: $radius;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
width: 300px;
|
||||
|
@ -135,17 +138,26 @@ $radius: 8px;
|
|||
height: 18rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.uneditable {
|
||||
background: $ui-blue-10percent;
|
||||
padding: 15px 20px;
|
||||
border-color: transparent;
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
/* Overrides for when title and description are editable textareas */
|
||||
}
|
||||
|
||||
/* Overrides for when title and description are editable textareas. These override inplace-input */
|
||||
textarea.studio-title, textarea.studio-description {
|
||||
background: transparent;
|
||||
padding: 5px 8px;
|
||||
border: 2px dashed $ui-blue-25percent;
|
||||
border-radius: $radius;
|
||||
resize: none;
|
||||
width: 300px;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.2em;
|
||||
&:not(:focus) {
|
||||
background: transparent;
|
||||
border: 2px dashed $ui-dark-gray;
|
||||
border-radius: $radius;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-image {
|
||||
|
@ -154,7 +166,7 @@ $radius: 8px;
|
|||
border-radius: 8px;
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid rgba(0, 0, 0, 0.15);
|
||||
border: 2px solid $box-shadow-light-gray;
|
||||
}
|
||||
|
||||
.studio-follow-button {
|
||||
|
@ -162,22 +174,32 @@ $radius: 8px;
|
|||
padding-bottom: 14px;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-section-load-error {
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.studio-tab-nav {
|
||||
border-bottom: 1px solid $active-dark-gray;
|
||||
padding-bottom: 8px;
|
||||
font-size: 14px;
|
||||
margin: 8px;
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid $box-shadow-light-gray;
|
||||
color: #575e75;
|
||||
padding: 0.5em 0.75em 0.5em 0.5em;
|
||||
padding: 0.75em 1.25em 0.75em 1em;
|
||||
&:active {
|
||||
padding: calc(0.5em) calc(0.75em) calc(0.5em) calc(0.5em);
|
||||
padding: calc(0.75em) calc(1.25em) calc(0.75em) calc(1em);
|
||||
}
|
||||
img {
|
||||
margin-right: 0.5em;
|
||||
|
@ -196,8 +218,15 @@ $radius: 8px;
|
|||
}
|
||||
}
|
||||
a.nav_link:hover > li {
|
||||
background: $ui-blue-25percent;
|
||||
border: 1px solid $ui-blue-10percent;
|
||||
background: $active-gray;
|
||||
border: 1px solid $active-gray;
|
||||
}
|
||||
a.active.nav_link:hover > li {
|
||||
background: $ui-blue-dark;
|
||||
color: white;
|
||||
img {
|
||||
filter: invert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,13 +265,10 @@ $radius: 8px;
|
|||
display: grid;
|
||||
|
||||
grid-template-columns: repeat(3, minmax(0,1fr));
|
||||
@media #{$medium-and-intermediate} {
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
||||
}
|
||||
@media #{$small} {
|
||||
& { grid-template-columns: repeat(1, minmax(0,1fr)); }
|
||||
}
|
||||
column-gap: 30px;
|
||||
column-gap: 20px;
|
||||
row-gap: 20px;
|
||||
|
||||
.studio-projects-load-more {
|
||||
|
@ -314,19 +340,20 @@ $radius: 8px;
|
|||
margin-top: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0,1fr));
|
||||
@media #{$medium-and-intermediate} {
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
||||
}
|
||||
@media #{$small} {
|
||||
& { grid-template-columns: repeat(1, minmax(0,1fr)); }
|
||||
}
|
||||
column-gap: 30px;
|
||||
column-gap: 20px;
|
||||
row-gap: 20px;
|
||||
.studio-members-load-more {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-tabs {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.studio-member-tile {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
|
@ -465,12 +492,20 @@ $radius: 8px;
|
|||
}
|
||||
}
|
||||
|
||||
.studio-comment-placholder-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.studio-managers-header {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.studio-compose-container {
|
||||
padding-top: 8px;
|
||||
|
||||
.load-more-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-comments-not-allowed {
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.header-wrapper {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
height: 10vh;
|
||||
}
|
||||
.header-content {
|
||||
width: calc(100% - 2rem);
|
||||
|
@ -40,7 +40,6 @@
|
|||
width: 90vw;
|
||||
max-width: 45rem;
|
||||
margin: 0 auto;
|
||||
padding-top: 4vw;
|
||||
text-align: center;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
color: white;
|
||||
|
@ -59,9 +58,13 @@
|
|||
opacity: 25%;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
img.main-img {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 1rem 1rem 2.5rem 1rem;
|
||||
margin: 1.5rem 1rem 2.5rem 1rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.125rem;
|
||||
|
@ -239,12 +242,12 @@
|
|||
</div>
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<h1>The Scratch Team is working hard to fix an issue with the Scratch website</h1>
|
||||
<h2 class="reload" onclick="location.reload()">Try reloading the page in a few minutes</h2>
|
||||
<img src="http://scratch-maintenance.s3.amazonaws.com/cairn.svg" />
|
||||
<h1>The Scratch Team is working on some planned updates to the website</h1>
|
||||
<h2 class="comment">Check <a href="https://twitter.com/scratch">@Scratch</a> for updates</h2>
|
||||
<hr>
|
||||
<div class="narrow">
|
||||
<p>While the website is being fixed, you can use the Scratch app to make projects offline</p>
|
||||
<p>While the website is being worked on, you can use the Scratch app to make projects offline</p>
|
||||
</div>
|
||||
<button class="toggleShowing showing"><span>Download Scratch app</span></button>
|
||||
<div class="grid-wrapper hidden">
|
||||
|
|
File diff suppressed because one or more lines are too long
36
static/images/studios/curators-empty-image.svg
Normal file
36
static/images/studios/curators-empty-image.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<svg width="179" height="111" viewBox="0 0 179 111" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.186 0.586919C151.349 8.2108 182.033 32.5008 178.762 70.0808C175.77 104.47 146.75 113.091 95.9136 110.421C45.0776 107.751 7.98193 85.766 7.98193 46.4483C7.98193 10.1617 54.4914 -3.07253 102.186 0.586919Z" fill="#4C97FF" fill-opacity="0.15"/>
|
||||
<path d="M21.8114 96.78C16.9592 90.5305 8.95811 81.5383 3.40773 85.2781C-2.14265 89.0179 2.73476 97.7817 6.62774 100.458C10.5207 103.134 15.3083 108.831 19.8754 109.08C24.4424 109.33 26.6636 103.03 21.8114 96.78Z" fill="#4C97FF" fill-opacity="0.15"/>
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="7" y="0" width="172" height="111">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.186 0.586919C151.348 8.2108 182.032 32.5008 178.762 70.0808C175.77 104.47 146.749 113.091 95.9132 110.421C45.0771 107.751 7.98145 85.766 7.98145 46.4483C7.98145 10.1617 54.4909 -3.07253 102.186 0.586919Z" fill="#C4C4C4"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M86.5648 67.4158C82.7664 63.9367 80.4314 58.9808 80.5268 53.5188C80.7066 43.2156 89.4663 35.0136 100.092 35.199C110.718 35.3845 119.186 43.8873 119.006 54.1905C118.911 59.6531 116.403 64.5252 112.485 67.8696C117.914 72.1038 121.963 79.2588 124.073 88.9688C128.687 110.21 127.917 121.698 98.5911 121.186C68.2651 120.657 68.9433 106.488 71.7591 92.2628C71.9043 91.5293 72.0537 90.7743 72.2033 89.9976C72.803 86.8833 73.7502 83.9571 74.98 81.2671C74.9108 81.2513 74.8414 81.2354 74.7719 81.2192C67.0317 79.4169 57.4187 74.7999 51.204 63.7656C49.2966 60.379 50.5182 56.1011 53.9326 54.2105C57.347 52.32 61.6612 53.5327 63.5686 56.9193C67.3267 63.5917 72.9763 66.3746 78.0055 67.5456C80.553 68.1388 82.8766 68.2938 84.5566 68.2971C84.826 68.2977 85.0766 68.2943 85.3064 68.2884C85.7202 67.9846 86.1398 67.6936 86.5648 67.4158Z" fill="#4C97FF" fill-opacity="0.4"/>
|
||||
</g>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.7908 19.9693L63.1276 25.1415L59.0583 28.3541C57.6709 29.4473 58.1406 31.6553 59.8518 32.0917L64.8776 33.3684L66.6736 38.2323C67.2851 39.8882 69.531 40.1243 70.4731 38.632L73.2406 34.2474L78.4215 34.043C80.1864 33.9724 81.1034 31.9102 79.9766 30.5519L76.6609 26.5654L78.0666 21.5754C78.5457 19.8744 76.8684 18.3636 75.228 19.0185L70.4097 20.9372L66.1002 18.0586C64.6313 17.0797 62.6776 18.2077 62.7908 19.9693Z" fill="#E9F1FC"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M137.526 57.3692L135.202 58.0676L133.307 56.5494C132.662 56.0316 131.704 56.4784 131.685 57.3052L131.632 59.7326L129.602 61.0644C128.911 61.5178 129.04 62.5673 129.82 62.84L132.112 63.6408L132.751 65.9831C132.969 66.7808 134.007 66.982 134.508 66.3248L135.977 64.3921L138.402 64.5079C139.229 64.5473 139.741 63.6229 139.27 62.9434L137.887 60.9472L138.746 58.6778C139.038 57.9045 138.318 57.132 137.526 57.3692Z" fill="#4C97FF" fill-opacity="0.5"/>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="35.3035" y="18.196" width="105.873" height="116.006" 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"/>
|
||||
<feOffset dy="-2"/>
|
||||
<feGaussianBlur stdDeviation="7.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 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="54.2454" y="14.6963" width="30.2257" height="29.9284" 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"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.298039 0 0 0 0 0.592157 0 0 0 0 1 0 0 0 0.25 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: 4.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
22
static/images/studios/empty-state-image-mini.svg
Normal file
22
static/images/studios/empty-state-image-mini.svg
Normal file
|
@ -0,0 +1,22 @@
|
|||
<svg width="200" height="138" viewBox="0 0 200 138" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M117.145 0.625752C170.613 9.41628 203.293 47.8375 199.736 91.1682C196.482 130.82 165.302 133.78 109.632 137.681C53.9624 141.582 13.9998 109.254 13.9998 63.9193C13.9998 13.0914 65.2734 -3.59368 117.145 0.625752Z" fill="#4C97FF" fill-opacity="0.15"/>
|
||||
<path d="M23.4733 117.719C18.2514 110.952 9.64069 101.215 3.66741 105.265C-2.30586 109.314 2.94316 118.804 7.13277 121.702C11.3224 124.6 16.4747 130.769 21.3898 131.039C26.3048 131.309 28.6952 124.487 23.4733 117.719Z" fill="#4C97FF" fill-opacity="0.15"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.2359 105.989C92.0166 108.936 142 101.342 142 101.342C146.775 100.146 153.355 98.9547 151.985 97.3596C142.352 79.1195 142.091 58.8004 136.55 39.399C136.42 38.9438 135.986 38.645 135.514 38.6825L62.7137 44.4631C62.7139 54.0953 64.7039 100.431 76.2359 105.989Z" fill="#E5F0FF"/>
|
||||
</g>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M81.7248 101.134C95.0988 102.623 136.821 96.8239 136.821 96.8239C140.911 95.9883 146.538 95.1175 145.405 94.1628C139.55 83.1014 138.528 70.0069 136.955 58.9666C136.881 58.4451 136.413 58.0735 135.888 58.1158L68.979 63.5031C68.979 69.8762 72.0221 97.9473 81.7248 101.134Z" fill="#4C97FF"/>
|
||||
<path d="M131.605 48.7576C130.029 41.7323 129.392 37.2941 129.029 30.5029C129.003 30.0226 128.38 29.8472 128.11 30.2452L127.865 30.6056C127.692 30.8606 127.332 30.9 127.108 30.6886L126.245 29.8737C126.012 29.6538 125.635 29.7067 125.472 29.9824L124.885 30.9713C124.722 31.247 124.345 31.3 124.112 31.08L123.276 30.2909C123.043 30.071 122.666 30.124 122.503 30.3996L121.903 31.4116C121.744 31.6797 121.381 31.7387 121.146 31.5347L120.486 30.9632C120.172 30.6916 119.684 30.9004 119.664 31.3149C119.303 38.9309 120.02 43.4923 122.332 50.7102C122.463 51.1191 123.015 51.1818 123.234 50.8125L123.738 49.9614C123.902 49.6857 124.279 49.6327 124.512 49.8527L125.348 50.6417C125.581 50.8617 125.958 50.8087 126.121 50.5331L126.707 49.5441C126.87 49.2685 127.247 49.2155 127.48 49.4354L128.316 50.2245C128.549 50.4445 128.926 50.3915 129.09 50.1158L129.695 49.0946C129.852 48.8296 130.209 48.7682 130.446 48.9657L130.793 49.2557C131.161 49.5629 131.709 49.2252 131.605 48.7576Z" fill="#4C97FF" fill-opacity="0.25"/>
|
||||
<path d="M81.5661 53.2015C80.6089 46.0656 80.3613 41.5888 80.5909 34.7917C80.6071 34.3111 80.0016 34.082 79.698 34.455L79.4231 34.7927C79.2286 35.0316 78.8664 35.0395 78.6617 34.8094L77.8727 33.9224C77.6597 33.6829 77.2796 33.7028 77.0928 33.9632L76.4227 34.8973C76.2359 35.1577 75.8559 35.1776 75.6429 34.9382L74.8788 34.0793C74.6658 33.8398 74.2858 33.8597 74.099 34.1201L73.4133 35.076C73.2316 35.3292 72.865 35.3563 72.648 35.1326L72.0403 34.5057C71.7514 34.2078 71.2469 34.3733 71.1912 34.7845C70.168 42.34 70.485 46.9466 72.1588 54.3385C72.2536 54.7572 72.7979 54.8678 73.0482 54.519L73.6249 53.7151C73.8117 53.4547 74.1917 53.4348 74.4047 53.6742L75.1688 54.5331C75.3818 54.7726 75.7618 54.7527 75.9486 54.4923L76.6187 53.5582C76.8055 53.2978 77.1856 53.2779 77.3986 53.5173L78.1626 54.3762C78.3756 54.6157 78.7557 54.5958 78.9425 54.3354L79.6345 53.3708C79.814 53.1204 80.175 53.0905 80.3934 53.3078L80.7143 53.627C81.0539 53.965 81.6298 53.6765 81.5661 53.2015Z" fill="#4C97FF" fill-opacity="0.25"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.364 88.3877C113.351 87.7753 117.17 85.3054 116.59 80.5877C116.447 79.4217 116.117 78.4608 115.618 77.6845L114.933 70.8494C114.84 70.096 113.896 69.775 113.325 70.3003L109.813 73.5175C108.866 73.1784 107.723 73.1185 106.507 73.2678C105.296 73.4166 104.22 73.7489 103.384 74.3068L99.197 72.035C98.4972 71.6657 97.6587 72.2057 97.7702 72.9567L98.7589 79.7545C98.46 80.6288 98.3745 81.6408 98.5176 82.8068C99.0969 87.5244 103.378 88.9999 108.364 88.3877ZM109.904 85.2504C110.646 85.1594 111.308 84.7685 111.716 84.1721C111.95 83.872 111.858 83.4446 111.525 83.2305C111.212 83.0322 110.76 83.1242 110.531 83.4454C110.349 83.7027 110.061 83.8655 109.739 83.9051C109.132 83.9796 108.563 83.5761 108.49 82.9841L108.369 81.9975C109.434 81.5754 110.16 80.5958 110.09 80.02C110.002 79.3025 108.807 79.4492 107.366 79.6262C105.907 79.8052 104.731 79.9496 104.82 80.6671C104.89 81.2429 105.794 82.0223 106.944 82.1542L107.068 83.1587C107.14 83.7507 106.707 84.2774 106.098 84.3521C105.758 84.3938 105.438 84.3057 105.201 84.0999C104.919 83.8414 104.459 83.8614 104.184 84.1319C103.914 84.42 103.946 84.8549 104.227 85.0917C104.767 85.5716 105.504 85.7906 106.263 85.6975C106.965 85.6113 107.574 85.2469 107.993 84.7403C108.506 85.1326 109.183 85.3389 109.904 85.2504Z" fill="#E5F0FF"/>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="58.7137" y="38.6792" width="97.4555" height="75.9893" 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"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.298039 0 0 0 0 0.592157 0 0 0 0 1 0 0 0 0.5 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: 5.2 KiB |
79
static/images/studios/empty-state-image.svg
Normal file
79
static/images/studios/empty-state-image.svg
Normal file
|
@ -0,0 +1,79 @@
|
|||
<svg width="397" height="265" viewBox="0 0 397 265" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M218.001 1.27839C327.899 17.1401 395.069 86.4675 387.759 164.654C381.07 236.202 323.68 256.864 209.257 263.904C94.8335 270.943 0 235.307 0 122.446C-1.30571e-05 18.319 111.384 -6.33517 218.001 1.27839Z" fill="#4C97FF" fill-opacity="0.2"/>
|
||||
<path d="M389.256 35.8627C378.999 23.6144 362.086 5.99053 350.354 13.3201C338.621 20.6498 348.931 37.8259 357.16 43.071C365.389 48.3161 375.509 59.4806 385.163 59.9696C394.817 60.4586 399.512 48.1111 389.256 35.8627Z" fill="#4C97FF" fill-opacity="0.15"/>
|
||||
</g>
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="1" y="0" width="389" height="265">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M219.004 1.2698C328.903 17.0248 396.073 85.886 388.763 163.546C381.832 237.177 315.557 260.745 196.372 264.756C83.2791 268.562 1.00342 228.151 1.00342 121.622C1.00342 18.1957 112.387 -6.29257 219.004 1.2698Z" fill="#4C97FF"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<g filter="url(#filter1_d)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M265.722 144.322C271.325 146.752 309.94 147.011 309.94 147.011C313.45 146.655 318.225 146.547 317.482 145.019C317.482 145.019 314.096 131.817 314.58 117.66C315.064 103.503 316.008 93.5654 314.642 93.4459L262.095 88.3739C260.728 88.2544 260.347 92.2973 260.189 94.0987C260.189 94.0987 257.439 125.926 258.618 133.285C259.797 140.644 260.12 141.892 265.722 144.322Z" fill="#E5F0FF"/>
|
||||
</g>
|
||||
<path d="M292.826 98.5732C292.33 91.7448 292.306 87.3804 292.818 80.7143C292.854 80.2359 292.262 79.9808 291.942 80.3382L291.702 80.6059C291.495 80.837 291.13 80.8259 290.937 80.5827L290.256 79.7217C290.056 79.4688 289.672 79.4688 289.472 79.7217L288.814 80.5528C288.614 80.8058 288.23 80.8058 288.03 80.5528L287.372 79.7217C287.172 79.4688 286.788 79.4688 286.588 79.7217L285.913 80.5741C285.719 80.8201 285.348 80.8281 285.143 80.5906L284.638 80.006C284.365 79.69 283.848 79.8302 283.773 80.2408C282.442 87.4855 282.459 91.9703 283.713 99.1974C283.786 99.6209 284.325 99.758 284.592 99.4209L285.146 98.7209C285.346 98.468 285.73 98.468 285.93 98.7209L286.588 99.5521C286.788 99.805 287.172 99.805 287.372 99.5521L288.03 98.7209C288.23 98.468 288.614 98.468 288.814 98.7209L289.472 99.5521C289.672 99.805 290.056 99.805 290.256 99.5521L290.937 98.6911C291.13 98.4479 291.495 98.4368 291.702 98.6679L291.951 98.9463C292.27 99.3026 292.861 99.0502 292.826 98.5732Z" fill="#4C97FF" fill-opacity="0.5"/>
|
||||
<g filter="url(#filter2_d)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M114.841 169.985C140.928 174.854 223.556 162.306 223.556 162.306C231.45 160.331 242.329 158.362 240.064 155.726C224.055 125.428 223.703 91.6588 214.401 59.4481C214.269 58.9933 213.835 58.6944 213.364 58.7318L92.4867 68.3252C92.4869 84.2406 95.7767 160.802 114.841 169.985Z" fill="#E5F0FF"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M123.915 161.963C146.024 164.423 214.996 154.841 214.996 154.841C221.757 153.46 231.06 152.022 229.186 150.444C219.399 131.965 217.782 110.051 215.131 91.6833C215.056 91.1619 214.588 90.7922 214.063 90.8345L102.844 99.7849C102.844 110.315 107.875 156.697 123.915 161.963Z" fill="#4C97FF" fill-opacity="0.25"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M104.786 78.7004C104.764 78.3944 104.89 78.1045 105.154 77.9561C106.929 76.9597 109.627 77.1253 111.423 78.4017C112.796 79.3777 114.697 79.7042 116.31 79.3582C117.113 79.1858 118.179 79.7405 118.237 80.545L118.647 86.1977C118.669 86.5036 118.543 86.7934 118.279 86.9418C116.503 87.939 113.805 87.7734 112.009 86.4962C110.635 85.5202 108.735 85.1937 107.123 85.5396C106.319 85.7121 105.254 85.1575 105.195 84.3529L104.786 78.7004Z" fill="#59C059"/>
|
||||
<path d="M106.885 92.6572C103.809 88.5793 105.168 77.3246 105.168 77.3246" stroke="#389438" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206.586 76.3677C203.748 63.8642 202.671 56.3696 202.063 44.2804C202.039 43.8001 201.417 43.6221 201.147 44.02L199.975 45.7459C199.803 46.0007 199.443 46.0401 199.219 45.8288L197.215 43.9389C196.982 43.719 196.605 43.7719 196.442 44.0475L195.057 46.3838C194.893 46.6593 194.517 46.7122 194.283 46.4924L192.307 44.6283C192.074 44.4084 191.698 44.4613 191.534 44.7369L190.135 47.0962C189.976 47.3642 189.614 47.4231 189.378 47.2192L187.49 45.5849C187.176 45.3134 186.687 45.5233 186.666 45.9376C185.984 59.1655 187.2 66.7949 191.265 79.3324C191.397 79.7407 191.949 79.8033 192.168 79.4342L193.577 77.0588C193.74 76.7833 194.117 76.7303 194.35 76.9502L196.326 78.8143C196.559 79.0342 196.936 78.9812 197.099 78.7057L198.485 76.3694C198.648 76.0939 199.025 76.041 199.258 76.2608L201.234 78.1249C201.467 78.3448 201.844 78.2918 202.007 78.0163L203.411 75.6477C203.569 75.3828 203.925 75.3216 204.162 75.5189L205.777 76.8664C206.145 77.1734 206.692 76.835 206.586 76.3677Z" fill="#4C97FF" fill-opacity="0.5"/>
|
||||
<path d="M123.782 83.7254C122.045 71.0222 121.626 63.4623 122.074 51.3661C122.092 50.8856 121.488 50.6541 121.184 51.0269L119.867 52.6442C119.672 52.883 119.31 52.8909 119.106 52.6609L117.275 50.6036C117.062 50.3643 116.682 50.3842 116.495 50.6445L114.911 52.8512C114.724 53.1114 114.344 53.1313 114.131 52.892L112.325 50.8629C112.112 50.6236 111.733 50.6435 111.546 50.9037L109.946 53.1322C109.765 53.3853 109.398 53.4124 109.181 53.1888L107.442 51.3962C107.154 51.0984 106.649 51.265 106.591 51.6758C104.758 64.7939 105.304 72.5003 108.261 85.3441C108.357 85.7624 108.901 85.8729 109.152 85.5242L110.762 83.2806C110.949 83.0203 111.329 83.0004 111.542 83.2397L113.348 85.2689C113.561 85.5082 113.941 85.4883 114.127 85.2281L115.711 83.0213C115.898 82.7611 116.278 82.7412 116.491 82.9805L118.297 85.0097C118.51 85.249 118.89 85.2291 119.077 84.9688L120.682 82.7316C120.862 82.4814 121.223 82.4515 121.441 82.6686L122.932 84.1517C123.272 84.4896 123.847 84.2001 123.782 83.7254Z" fill="#4C97FF" fill-opacity="0.5"/>
|
||||
<path opacity="0.7" fill-rule="evenodd" clip-rule="evenodd" d="M131.092 77.1781C130.567 77.2519 130.093 77.5312 129.774 77.9545L127.463 81.0193C127.144 81.443 127.006 81.9762 127.08 82.5016L127.614 86.3009C127.688 86.8263 127.967 87.3009 128.391 87.6201L131.458 89.9296C131.881 90.2487 132.414 90.3865 132.939 90.3127L136.742 89.7785C137.267 89.7047 137.742 89.4254 138.061 89.0021L140.372 85.9373C140.691 85.5136 140.829 84.9804 140.755 84.455L140.221 80.6557C140.147 80.1303 139.867 79.6557 139.443 79.3365L136.377 77.027C135.953 76.7079 135.421 76.5701 134.895 76.6439L131.092 77.1781Z" fill="#EC5858"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.953 140.903C176.197 139.891 182.509 135.811 181.551 128.015C181.314 126.088 180.769 124.501 179.943 123.218L178.81 111.924C178.657 110.679 177.096 110.148 176.152 111.016L170.346 116.332C168.782 115.771 166.892 115.672 164.883 115.919C162.879 116.165 161.101 116.714 159.719 117.635L152.798 113.881C151.641 113.271 150.255 114.163 150.439 115.404L152.074 126.637C151.58 128.081 151.439 129.753 151.676 131.68C152.634 139.476 159.711 141.914 167.953 140.903ZM170.498 135.719C171.724 135.569 172.819 134.923 173.493 133.937C173.881 133.441 173.727 132.735 173.178 132.381C172.66 132.054 171.912 132.206 171.535 132.736C171.234 133.161 170.758 133.43 170.225 133.496C169.222 133.619 168.281 132.952 168.16 131.974L167.96 130.344C169.72 129.646 170.921 128.028 170.804 127.076C170.659 125.891 168.684 126.133 166.301 126.425C163.89 126.721 161.947 126.959 162.093 128.145C162.209 129.096 163.704 130.384 165.605 130.602L165.809 132.262C165.929 133.24 165.212 134.111 164.206 134.234C163.645 134.303 163.115 134.157 162.723 133.817C162.257 133.39 161.497 133.423 161.043 133.87C160.595 134.346 160.649 135.065 161.114 135.456C162.007 136.249 163.225 136.611 164.479 136.457C165.639 136.315 166.647 135.713 167.34 134.876C168.186 135.524 169.307 135.865 170.498 135.719Z" fill="#E5F0FF"/>
|
||||
<path d="M292.617 185.109L316.288 170.838L372.924 222.499C372.924 222.499 370.224 245.337 342.163 243.983C332.693 237.236 292.617 185.109 292.617 185.109Z" fill="#ECC293"/>
|
||||
<path d="M304.248 121.158C302.434 115.649 301.033 111.393 297.423 112.538C291.948 114.276 295.946 123.832 296.902 131.9C296.861 133.773 297.052 135.9 297.248 138.066C297.456 140.371 297.668 142.722 297.607 144.859C297.605 144.931 297.543 144.988 297.471 144.984C297.408 144.982 297.357 144.935 297.349 144.873C296.532 139.002 295.086 133.24 292.995 127.683C290.503 121.059 287.556 116.017 284.729 117.016C281.901 118.015 281.629 124.195 284.122 130.818C284.911 132.916 285.379 134.985 285.9 136.745C286.134 137.538 286.206 138.372 286.151 139.199C286.037 140.913 286.145 142.905 286.263 145.089C286.416 147.93 287.581 153.536 287.409 157.778C287.301 160.465 284.426 161.053 282.572 159.122C282.215 158.75 281.868 158.368 281.534 157.974L281.533 157.974C277.977 153.793 274.357 149.537 270.298 150.939C267.508 151.902 270.137 157.24 274.556 164.525C274.688 164.743 274.803 164.97 274.904 165.203C278.492 173.51 286.853 181.65 294.741 186.754C295.11 186.993 295.525 187.18 295.945 187.306C320.574 194.728 321.634 187.8 323.999 178.083C328.729 158.651 330.624 143.146 327.138 131.227C325.724 126.391 323.847 122.676 321.621 123.304C319.394 123.933 318.736 128.362 320.15 133.199C320.34 133.847 320.486 134.49 320.612 135.113C320.816 136.124 320.653 137.171 320.386 138.17C320.065 139.367 319.856 140.784 319.62 142.377C319.582 142.635 319.187 142.607 319.179 142.346C319.002 136.808 317.977 132.89 316.407 123.906C314.824 117.621 312.636 112.749 309.877 113.432C307.119 114.115 306.166 119.764 307.75 126.05C308.242 128.003 308.479 130.054 308.801 131.898C309.26 140.025 308.038 146.338 308.115 144.909C308.38 139.891 307.836 133.051 306.208 128.156C306.121 127.895 306.052 127.63 306 127.36C305.592 125.246 304.922 123.202 304.248 121.158Z" fill="#ECC293"/>
|
||||
<path d="M312.019 131.757C308.793 120.277 310.701 116.838 310.496 113.405C305.001 114.373 308.487 128.744 308.921 131.208C309.521 134.607 309.289 141.549 308.448 145.531C310.725 146.772 313.538 137.162 312.019 131.757Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M300.219 130.262C296.962 118.994 296.066 115.897 298.168 112.272C291.219 113.496 295.91 127.592 296.343 130.046C296.941 133.431 297.446 145.668 297.726 147.254C302.469 149.146 301.826 135.819 300.272 130.444L300.219 130.262Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M288.732 135.976C284.454 125.158 283.527 120.456 285.337 116.737C278.888 118.123 285.074 132.763 285.922 137.573C285.738 146.922 288.399 156.207 287.006 159.263C286.787 159.533 286.548 159.81 286.291 160.1C286.6 159.924 286.834 159.641 287.006 159.263C290.26 155.268 289.495 152.874 288.732 135.976Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M323.198 134.836C320.561 126.958 322.202 125.866 322.194 123.466C317.929 124.218 320.656 134.624 320.957 136.33C321.372 138.684 319.578 139.873 320.236 143.605C322.274 143.796 324.44 138.545 323.198 134.836Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M90.1627 135.318C90.0912 136.382 90.1191 139.257 90.3113 140.141C91.4607 145.428 93.3641 147.841 96.293 147.272C99.7115 146.608 101.666 145.89 101.366 138.098C99.9523 130.827 103.115 119.429 92.4879 123.166C89.232 124.31 90.2145 127.512 90.1627 135.318Z" fill="#B5875C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.9978 131.8C78.2647 132.014 79.1711 132.394 79.8134 132.928C79.9563 131.97 80.1634 131.152 80.4425 130.513C80.5677 129.959 80.7371 129.352 80.9533 128.687C81.7851 126.129 85.4329 125.866 89.9186 126.906C90.0327 126.104 90.2608 125.169 90.6159 124.077C91.5883 121.086 96.4343 121.24 101.975 122.949C102.281 121.844 102.615 120.927 102.993 120.209C103.695 116.906 109.941 116.862 117.322 119.974C120.483 121.307 123.443 121.973 125.821 122.507C129.4 123.311 131.66 123.819 131.299 125.861C130.699 129.259 124.297 131.211 116.281 129.799C114.907 129.557 113.563 129.396 112.289 129.248C112.242 130.214 112.175 131.226 112.106 132.277C111.953 134.584 111.789 137.078 111.788 139.681C111.788 141.032 111.785 142.352 111.774 143.629C111.81 143.788 111.845 143.946 111.88 144.103C112.598 147.321 113.274 150.352 115.192 151.648C116.504 152.535 117.761 153.238 118.892 153.87C123.595 156.497 126.111 157.902 121.281 166.264C115.289 176.637 102.648 192.541 91.567 192.543C71.0143 201.565 71.0976 175.719 71.1615 155.919C71.1644 155.004 71.1673 154.101 71.1682 153.216C71.0103 151.959 71.155 150.445 71.3159 148.761C71.4459 147.401 71.5864 145.931 71.5866 144.397C71.5874 137.44 74.0101 131.801 76.9978 131.8Z" fill="#67462D"/>
|
||||
<path d="M110.515 180.892C110.515 180.892 82.7766 173.522 75.7372 172.338C76.6262 183.214 27.9302 223.585 27.9302 223.585C27.9302 223.585 31.8593 246.069 60.4011 243.189C69.7085 235.984 110.515 180.892 110.515 180.892Z" fill="#67462D"/>
|
||||
<path d="M79.6788 132.994C79.6788 132.994 78.8772 131.374 75.7017 131.858C73.395 132.843 71.5109 139.004 71.8713 139.491L74.998 136.084C75.6832 135.9 76.7356 137.087 76.9192 137.772C77.4767 139.852 79.6323 134.093 79.6788 132.994Z" fill="black" fill-opacity="0.2"/>
|
||||
<path d="M80.1132 133.245C80.1234 133.074 80.1239 132.873 80.1245 132.647C80.1276 131.394 80.1325 129.36 81.8202 127.282C86.0901 125.827 88.9624 126.881 89.7465 127.24C89.9452 126.14 90.1772 125.05 90.7965 123.776C91.3014 121.45 96.1146 121.718 97.6484 121.939C100.322 122.324 101.625 123.076 101.625 123.076C100.912 124.98 99.1973 129.086 98.047 130.275C97.2433 131.106 96.0802 129.74 94.9713 128.437C94.0963 127.409 93.2549 126.42 92.6504 126.582C91.6101 126.861 90.146 128.355 89.4465 129.178C88.7939 131.299 87.8898 133.537 87.0823 133.213C85.5963 132.617 84.9482 131.797 84.4322 131.143C83.8051 130.349 83.3731 129.802 81.8693 130.205C81.3211 130.352 80.4701 132.293 80.1132 133.245Z" fill="black" fill-opacity="0.2"/>
|
||||
<path d="M110.15 123.364C107.663 122.856 106.889 129.01 106.754 131.248C107.286 137.346 107.017 141.823 112.713 148.005C110.927 144.079 111.968 134.257 112.74 128.912C115.246 129.586 122.538 130.396 126.175 129.716C129.812 129.035 131.451 127.1 131.335 125.295C130.939 125.646 129.485 126.993 126.56 127.043C124.317 127.081 113.258 124 110.15 123.364Z" fill="black" fill-opacity="0.2"/>
|
||||
<path d="M55.0789 153.377C50.5665 148.381 53.0736 141.347 53.0736 141.347C53.0736 141.347 124.477 129.066 124.687 129.267C128.326 133.819 126.875 140.587 126.875 140.587C126.875 140.587 57.9523 152.641 55.0789 153.377Z" fill="#FFBF00"/>
|
||||
<path d="M54.0117 153.705C54.5517 153.601 55.2971 153.448 56.1746 153.248C56.8899 147.697 55.3167 143.112 53.5543 141.27L51.6962 141.628C52.0673 142.196 55.2917 147.374 54.0117 153.705Z" fill="#0FBD8C"/>
|
||||
<path d="M60.0955 152.374C61.6015 145.354 57.0888 140.59 57.0888 140.59L55.4645 140.903C56.482 142.493 59.1209 147.263 58.2089 152.751C58.658 152.634 59.6221 152.509 60.0955 152.374Z" fill="#0FBD8C"/>
|
||||
<path d="M124.703 129.265L135.696 132.194C136.515 132.412 136.708 133.485 136.017 133.975L126.954 140.408L125.314 138.335L126.82 136.329L125.315 134.825L125.817 132.744L124.312 131.24L124.703 129.265Z" fill="#F6DDC3"/>
|
||||
<path d="M131.789 131.038C131.789 131.038 133.341 132.819 132.822 136.267L136.068 133.948C136.749 133.461 136.567 132.403 135.762 132.173L131.789 131.038Z" fill="black" fill-opacity="0.5"/>
|
||||
<path d="M48.8736 154.225C42.2573 150.481 45.7812 143.535 47.2415 142.755C48.7018 141.975 51.7767 141.554 51.7767 141.554C51.7767 141.554 55.2386 147.02 54.1062 153.699C53.0848 154.065 50.6929 154.835 48.8736 154.225Z" fill="#FF6680"/>
|
||||
<path d="M45.0452 149.369C45.5467 150.872 47.5528 155.383 51.5669 154.38C57.0856 152.375 127.131 140.512 127.131 140.512L126.469 136.756C126.469 136.756 75.6486 146.859 52.5711 150.719C48.0559 151.874 45.8856 150.719 45.0452 149.369Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M110.378 158.701C106.703 168.792 109.412 172.675 112.554 173.818C115.695 174.96 123.66 167.272 126.777 153.086C130.452 142.995 132.529 135.712 129.364 135.224C124.278 134.44 120.881 142.091 117.359 148.573C116.168 150.765 111.307 156.15 110.378 158.701Z" fill="#67462D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M202.816 152.23C201.308 146.15 199.314 141.991 196.764 142.44C193.471 143.02 192.563 149.954 194.151 158.954C194.845 162.89 194.977 167.518 194.977 170.242C193.677 178.236 190.942 187.536 190.942 193.761C185.961 185.414 178.746 179.36 175.913 180.994C170.047 184.379 172.922 188.883 176.882 195.084L176.888 195.094C177.799 196.521 178.768 198.038 179.7 199.653C180.745 201.461 181.851 203.139 182.974 204.645C183.18 205.169 183.436 205.736 183.745 206.354C189.258 217.374 194.977 221.633 202.816 226.647C209.838 237.679 219.474 276.578 227.113 282.306C256.712 284.686 265.389 272.779 265.389 272.779L228.876 213.719C227.78 211.947 227.707 209.742 228.472 207.804C231.372 200.463 233.246 190.241 233.246 183.524C232.278 173.997 232.78 170.988 223.676 169.551C221.359 166.495 217.933 162.496 215.689 162.238C214.778 160.25 212.581 158.098 209.838 156.032C207.327 154.141 204.873 152.709 202.816 152.23ZM205.81 178.883C205.859 178.665 205.907 178.45 205.955 178.236C206.315 176.627 206.651 175.128 207.048 173.804L207.037 173.614L207.022 173.332V173.33C206.957 172.128 206.887 170.829 206.653 169.502C206.536 168.835 206.439 168.191 206.365 167.573C206.346 167.555 206.327 167.536 206.308 167.518C206.488 170.477 206.18 174.405 205.555 178.831C205.639 178.847 205.724 178.865 205.81 178.883Z" fill="#B5875C"/>
|
||||
<path d="M206.042 164.735L203.032 152.199C207.48 154.422 212.062 157.214 212.564 158.718C207.046 156.948 213.078 169.093 211.208 177.507C211.208 177.507 208.198 184.026 205.689 179.011C206.191 175.501 207.329 174.744 207.046 172.758C206.544 169.248 206.377 165.237 206.042 164.735Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M217.581 170.752L215.574 161.727C216.912 163.565 217.681 162.529 220.089 165.738C223.099 169.749 221.594 178.274 221.093 180.781C220.892 181.784 218.584 184.792 216.578 180.781C216.578 178.775 217.581 174.764 217.581 170.752Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M223.601 169.75L223.613 169.768C224.685 171.349 227.919 170.359 228.767 173.629C229.417 176.137 229.164 178.324 228.767 180.516C228.494 182.02 225.398 184.151 225.398 182.397C225.398 180.644 226.192 177.137 226.192 173.629L223.613 169.768C223.609 169.762 223.605 169.756 223.601 169.75Z" fill="black" fill-opacity="0.15"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i" x="0" y="0" width="395.027" height="269.756" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<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" result="hardAlpha"/>
|
||||
<feOffset dy="5"/>
|
||||
<feGaussianBlur stdDeviation="7"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.298039 0 0 0 0 0.592157 0 0 0 0 1 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="254.324" y="88.3713" width="67.2353" height="66.6402" 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"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.298039 0 0 0 0 0.592157 0 0 0 0 1 0 0 0 0.5 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="88.4867" y="58.7288" width="155.881" height="120.379" 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"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.298039 0 0 0 0 0.592157 0 0 0 0 1 0 0 0 0.5 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: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.7 KiB |
|
@ -43,9 +43,9 @@ tap.test('setComments', t => {
|
|||
|
||||
const commentState = {
|
||||
comments: [
|
||||
{id: 'id1', visibility: 'visible'},
|
||||
{id: 'id2', visibility: 'visible'},
|
||||
{id: 'id3', visibility: 'visible'}
|
||||
{id: 'id1', visibility: 'visible', reply_count: 2},
|
||||
{id: 'id2', visibility: 'visible', reply_count: 0},
|
||||
{id: 'id3', visibility: 'visible', reply_count: 0}
|
||||
],
|
||||
replies: {
|
||||
id1: [
|
||||
|
@ -108,6 +108,12 @@ tap.test('addNewComment, reply comment', t => {
|
|||
state = reducer(commentState, Comments.addNewComment({id: 'new comment'}, 'id1'));
|
||||
// Adds replies to the end of the replies list
|
||||
t.equal(state.replies.id1[2].id, 'new comment');
|
||||
t.equal(state.comments[0].reply_count, 3);
|
||||
|
||||
// Also check a top-level comment that doesn't have replies yet
|
||||
state = reducer(commentState, Comments.addNewComment({id: 'new comment2'}, 'id2'));
|
||||
t.equal(state.replies.id2[0].id, 'new comment2');
|
||||
t.equal(state.comments[1].reply_count, 1);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
|
@ -39,6 +39,19 @@ describe('Studio comments', () => {
|
|||
expect(child.prop('isOpen')).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('non admins', () => {
|
||||
test('should not have localStorage set with a false value', () => {
|
||||
mountWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
expect(global.localStorage.getItem(adminPanelOpenKey)).toBe(null);
|
||||
});
|
||||
test('should not have css class set even if localStorage contains open key', () => {
|
||||
// Regression test for situation where admin logs out but localStorage still
|
||||
// contains "open", causing extra space to appear
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'open');
|
||||
mountWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(false);
|
||||
});
|
||||
});
|
||||
test('calling onOpen sets a class on the #viewEl and records in local storage', () => {
|
||||
const component = mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
let child = component.find(AdminPanel);
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
|
||||
import {StudioComments} from '../../../src/views/studio/studio-comments.jsx';
|
||||
|
||||
// Replace customized studio comment with default comment to avoid redux issues in the test
|
||||
jest.mock('../../../src/views/studio/studio-comment.js', () => (
|
||||
jest.requireActual('../../../src/views/preview/comment/comment.jsx')
|
||||
));
|
||||
|
||||
describe('Studio comments', () => {
|
||||
test('if there are no comments, they get loaded', () => {
|
||||
const loadComments = jest.fn();
|
||||
|
@ -69,4 +75,95 @@ describe('Studio comments', () => {
|
|||
);
|
||||
expect(resetComments).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Comments do not show when shouldShowCommentsList is false', () => {
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsList={false}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('Comments show when shouldShowCommentsList is true', () => {
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsList
|
||||
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('Single comment load more shows when shouldShowCommentsList is true', () => {
|
||||
// Make the component think this is a single view.
|
||||
global.window.location.hash = '#comments-6';
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsList
|
||||
singleCommentId
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(true);
|
||||
expect(component.find('Button').exists()).toBe(true);
|
||||
expect(component.find('button.load-more-button').exists()).toBe(true);
|
||||
global.window.location.hash = '';
|
||||
});
|
||||
|
||||
test('Single comment does not show when shouldShowCommentsList is false', () => {
|
||||
// Make the component think this is a single view.
|
||||
global.window.location.hash = '#comments-6';
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsList={false}
|
||||
singleCommentId
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(false);
|
||||
expect(component.find('Button').exists()).toBe(false);
|
||||
expect(component.find('button.load-more-button').exists()).toBe(false);
|
||||
global.window.location.hash = '';
|
||||
});
|
||||
|
||||
test('Comment status error shows when shoudlShowCommentsGloballyOffError is true', () => {
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession={false}
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsGloballyOffError
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('CommentingStatus').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('Comment status error does not show when shoudlShowCommentsGloballyOffError is false', () => {
|
||||
const component = mountWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession={false}
|
||||
isAdmin={false}
|
||||
comments={[{id: 123, author: {}}]}
|
||||
shouldShowCommentsGloballyOffError={false}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('CommentingStatus').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,13 +15,16 @@ import {
|
|||
selectCanRemoveManager,
|
||||
selectCanPromoteCurators,
|
||||
selectCanRemoveProject,
|
||||
selectShowCommentsList,
|
||||
selectShowCommentsGloballyOffError,
|
||||
selectShowProjectMuteError,
|
||||
selectShowCuratorMuteError,
|
||||
selectShowEditMuteError
|
||||
} from '../../../src/redux/studio-permissions';
|
||||
|
||||
import {getInitialState as getInitialStudioState} from '../../../src/redux/studio';
|
||||
import {getInitialState as getInitialSessionState, selectUserId, selectUsername} from '../../../src/redux/session';
|
||||
import {getInitialState as getInitialSessionState,
|
||||
selectUserId, selectUsername, Status} from '../../../src/redux/session';
|
||||
import {sessions, studios} from '../../helpers/state-fixtures.json';
|
||||
|
||||
let state;
|
||||
|
@ -185,17 +188,22 @@ describe('studio comments', () => {
|
|||
describe('can report comment', () => {
|
||||
test.each([
|
||||
['logged in', true],
|
||||
['unconfirmed', false],
|
||||
['unconfirmed', true],
|
||||
['logged out', false],
|
||||
['muted creator', true],
|
||||
['muted logged in', true]
|
||||
])('%s: %s', (role, expected) => {
|
||||
setStateByRole(role);
|
||||
expect(selectCanReportComment(state)).toBe(expected);
|
||||
expect(selectCanReportComment(state, 'not me')).toBe(expected);
|
||||
});
|
||||
test('cannot report your own comment', () => {
|
||||
setStateByRole('logged in');
|
||||
const loggedInUsername = selectUsername(state);
|
||||
expect(selectCanReportComment(state, loggedInUsername)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('can delete comment', () => {
|
||||
describe('can delete others comments', () => {
|
||||
test.each([
|
||||
['admin', true],
|
||||
['curator', false],
|
||||
|
@ -208,7 +216,27 @@ describe('studio comments', () => {
|
|||
['muted logged in', false]
|
||||
])('%s: %s', (role, expected) => {
|
||||
setStateByRole(role);
|
||||
expect(selectCanDeleteComment(state)).toBe(expected);
|
||||
expect(selectCanDeleteComment(state, 'not me')).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('can delete my own comment', () => {
|
||||
test.each([
|
||||
['admin', true],
|
||||
['curator', false],
|
||||
['manager', true],
|
||||
['creator', true],
|
||||
['logged in', false],
|
||||
['unconfirmed', false],
|
||||
['logged out', false],
|
||||
['muted creator', true],
|
||||
['muted manager', true],
|
||||
['muted curator', false],
|
||||
['muted logged in', false]
|
||||
])('%s: %s', (role, expected) => {
|
||||
setStateByRole(role);
|
||||
const loggedInUsername = selectUsername(state);
|
||||
expect(selectCanDeleteComment(state, loggedInUsername)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -483,4 +511,81 @@ describe('studio mute errors', () => {
|
|||
expect(selectShowEditMuteError(state)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('show comments list selector', () => {
|
||||
test('show comments is true', () => {
|
||||
const thisSession = {
|
||||
status: Status.FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsList(state)).toBe(true);
|
||||
});
|
||||
test('show comments is false because feature flag is false', () => {
|
||||
const thisSession = {
|
||||
status: Status.FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: false
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsList(state)).toBe(false);
|
||||
});
|
||||
test('show comments is false because session not fetched', () => {
|
||||
const thisSession = {
|
||||
status: Status.NOT_FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsList(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('show comments globally off error', () => {
|
||||
test('show comments off error because feature flag is false', () => {
|
||||
const thisSession = {
|
||||
status: Status.FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: false
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsGloballyOffError(state)).toBe(true);
|
||||
});
|
||||
test('Do not show comments off error because feature flag is on ', () => {
|
||||
const thisSession = {
|
||||
status: Status.FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsGloballyOffError(state)).toBe(false);
|
||||
});
|
||||
test('Do not show comments off error because session not fetched ', () => {
|
||||
const thisSession = {
|
||||
status: Status.NOT_FETCHED,
|
||||
session: {
|
||||
flags: {
|
||||
gallery_comments_enabled: false
|
||||
}
|
||||
}
|
||||
};
|
||||
state.session = thisSession;
|
||||
expect(selectShowCommentsGloballyOffError(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue