mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Merge branch 'develop' of https://github.com/LLK/scratch-www into studio-report-modal
# Conflicts: # src/views/studio/studio-report.jsx
This commit is contained in:
commit
20a342c0a2
42 changed files with 793 additions and 328 deletions
309
package-lock.json
generated
309
package-lock.json
generated
|
@ -225,20 +225,20 @@
|
|||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz",
|
||||
"integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.2.tgz",
|
||||
"integrity": "sha512-OgC1mON+l4U4B4wiohJlQNUU3H73mpTyYY3j/c8U9dr9UagGGSm+WFpzjy/YLdoyjiG++c1kIDgxCo/mLwQJeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.0",
|
||||
"@babel/generator": "^7.14.2",
|
||||
"@babel/helper-compilation-targets": "^7.13.16",
|
||||
"@babel/helper-module-transforms": "^7.14.0",
|
||||
"@babel/helper-module-transforms": "^7.14.2",
|
||||
"@babel/helpers": "^7.14.0",
|
||||
"@babel/parser": "^7.14.0",
|
||||
"@babel/parser": "^7.14.2",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.14.0",
|
||||
"@babel/types": "^7.14.0",
|
||||
"@babel/traverse": "^7.14.2",
|
||||
"@babel/types": "^7.14.2",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
|
@ -257,25 +257,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz",
|
||||
"integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz",
|
||||
"integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.1",
|
||||
"@babel/types": "^7.14.2",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
|
||||
"integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz",
|
||||
"integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.12.13",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
|
@ -308,9 +308,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
|
||||
"integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz",
|
||||
"integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -325,25 +325,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
|
||||
"integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz",
|
||||
"integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.0",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/generator": "^7.14.2",
|
||||
"@babel/helper-function-name": "^7.14.2",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.14.0",
|
||||
"@babel/types": "^7.14.0",
|
||||
"@babel/parser": "^7.14.2",
|
||||
"@babel/types": "^7.14.2",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -546,9 +546,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -573,9 +573,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -591,9 +591,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz",
|
||||
"integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz",
|
||||
"integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.13.12",
|
||||
|
@ -602,8 +602,8 @@
|
|||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.14.0",
|
||||
"@babel/types": "^7.14.0"
|
||||
"@babel/traverse": "^7.14.2",
|
||||
"@babel/types": "^7.14.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
|
@ -616,25 +616,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz",
|
||||
"integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz",
|
||||
"integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.1",
|
||||
"@babel/types": "^7.14.2",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
|
||||
"integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz",
|
||||
"integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.12.13",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
|
@ -667,9 +667,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
|
||||
"integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz",
|
||||
"integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -684,25 +684,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
|
||||
"integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz",
|
||||
"integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.0",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/generator": "^7.14.2",
|
||||
"@babel/helper-function-name": "^7.14.2",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.14.0",
|
||||
"@babel/types": "^7.14.0",
|
||||
"@babel/parser": "^7.14.2",
|
||||
"@babel/types": "^7.14.2",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -789,9 +789,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -834,25 +834,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz",
|
||||
"integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz",
|
||||
"integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.1",
|
||||
"@babel/types": "^7.14.2",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
|
||||
"integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz",
|
||||
"integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.12.13",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
|
@ -885,9 +885,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
|
||||
"integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz",
|
||||
"integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -902,25 +902,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
|
||||
"integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz",
|
||||
"integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.0",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/generator": "^7.14.2",
|
||||
"@babel/helper-function-name": "^7.14.2",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.14.0",
|
||||
"@babel/types": "^7.14.0",
|
||||
"@babel/parser": "^7.14.2",
|
||||
"@babel/types": "^7.14.2",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -1007,9 +1007,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -1066,25 +1066,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz",
|
||||
"integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz",
|
||||
"integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.1",
|
||||
"@babel/types": "^7.14.2",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
|
||||
"integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz",
|
||||
"integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.12.13",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
|
@ -1117,9 +1117,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
|
||||
"integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz",
|
||||
"integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -1134,25 +1134,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
|
||||
"integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz",
|
||||
"integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.0",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/generator": "^7.14.2",
|
||||
"@babel/helper-function-name": "^7.14.2",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.14.0",
|
||||
"@babel/types": "^7.14.0",
|
||||
"@babel/parser": "^7.14.2",
|
||||
"@babel/types": "^7.14.2",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
|
||||
"integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz",
|
||||
"integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.0",
|
||||
|
@ -1368,10 +1368,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
|
||||
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-getcanonicallocales": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.10.tgz",
|
||||
"integrity": "sha512-tFqGxZ9HkAzphupybyCKdWHzL1ge/sY8TtzEK57Hs3RCxrv/y+VxIPrE+Izw2oCFowQBz76cyi0zT6PjHuWArA==",
|
||||
"version": "1.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.11.tgz",
|
||||
"integrity": "sha512-S+D4P8BSZDVTooR0AkqJUWMF6BKMyaBgM/XJiXiuOhVNg44ZYwwsD4PhnTGEbLpTpAcJX1Leway1SLfzrH8YOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cldr-core": "38",
|
||||
|
@ -1387,26 +1404,17 @@
|
|||
}
|
||||
},
|
||||
"@formatjs/intl-locale": {
|
||||
"version": "2.4.24",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.24.tgz",
|
||||
"integrity": "sha512-+JOwvBRFS/GFuJlWiWbfAzBng0A+ANoGV1LRseXK+4uzp4Sn35GD8M/dfgU1lp2R2dTWpYie2yyoHe4k4aHF6w==",
|
||||
"version": "2.4.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.25.tgz",
|
||||
"integrity": "sha512-UgoU8TlMhjkiIJCRIgMs6JzTwYcVH9BnpL0j8Vy3A1zCXYDsRPu7TKpEQ5mrc8qkCqxz5LWZfocJqnluw/G1JQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.7.1",
|
||||
"@formatjs/intl-getcanonicallocales": "1.5.10",
|
||||
"@formatjs/intl-getcanonicallocales": "1.5.11",
|
||||
"cldr-core": "38",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
|
||||
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
|
@ -1416,24 +1424,15 @@
|
|||
}
|
||||
},
|
||||
"@formatjs/intl-pluralrules": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.18.tgz",
|
||||
"integrity": "sha512-qRFITPsNoeXfsiGc97pp8mVgqcC7aQNuXsiJjY9LpXVTkYNfjUP4ZpbYXflM4xoWCXMJNz3ilsrQhZWXy9td5g==",
|
||||
"version": "4.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.19.tgz",
|
||||
"integrity": "sha512-ArxXyH1NVLHKjiEVG0Lg8BMjGJoC7M+0FZnr2ln5uhrosQoctKmTrTXpezhqw0NOKyo05jIQY/keMlfRgEpIsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.7.1",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
|
||||
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
|
@ -20856,9 +20855,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-blocks": {
|
||||
"version": "0.1.0-prerelease.20210510043314",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210510043314.tgz",
|
||||
"integrity": "sha512-wXogEeojamKgRyBxkScpchd3sDp6sZsIxYcYNtWdPIbMpNacytMzC6hXYjFfq/BUZs4Vmnnu1x2+NKKX6N8WuA==",
|
||||
"version": "0.1.0-prerelease.20210514034551",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210514034551.tgz",
|
||||
"integrity": "sha512-OfQNiJG8lidyXBGTeWXpC6caSVroAxfmkK1mxOFdGoIW3adUJTrl6np5RXwD58ByYnerxt+AldR3kIOyNAdBrg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exports-loader": "0.6.3",
|
||||
|
@ -20866,9 +20865,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-gui": {
|
||||
"version": "0.1.0-prerelease.20210510170231",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210510170231.tgz",
|
||||
"integrity": "sha512-RS12FvNZR1CoEUxtvRpHIrDQVJkPJWrIYkNS5Bjr/ZtixVN1fdRB/zHtfZ0LIbw+TOVABSyn81kKWGW011J73g==",
|
||||
"version": "0.1.0-prerelease.20210514040957",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210514040957.tgz",
|
||||
"integrity": "sha512-qjn3YtszSnj0iI/Bd9LXJBuudyg2T2QwJ0TOl9628/wuwexZsLxEERyHl8o9vhErUaiJ2TLeL5nTYWbMbhdA9A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arraybuffer-loader": "^1.0.6",
|
||||
|
@ -20919,13 +20918,13 @@
|
|||
"redux": "3.7.2",
|
||||
"redux-throttle": "0.1.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210510043314",
|
||||
"scratch-l10n": "3.11.20210510031549",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210514034551",
|
||||
"scratch-l10n": "3.11.20210513031505",
|
||||
"scratch-paint": "0.2.0-prerelease.20210407203313",
|
||||
"scratch-render": "0.1.0-prerelease.20210325231800",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
|
||||
"scratch-storage": "1.3.4",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20210408171934",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20210511195415",
|
||||
"scratch-vm": "0.2.0-prerelease.20210510162256",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
@ -21312,9 +21311,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.11.20210510031549",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210510031549.tgz",
|
||||
"integrity": "sha512-WeoSa4zxtOcAGeaASqVcJcZu/QWoWtHohsP71ICbNcBpcAqnpPV1AhdGNrOQyiwuj0Vy/6CBuH9QYpS0IF+AkA==",
|
||||
"version": "3.11.20210513031505",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210513031505.tgz",
|
||||
"integrity": "sha512-BZagwqCz1/HmCeYURZGyY+wvw7h644PyO18ZWaFTtneYorukWNv3fT1ARmwwvCwzCY4FZwJ7ksogxTlQSFirDw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
|
@ -21401,9 +21400,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.11.20210511031531",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210511031531.tgz",
|
||||
"integrity": "sha512-E+Gm8pSNgZhVEsdRXshkhk6MnKeQdbqXx3KSctaJ9wBibxZwRX3kB+6vvkt75zESfCuQ5f3iXdB6s2KavZzHaA==",
|
||||
"version": "3.11.20210514031523",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210514031523.tgz",
|
||||
"integrity": "sha512-SBsH0mYZJ35/X/eHizJWLE26xtFItjb2++hMoC4X1ZfkeYazBZYv3baxS50VfzGsZcWgVEKpQ2sMePctSwBN7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
|
@ -21637,9 +21636,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-svg-renderer": {
|
||||
"version": "0.2.0-prerelease.20210408171934",
|
||||
"resolved": "https://registry.npmjs.org/scratch-svg-renderer/-/scratch-svg-renderer-0.2.0-prerelease.20210408171934.tgz",
|
||||
"integrity": "sha512-kc59fyZlJ58ooW86VQo9oqXNzpR48RH7vObehekVyPq4FMRENwtv9gCZ5XitLPNsLEheFCJdfRVPkVsMAjhPYQ==",
|
||||
"version": "0.2.0-prerelease.20210511195415",
|
||||
"resolved": "https://registry.npmjs.org/scratch-svg-renderer/-/scratch-svg-renderer-0.2.0-prerelease.20210511195415.tgz",
|
||||
"integrity": "sha512-zeT93lfMeJNWhj8cLfNeDWTZT/fDS2Fnz6btCJpvE5AAyel+8VE1Y9hBb1OJ+ap8vjA1O31TnDApIylRmA/g5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-js": "1.2.1",
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
"redux-mock-store": "^1.2.3",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20210510170231",
|
||||
"scratch-gui": "0.1.0-prerelease.20210514040957",
|
||||
"scratch-l10n": "latest",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
|
|
|
@ -6,7 +6,7 @@ const React = require('react');
|
|||
require('./button.scss');
|
||||
|
||||
const Button = props => {
|
||||
const classes = classNames('button', props.className, {'close-button': props.isCloseType});
|
||||
const classes = classNames('button', props.className, {'forms-close-button': props.isCloseType});
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
|
@ -54,7 +54,7 @@ $pass-bg: $ui-aqua;
|
|||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
.forms-close-button {
|
||||
padding: 0;
|
||||
|
||||
position: absolute;
|
||||
|
@ -69,6 +69,6 @@ $pass-bg: $ui-aqua;
|
|||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.close-button img {
|
||||
.forms-close-button img {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ const ToggleSlider = props => (
|
|||
<label className={classNames('toggle-switch', props.className)} >
|
||||
<input
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
type="checkbox"
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
@ -17,6 +18,7 @@ const ToggleSlider = props => (
|
|||
|
||||
ToggleSlider.propTypes = {
|
||||
checked: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ const ValidationMessage = props => (
|
|||
ValidationMessage.propTypes = {
|
||||
className: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
mode: PropTypes.string
|
||||
mode: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||
};
|
||||
|
||||
module.exports = ValidationMessage;
|
||||
|
|
9
src/components/overflow-menu/overflow-icon.svg
Normal file
9
src/components/overflow-menu/overflow-icon.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg width="21" height="19" viewBox="0 0 21 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4923 14.0441C11.4621 14.0441 12.2482 14.7672 12.2482 15.659C12.2482 16.5509 11.4621 17.2739 10.4923 17.2739C9.52252 17.2739 8.73636 16.5509 8.73636 15.659C8.73636 14.7672 9.52252 14.0441 10.4923 14.0441ZM10.4923 7.58453C11.4621 7.58453 12.2482 8.30755 12.2482 9.19943C12.2482 10.0913 11.4621 10.8143 10.4923 10.8143C9.52252 10.8143 8.73636 10.0913 8.73636 9.19943C8.73636 8.30755 9.52252 7.58453 10.4923 7.58453ZM12.2482 2.73983C12.2482 1.84795 11.4621 1.12493 10.4923 1.12493C9.52252 1.12493 8.73636 1.84795 8.73636 2.73983C8.73636 3.63172 9.52252 4.35474 10.4923 4.35474C11.4621 4.35474 12.2482 3.63172 12.2482 2.73983Z" fill="#575E75" fill-opacity="0.6"/>
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="8" y="1" width="5" height="17">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4923 14.0441C11.4621 14.0441 12.2482 14.7672 12.2482 15.659C12.2482 16.5509 11.4621 17.2739 10.4923 17.2739C9.52252 17.2739 8.73636 16.5509 8.73636 15.659C8.73636 14.7672 9.52252 14.0441 10.4923 14.0441ZM10.4923 7.58453C11.4621 7.58453 12.2482 8.30755 12.2482 9.19943C12.2482 10.0913 11.4621 10.8143 10.4923 10.8143C9.52252 10.8143 8.73636 10.0913 8.73636 9.19943C8.73636 8.30755 9.52252 7.58453 10.4923 7.58453ZM12.2482 2.73983C12.2482 1.84795 11.4621 1.12493 10.4923 1.12493C9.52252 1.12493 8.73636 1.84795 8.73636 2.73983C8.73636 3.63172 9.52252 4.35474 10.4923 4.35474C11.4621 4.35474 12.2482 3.63172 12.2482 2.73983Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<rect x="0.93222" y="18.1711" width="17.9433" height="19.5104" transform="rotate(-90 0.93222 18.1711)" fill="#575E75" fill-opacity="0.6"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
43
src/components/overflow-menu/overflow-menu.jsx
Normal file
43
src/components/overflow-menu/overflow-menu.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* eslint-disable react/jsx-no-bind */
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Dropdown from '../dropdown/dropdown.jsx';
|
||||
import overflowIcon from './overflow-icon.svg';
|
||||
|
||||
import './overflow-menu.scss';
|
||||
|
||||
const OverflowMenu = ({children, dropdownAs, className}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className={classNames('overflow-menu-container', className)}>
|
||||
<button
|
||||
className="overflow-menu-trigger ignore-react-onclickoutside"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<img src={overflowIcon} />
|
||||
</button>
|
||||
{open && <Dropdown
|
||||
isOpen
|
||||
as={dropdownAs}
|
||||
className="overflow-menu-dropdown"
|
||||
onRequestClose={() => setOpen(false)}
|
||||
>
|
||||
{children}
|
||||
</Dropdown>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
OverflowMenu.propTypes = {
|
||||
children: PropTypes.node,
|
||||
dropdownAs: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
OverflowMenu.defaultProps = {
|
||||
dropdownAs: 'ul'
|
||||
};
|
||||
|
||||
export default OverflowMenu;
|
50
src/components/overflow-menu/overflow-menu.scss
Normal file
50
src/components/overflow-menu/overflow-menu.scss
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
.overflow-menu-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.overflow-menu-trigger {
|
||||
background: transparent;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overflow-menu-dropdown {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 2px 8px rgba(87, 94, 117, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
margin: 30px 0 0 0;
|
||||
right: unset; /* default dropdown aligns right edges, but we want left edges */
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
|
||||
/* Include default styling for <li><button />... list */
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
& + li {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
& > img {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -127,6 +127,7 @@ module.exports.selectUsername = state => get(state, ['session', 'session', 'user
|
|||
module.exports.selectToken = state => get(state, ['session', 'session', 'user', 'token'], null);
|
||||
module.exports.selectIsAdmin = state => get(state, ['session', 'session', 'permissions', 'admin'], false);
|
||||
module.exports.selectIsSocial = state => get(state, ['session', 'session', 'permissions', 'social'], false);
|
||||
module.exports.selectIsEducator = state => get(state, ['session', 'session', 'permissions', 'educator'], false);
|
||||
|
||||
// NB logged out user id as NaN so that it can never be used in equality testing since NaN !== NaN
|
||||
module.exports.selectUserId = state => get(state, ['session', 'session', 'user', 'id'], NaN);
|
||||
|
|
|
@ -18,7 +18,7 @@ const Errors = keyMirror({
|
|||
INAPPROPRIATE: null,
|
||||
PERMISSION: null,
|
||||
THUMBNAIL_TOO_LARGE: null,
|
||||
THUMBNAIL_MISSING: null,
|
||||
THUMBNAIL_INVALID: null,
|
||||
TEXT_TOO_LONG: null,
|
||||
REQUIRED_FIELD: null,
|
||||
UNHANDLED: null
|
||||
|
@ -111,7 +111,7 @@ const normalizeError = (err, body, res) => {
|
|||
switch (body.errors[0]) {
|
||||
case 'inappropriate-generic': return Errors.INAPPROPRIATE;
|
||||
case 'thumbnail-too-large': return Errors.THUMBNAIL_TOO_LARGE;
|
||||
case 'thumbnail-missing': return Errors.THUMBNAIL_MISSING;
|
||||
case 'image-invalid': return Errors.THUMBNAIL_INVALID;
|
||||
case 'editable-text-too-long': return Errors.TEXT_TOO_LONG;
|
||||
case 'This field is required.': return Errors.REQUIRED_FIELD;
|
||||
default: return Errors.UNHANDLED;
|
||||
|
|
|
@ -3,7 +3,7 @@ const keyMirror = require('keymirror');
|
|||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const {selectUsername, selectToken} = require('./session');
|
||||
const {selectUsername, selectToken, selectIsEducator} = require('./session');
|
||||
|
||||
const Status = keyMirror({
|
||||
FETCHED: null,
|
||||
|
@ -22,6 +22,9 @@ const getInitialState = () => ({
|
|||
followers: 0,
|
||||
owner: null,
|
||||
|
||||
// BEWARE: classroomId is only loaded if the user is an educator
|
||||
classroomId: null,
|
||||
|
||||
rolesStatus: Status.NOT_FETCHED,
|
||||
manager: false,
|
||||
curator: false,
|
||||
|
@ -95,6 +98,7 @@ const selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR
|
|||
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
|
||||
const selectIsFollowing = state => state.studio.following;
|
||||
const selectIsFetchingRoles = state => state.studio.rolesStatus === Status.FETCHING;
|
||||
const selectClassroomId = state => state.studio.classroomId;
|
||||
|
||||
// Thunks
|
||||
const getInfo = () => ((dispatch, getState) => {
|
||||
|
@ -138,6 +142,14 @@ const getRoles = () => ((dispatch, getState) => {
|
|||
invited: body.invited
|
||||
}));
|
||||
});
|
||||
|
||||
// Since the user is now loaded, it's a good time to check if the studio is part of a classroom
|
||||
if (selectIsEducator(state)) {
|
||||
api({uri: `/studios/${studioId}/classroom`}, (err, body, res) => {
|
||||
// No error states for inability/problems loading classroom, just swallow them
|
||||
if (!err && res.statusCode === 200 && body) dispatch(setInfo({classroomId: body.id}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
@ -161,5 +173,6 @@ module.exports = {
|
|||
selectStudioLoadFailed,
|
||||
selectIsFetchingInfo,
|
||||
selectIsFetchingRoles,
|
||||
selectIsFollowing
|
||||
selectIsFollowing,
|
||||
selectClassroomId
|
||||
};
|
||||
|
|
|
@ -12,12 +12,31 @@ const Grid = require('../../components/grid/grid.jsx');
|
|||
const TextArea = require('../../components/forms/textarea.jsx');
|
||||
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
|
||||
const Select = require('../../components/forms/select.jsx');
|
||||
const OverflowMenu = require('../../components/overflow-menu/overflow-menu.jsx').default;
|
||||
const exampleIcon = require('./example-icon.svg');
|
||||
|
||||
require('./components.scss');
|
||||
|
||||
const Components = () => (
|
||||
<div className="components">
|
||||
<div className="inner">
|
||||
<h1>Overflow Menu</h1>
|
||||
<div className="example-tile">
|
||||
<OverflowMenu>
|
||||
<li>
|
||||
<button>
|
||||
<img src={exampleIcon} />
|
||||
Remove
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button>
|
||||
<img src={exampleIcon} />
|
||||
Upgrade!
|
||||
</button>
|
||||
</li>
|
||||
</OverflowMenu>
|
||||
</div>
|
||||
<h1>Nav Bubbles</h1>
|
||||
<div className="subnavigation">
|
||||
<SubNavigation>
|
||||
|
|
|
@ -18,6 +18,17 @@
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
.example-tile {
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.colors {
|
||||
span {
|
||||
display: inline-block;
|
||||
|
|
18
src/views/components/example-icon.svg
Normal file
18
src/views/components/example-icon.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
|
||||
<title>Sound/General/Delete</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M4.54751641,6.99994966 L15.4523042,6.99994966 C15.7284466,6.99994966 15.9523042,7.22380729 15.9523042,7.49994966 C15.9523042,7.51506367 15.9516189,7.5301699 15.9502504,7.54522183 L15.1651793,16.1801783 C15.0715275,17.2102489 14.207924,17.9989808 13.1736049,17.9990897 L6.82662224,17.9997575 C5.79213514,17.9998663 4.92828345,17.2110677 4.83462539,16.180829 L4.04956981,7.54521753 C4.02456905,7.27020922 4.22724022,7.02700381 4.50224854,7.00200306 C4.51729904,7.00063483 4.53240384,6.99994966 4.54751641,6.99994966 Z M7.33333333,4 L7.88603796,2.34188612 C7.95409498,2.13771505 8.14516441,2 8.36037961,2 L11.6396204,2 C11.8548356,2 12.045905,2.13771505 12.113962,2.34188612 L12.6666667,4 L16.5,4 C16.7761424,4 17,4.22385763 17,4.5 L17,5.5 C17,5.77614237 16.7761424,6 16.5,6 L3.5,6 C3.22385763,6 3,5.77614237 3,5.5 L3,4.5 C3,4.22385763 3.22385763,4 3.5,4 L7.33333333,4 Z M8.38742589,4 L11.6125741,4 L11.2792408,3 L8.72075922,3 L8.38742589,4 Z M10,11.7204812 L11.5952436,10.1252376 C11.7905057,9.92997548 12.1070882,9.92997548 12.3023504,10.1252376 L12.3747624,10.1976496 C12.5700245,10.3929118 12.5700245,10.7094943 12.3747624,10.9047564 L10.7795188,12.5 L12.3747624,14.0952436 C12.5700245,14.2905057 12.5700245,14.6070882 12.3747624,14.8023504 L12.3023504,14.8747624 C12.1070882,15.0700245 11.7905057,15.0700245 11.5952436,14.8747624 L10,13.2795188 L8.40475641,14.8747624 C8.20949427,15.0700245 7.89291178,15.0700245 7.69764963,14.8747624 L7.62523762,14.8023504 C7.42997548,14.6070882 7.42997548,14.2905057 7.62523762,14.0952436 L9.22048121,12.5 L7.62523762,10.9047564 C7.42997548,10.7094943 7.42997548,10.3929118 7.62523762,10.1976496 L7.69764963,10.1252376 C7.89291178,9.92997548 8.20949427,9.92997548 8.40475641,10.1252376 L10,11.7204812 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Sound/General/Delete" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Trash-Can"></g>
|
||||
<g id="White" mask="url(#mask-2)" fill="#FFFFFF">
|
||||
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -364,7 +364,7 @@ const Developers = () => (
|
|||
<a href="https://github.com/LLK/">GitHub</a>
|
||||
),
|
||||
contactUsLink: (
|
||||
<a href="https://scratch.mit.edu/contact-us/">
|
||||
<a href="/contact-us">
|
||||
<FormattedMessage id="general.contactUs" />
|
||||
</a>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"developers.hereLinkText": "here",
|
||||
"developers.title": "Scratch for Developers",
|
||||
"developers.introLinkText": "Scratch Team at MIT",
|
||||
"developers.introLinkText": "Scratch Team",
|
||||
"developers.intro": "On this page, you’ll find information about open source projects created and maintained by the {introLink}, as well as our thoughts on best practices for designing learning experiences for children.",
|
||||
"developers.projectsTitle": "Projects",
|
||||
"developers.principlesTitle": "Principles",
|
||||
|
|
|
@ -159,6 +159,10 @@
|
|||
|
||||
.avatar {
|
||||
margin-right: .5rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 0px 1px rgba(77, 151, 255, 0.25);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.comment-body {
|
||||
|
|
|
@ -165,6 +165,13 @@ class Splash extends React.Component {
|
|||
shouldShowHOCMiddleBanner () {
|
||||
return false; // we did not use this middle banner in last HoC
|
||||
}
|
||||
shouldShowIntro () {
|
||||
return (
|
||||
this.props.sessionStatus === sessionActions.Status.FETCHED && // done fetching session
|
||||
Object.keys(this.props.user).length === 0 && // no user session found
|
||||
this.shouldShowHOCTopBanner() !== true
|
||||
);
|
||||
}
|
||||
shouldShowDonateBanner () {
|
||||
return (
|
||||
this.state.dismissedDonateBanner === false &&
|
||||
|
@ -180,7 +187,7 @@ class Splash extends React.Component {
|
|||
const showDonateBanner = this.shouldShowDonateBanner() || false;
|
||||
const showHOCTopBanner = this.shouldShowHOCTopBanner() || false;
|
||||
const showHOCMiddleBanner = this.shouldShowHOCMiddleBanner() || false;
|
||||
const showIntro = showHOCTopBanner !== true;
|
||||
const showIntro = this.shouldShowIntro() || false;
|
||||
const showWelcome = this.shouldShowWelcome();
|
||||
const homepageRefreshStatus = this.getHomepageRefreshStatus();
|
||||
|
||||
|
|
4
src/views/studio/icons/curator-icon.svg
Normal file
4
src/views/studio/icons/curator-icon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.2531 7.47107C5.2531 8.16594 5.51512 8.80522 5.9491 9.28437C4.50749 10.1253 3.54431 11.8131 3.54431 13.7407C3.54431 14.5562 4.19249 15.1128 5.00253 15.4536C5.82565 15.7998 6.90529 15.9673 7.97081 15.9673C9.03633 15.9673 10.116 15.7998 10.9391 15.4536C11.7491 15.1128 12.3973 14.5562 12.3973 13.7407V13.7342C12.3973 13.6496 12.3973 13.5472 12.3834 13.4418C12.3334 12.4083 12.0039 11.4543 11.4801 10.6824C11.0859 10.1059 10.5795 9.62615 9.99447 9.28522C10.4318 8.80663 10.6887 8.1706 10.6887 7.47107C10.6887 5.96646 9.47307 4.75 7.97416 4.75C6.46912 4.75 5.2531 5.96602 5.2531 7.47107Z" fill="#FFFFFF"/>
|
||||
<path d="M15.0868 5.71516C14.0615 5.71516 13.2332 6.54343 13.2332 7.56872C13.2332 8.00598 13.385 8.40622 13.6373 8.72277C13.1654 9.02039 12.7758 9.45561 12.5108 9.97982C12.4698 10.0609 12.476 10.1578 12.527 10.233C13.0003 10.9304 13.3012 11.7992 13.3451 12.7461C13.3501 12.8539 13.4236 12.9463 13.5274 12.9753C14.3677 13.2105 15.4588 13.2317 16.3459 13.0414C16.7892 12.9463 17.2012 12.7943 17.5086 12.5735C17.8194 12.3501 18.0443 12.0384 18.0443 11.637C18.0443 10.393 17.4408 9.29929 16.5294 8.72307C16.7819 8.40648 16.9338 8.00612 16.9338 7.56872C16.9338 6.5441 16.1062 5.71516 15.0868 5.71516Z" fill="#FFFFFF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
src/views/studio/icons/remove-icon.svg
Normal file
18
src/views/studio/icons/remove-icon.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
|
||||
<title>Sound/General/Delete</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M4.54751641,6.99994966 L15.4523042,6.99994966 C15.7284466,6.99994966 15.9523042,7.22380729 15.9523042,7.49994966 C15.9523042,7.51506367 15.9516189,7.5301699 15.9502504,7.54522183 L15.1651793,16.1801783 C15.0715275,17.2102489 14.207924,17.9989808 13.1736049,17.9990897 L6.82662224,17.9997575 C5.79213514,17.9998663 4.92828345,17.2110677 4.83462539,16.180829 L4.04956981,7.54521753 C4.02456905,7.27020922 4.22724022,7.02700381 4.50224854,7.00200306 C4.51729904,7.00063483 4.53240384,6.99994966 4.54751641,6.99994966 Z M7.33333333,4 L7.88603796,2.34188612 C7.95409498,2.13771505 8.14516441,2 8.36037961,2 L11.6396204,2 C11.8548356,2 12.045905,2.13771505 12.113962,2.34188612 L12.6666667,4 L16.5,4 C16.7761424,4 17,4.22385763 17,4.5 L17,5.5 C17,5.77614237 16.7761424,6 16.5,6 L3.5,6 C3.22385763,6 3,5.77614237 3,5.5 L3,4.5 C3,4.22385763 3.22385763,4 3.5,4 L7.33333333,4 Z M8.38742589,4 L11.6125741,4 L11.2792408,3 L8.72075922,3 L8.38742589,4 Z M10,11.7204812 L11.5952436,10.1252376 C11.7905057,9.92997548 12.1070882,9.92997548 12.3023504,10.1252376 L12.3747624,10.1976496 C12.5700245,10.3929118 12.5700245,10.7094943 12.3747624,10.9047564 L10.7795188,12.5 L12.3747624,14.0952436 C12.5700245,14.2905057 12.5700245,14.6070882 12.3747624,14.8023504 L12.3023504,14.8747624 C12.1070882,15.0700245 11.7905057,15.0700245 11.5952436,14.8747624 L10,13.2795188 L8.40475641,14.8747624 C8.20949427,15.0700245 7.89291178,15.0700245 7.69764963,14.8747624 L7.62523762,14.8023504 C7.42997548,14.6070882 7.42997548,14.2905057 7.62523762,14.0952436 L9.22048121,12.5 L7.62523762,10.9047564 C7.42997548,10.7094943 7.42997548,10.3929118 7.62523762,10.1976496 L7.69764963,10.1252376 C7.89291178,9.92997548 8.20949427,9.92997548 8.40475641,10.1252376 L10,11.7204812 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Sound/General/Delete" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Trash-Can"></g>
|
||||
<g id="White" mask="url(#mask-2)" fill="#FFFFFF">
|
||||
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -7,16 +7,26 @@
|
|||
"studio.title": "Title",
|
||||
"studio.description": "Description",
|
||||
"studio.thumbnail": "Thumbnail",
|
||||
"studio.updateErrors.generic": "Something went wrong updating the studio.",
|
||||
"studio.updateErrors.inappropriate": "That seems inappropriate. Please be respectful.",
|
||||
"studio.updateErrors.textTooLong": "That is too long.",
|
||||
"studio.updateErrors.requiredField": "This cannot be blank.",
|
||||
"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.projectsHeader": "Projects",
|
||||
"studio.addProjectsHeader": "Add Projects",
|
||||
"studio.addProject": "Add",
|
||||
"studio.addProjectPlaceholder": "Project URL",
|
||||
|
||||
"studio.openToAll": "Anyone can add projects",
|
||||
|
||||
"studio.projectsEmptyCanAdd1": "Your studio is looking a little empty.",
|
||||
"studio.projectsEmptyCanAdd2": "Add your first project!",
|
||||
"studio.projectsEmpty1": "This studio has no projects yet.",
|
||||
"studio.projectsEmpty2": "Suggest projects you want to add in the comments!",
|
||||
"studio.browseProjects": "Browse Projects",
|
||||
"studio.projectErrors.checkUrl": "Could not add project. Check the URL and try again.",
|
||||
|
||||
"studio.creatorRole": "Studio Creator",
|
||||
|
||||
|
@ -28,17 +38,30 @@
|
|||
"studio.curatorsHeader": "Curators",
|
||||
"studio.inviteCuratorsHeader": "Invite Curators",
|
||||
"studio.inviteCurator": "Invite",
|
||||
"studio.inviteCuratorPlaceholder": "Scratch Username",
|
||||
"studio.curatorAcceptInvite": "Accept Invite",
|
||||
"studio.curatorsEmptyCanAdd1": "You don’t have curators right now.",
|
||||
"studio.curatorsEmptyCanAdd2": "Add some curators to collaborate with!",
|
||||
"studio.curatorsEmpty1": "This studio has no curators right now.",
|
||||
"studio.curatorErrors.generic": "Could not invite curator.",
|
||||
"studio.curatorErrors.alreadyCurator": "They are already part of the studio.",
|
||||
"studio.curatorErrors.unknownUsername": "Could not invite a curator with that username.",
|
||||
"studio.curatorErrors.tooFast": "You are adding curators too fast.",
|
||||
|
||||
"studio.remove": "Remove",
|
||||
"studio.promote": "Promote",
|
||||
|
||||
"studio.commentsHeader": "Comments",
|
||||
"studio.comments.toggleOff": "Commenting off",
|
||||
"studio.comments.toggleOn": "Commenting on",
|
||||
"studio.comments.turnedOff": "Sorry, comment posting has been turned off for this studio.",
|
||||
|
||||
"studio.sharedFilter": "Shared",
|
||||
"studio.favoritedFilter": "Favorited",
|
||||
"studio.recentFilter": "Recent",
|
||||
|
||||
"studio.studentsFilter": "Students",
|
||||
|
||||
"studio.activityHeader": "Activity",
|
||||
"studio.activityAddProjectToStudio": "{profileLink} added the project {projectLink}",
|
||||
"studio.activityRemoveProjectStudio": "{profileLink} removed the project {projectLink}",
|
||||
"studio.activityUpdateStudio": "{profileLink} made edits to the title, thumbnail, or description",
|
||||
|
|
|
@ -11,13 +11,15 @@ const Errors = keyMirror({
|
|||
SERVER: null,
|
||||
PERMISSION: null,
|
||||
UNKNOWN_PROJECT: null,
|
||||
RATE_LIMIT: null
|
||||
RATE_LIMIT: null,
|
||||
DUPLICATE: null
|
||||
});
|
||||
|
||||
const normalizeError = (err, body, res) => {
|
||||
if (err) return Errors.NETWORK;
|
||||
if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION;
|
||||
if (res.statusCode === 404) return Errors.UNKNOWN_PROJECT;
|
||||
if (res.statusCode === 409) return Errors.DUPLICATE;
|
||||
if (res.statusCode === 429) return Errors.RATE_LIMIT;
|
||||
if (res.statusCode !== 200) return Errors.SERVER;
|
||||
return null;
|
||||
|
@ -59,11 +61,25 @@ const generateProjectListItem = (postBody, infoBody) => ({
|
|||
username: infoBody.author.username,
|
||||
avatar: infoBody.author.profile.images
|
||||
});
|
||||
|
||||
const addProject = projectId => ((dispatch, getState) => new Promise((resolve, reject) => {
|
||||
|
||||
const addProject = projectIdOrUrl => ((dispatch, getState) => new Promise((resolve, reject) => {
|
||||
// Strings are passed by the open input, numbers by the project browser
|
||||
let projectId = projectIdOrUrl;
|
||||
if (typeof projectIdOrUrl === 'string') {
|
||||
const matches = projectIdOrUrl.match(/(\d+)/g);
|
||||
if (!matches) return reject(Errors.UNKNOWN_PROJECT);
|
||||
// Take the last match, in case we are on localhost and there are port numbers, e.g.
|
||||
projectId = parseInt(matches[matches.length - 1], 10);
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const studioId = selectStudioId(state);
|
||||
const token = selectToken(state);
|
||||
|
||||
// Check for existing duplicates before going to the server
|
||||
if (projects.selector(state).items.filter(p => p.id === projectId).length !== 0) {
|
||||
return reject(Errors.DUPLICATE);
|
||||
}
|
||||
api({
|
||||
uri: `/studios/${studioId}/project/${projectId}`,
|
||||
method: 'POST',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import keyMirror from 'keymirror';
|
||||
import api from '../../../lib/api';
|
||||
import {selectUsername} from '../../../redux/session';
|
||||
import {selectToken, selectUsername} from '../../../redux/session';
|
||||
import {selectClassroomId} from '../../../redux/studio';
|
||||
import {userProjects, projects} from './redux-modules';
|
||||
|
||||
const Errors = keyMirror({
|
||||
|
@ -12,13 +13,25 @@ const Errors = keyMirror({
|
|||
const Filters = keyMirror({
|
||||
SHARED: null,
|
||||
FAVORITED: null,
|
||||
RECENT: null
|
||||
RECENT: null,
|
||||
STUDENTS: null
|
||||
});
|
||||
|
||||
const Uris = {
|
||||
[Filters.SHARED]: username => `/users/${username}/projects`,
|
||||
[Filters.FAVORITED]: username => `/users/${username}/favorites`,
|
||||
[Filters.RECENT]: username => `/users/${username}/recent`
|
||||
const Endpoints = {
|
||||
[Filters.SHARED]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/projects`
|
||||
}),
|
||||
[Filters.FAVORITED]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/favorites`
|
||||
}),
|
||||
[Filters.RECENT]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/projects/recentlyviewed`,
|
||||
authentication: selectToken(state)
|
||||
}),
|
||||
[Filters.STUDENTS]: state => ({
|
||||
uri: `/classrooms/${selectClassroomId(state)}/projects`,
|
||||
authentication: selectToken(state)
|
||||
})
|
||||
};
|
||||
|
||||
const normalizeError = (err, body, res) => {
|
||||
|
@ -30,14 +43,17 @@ const normalizeError = (err, body, res) => {
|
|||
|
||||
const loadUserProjects = type => ((dispatch, getState) => {
|
||||
const state = getState();
|
||||
const username = selectUsername(state);
|
||||
const projectCount = userProjects.selector(state).items.length;
|
||||
const projectsPerPage = 20;
|
||||
const opts = {
|
||||
...Endpoints[type](state),
|
||||
params: {
|
||||
limit: projectsPerPage,
|
||||
offset: projectCount
|
||||
}
|
||||
};
|
||||
dispatch(userProjects.actions.loading());
|
||||
api({
|
||||
uri: Uris[type](username),
|
||||
params: {limit: projectsPerPage, offset: projectCount}
|
||||
}, (err, body, res) => {
|
||||
api(opts, (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(userProjects.actions.error(error));
|
||||
const moreToLoad = body.length === projectsPerPage;
|
||||
|
|
|
@ -5,6 +5,7 @@ import {connect} from 'react-redux';
|
|||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectClassroomId} from '../../../redux/studio';
|
||||
import {addProject, removeProject} from '../lib/studio-project-actions';
|
||||
import {userProjects} from '../lib/redux-modules';
|
||||
import {Filters, loadUserProjects, clearUserProjects} from '../lib/user-projects-actions';
|
||||
|
@ -16,10 +17,11 @@ import SubNavigation from '../../../components/subnavigation/subnavigation.jsx';
|
|||
import UserProjectsTile from './user-projects-tile.jsx';
|
||||
|
||||
import './user-projects-modal.scss';
|
||||
import {selectIsEducator} from '../../../redux/session';
|
||||
|
||||
const UserProjectsModal = ({
|
||||
items, error, loading, moreToLoad, onLoadMore, onClear,
|
||||
onAdd, onRemove, onRequestClose
|
||||
items, error, loading, moreToLoad, showStudentsFilter,
|
||||
onLoadMore, onClear, onAdd, onRemove, onRequestClose
|
||||
}) => {
|
||||
const [filter, setFilter] = useState(Filters.SHARED);
|
||||
|
||||
|
@ -60,6 +62,14 @@ const UserProjectsModal = ({
|
|||
>
|
||||
<FormattedMessage id="studio.recentFilter" />
|
||||
</li>
|
||||
{showStudentsFilter &&
|
||||
<li
|
||||
className={classNames({active: filter === Filters.STUDENTS})}
|
||||
onClick={() => setFilter(Filters.STUDENTS)}
|
||||
>
|
||||
<FormattedMessage id="studio.studentsFilter" />
|
||||
</li>
|
||||
}
|
||||
</SubNavigation>
|
||||
<ModalInnerContent className="user-projects-modal-content">
|
||||
{error && <div>Error loading {filter}: {error}</div>}
|
||||
|
@ -75,15 +85,18 @@ const UserProjectsModal = ({
|
|||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
{moreToLoad &&
|
||||
<div className="studio-projects-load-more">
|
||||
{loading ? <small>Loading...</small> : (
|
||||
moreToLoad ?
|
||||
<button onClick={() => onLoadMore(filter)}>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</button> :
|
||||
<small>No more to load</small>
|
||||
)}
|
||||
<button
|
||||
className={classNames('button', {
|
||||
'mod-mutating': loading
|
||||
})}
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ModalInnerContent>
|
||||
</Modal>
|
||||
|
@ -91,6 +104,7 @@ const UserProjectsModal = ({
|
|||
};
|
||||
|
||||
UserProjectsModal.propTypes = {
|
||||
showStudentsFilter: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.id,
|
||||
image: PropTypes.string,
|
||||
|
@ -108,7 +122,8 @@ UserProjectsModal.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
...userProjects.selector(state)
|
||||
...userProjects.selector(state),
|
||||
showStudentsFilter: selectIsEducator(state) && selectClassroomId(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
|
|
|
@ -22,11 +22,14 @@
|
|||
.user-projects-modal-content {
|
||||
padding: 0 30px 30px;
|
||||
background: #E9F1FC;
|
||||
max-height: 80vh;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& { max-height: calc(100vh - 105px); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,7 +177,9 @@ const StudioActivity = ({items, loading, error, moreToLoad, onLoadMore}) => {
|
|||
|
||||
return (
|
||||
<div className="studio-activity">
|
||||
<h2>Activity</h2>
|
||||
<div className="studio-header-container">
|
||||
<h2><FormattedMessage id="studio.activityHeader" /></h2>
|
||||
</div>
|
||||
{loading && <div>Loading...</div>}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {selectStudioCommentsAllowed, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {
|
||||
mutateStudioCommentsAllowed, selectIsMutatingCommentsAllowed, selectCommentsAllowedMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
|
||||
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
||||
|
||||
const StudioCommentsAllowed = ({
|
||||
commentsAllowedError, isFetching, isMutating, commentsAllowed, handleUpdate
|
||||
}) => (
|
||||
|
@ -16,16 +20,20 @@ const StudioCommentsAllowed = ({
|
|||
<h4>Fetching...</h4>
|
||||
) : (
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
disabled={isMutating}
|
||||
type="checkbox"
|
||||
checked={commentsAllowed}
|
||||
onChange={e => handleUpdate(e.target.checked)}
|
||||
/>
|
||||
<span>{commentsAllowed ? 'Comments allowed' : 'Comments not allowed'}</span>
|
||||
{commentsAllowedError && <div>Error mutating commentsAllowed: {commentsAllowedError}</div>}
|
||||
</label>
|
||||
{commentsAllowed ? (
|
||||
<FormattedMessage id="studio.comments.toggleOn" />
|
||||
) : (
|
||||
<FormattedMessage id="studio.comments.toggleOff" />
|
||||
)}
|
||||
<ToggleSlider
|
||||
disabled={isMutating}
|
||||
checked={commentsAllowed}
|
||||
className={classNames('comments-allowed-input', {
|
||||
'mod-mutating': isMutating
|
||||
})}
|
||||
onChange={e => handleUpdate(e.target.checked)}
|
||||
/>
|
||||
{commentsAllowedError && <div>Error mutating commentsAllowed: {commentsAllowedError}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -56,9 +56,11 @@ const StudioComments = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h2><FormattedMessage id="studio.commentsHeader" /></h2>
|
||||
{canEditCommentsAllowed && <StudioCommentsAllowed />}
|
||||
<div>
|
||||
<div className="studio-header-container">
|
||||
<h2><FormattedMessage id="studio.commentsHeader" /></h2>
|
||||
{canEditCommentsAllowed && <StudioCommentsAllowed />}
|
||||
</div>
|
||||
<div className="studio-compose-container">
|
||||
{shouldShowCommentComposer && commentsAllowed &&
|
||||
<ComposeComment
|
||||
postURI={postURI}
|
||||
|
|
|
@ -3,12 +3,24 @@ import React, {useState} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, intlShape, injectIntl} from 'react-intl';
|
||||
|
||||
import {inviteCurator} from './lib/studio-member-actions';
|
||||
import FlexRow from '../../components/flex-row/flex-row.jsx';
|
||||
import {Errors, inviteCurator} from './lib/studio-member-actions';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const StudioCuratorInviter = ({onSubmit}) => {
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.NETWORK: return 'studio.curatorErrors.generic';
|
||||
case Errors.SERVER: return 'studio.curatorErrors.generic';
|
||||
case Errors.PERMISSION: return 'studio.curatorErrors.generic';
|
||||
case Errors.DUPLICATE: return 'studio.curatorErrors.alreadyCurator';
|
||||
case Errors.UNKNOWN_USERNAME: return 'studio.curatorErrors.unknownUsername';
|
||||
case Errors.RATE_LIMIT: return 'studio.curatorErrors.tooFast';
|
||||
default: return 'studio.curatorErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioCuratorInviter = ({intl, onSubmit}) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
@ -23,11 +35,19 @@ const StudioCuratorInviter = ({onSubmit}) => {
|
|||
return (
|
||||
<div className="studio-adder-section">
|
||||
<h3><FormattedMessage id="studio.inviteCuratorsHeader" /></h3>
|
||||
<FlexRow>
|
||||
<div className="studio-adder-row">
|
||||
{error && <div className="studio-adder-error">
|
||||
<ValidationMessage
|
||||
mode="error"
|
||||
className="validation-left"
|
||||
message={<FormattedMessage id={errorToMessageId(error)} />}
|
||||
/>
|
||||
</div>}
|
||||
<input
|
||||
className={classNames({'mod-form-error': error})}
|
||||
disabled={submitting}
|
||||
type="text"
|
||||
placeholder="<username>"
|
||||
placeholder={intl.formatMessage({id: 'studio.inviteCuratorPlaceholder'})}
|
||||
value={value}
|
||||
onKeyDown={e => e.key === 'Enter' && submit()}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
|
@ -36,17 +56,17 @@ const StudioCuratorInviter = ({onSubmit}) => {
|
|||
className={classNames('button', {
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
disabled={submitting || value === ''}
|
||||
onClick={submit}
|
||||
><FormattedMessage id="studio.inviteCurator" /></button>
|
||||
{error && <div>{error}</div>}
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StudioCuratorInviter.propTypes = {
|
||||
onSubmit: PropTypes.func
|
||||
onSubmit: PropTypes.func,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
|
@ -55,4 +75,4 @@ const mapDispatchToProps = ({
|
|||
onSubmit: inviteCurator
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StudioCuratorInviter);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(StudioCuratorInviter));
|
||||
|
|
|
@ -20,7 +20,9 @@ const StudioCurators = ({
|
|||
}, []);
|
||||
|
||||
return (<div className="studio-members">
|
||||
<h2><FormattedMessage id="studio.curatorsHeader" /></h2>
|
||||
<div className="studio-header-container">
|
||||
<h2><FormattedMessage id="studio.curatorsHeader" /></h2>
|
||||
</div>
|
||||
{canInviteCurators && <CuratorInviter />}
|
||||
{showCuratorInvite && <CuratorInvite />}
|
||||
{error && <Debug
|
||||
|
|
|
@ -2,23 +2,36 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioDescription, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {
|
||||
mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
|
||||
Errors, mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioDescription = ({
|
||||
descriptionError, isFetching, isMutating, description, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-description', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
'mod-mutating': isMutating,
|
||||
'mod-form-error': !!descriptionError
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="studio-info-section">
|
||||
<textarea
|
||||
rows="20"
|
||||
className={fieldClassName}
|
||||
|
@ -27,8 +40,11 @@ const StudioDescription = ({
|
|||
onBlur={e => e.target.value !== description &&
|
||||
handleUpdate(e.target.value)}
|
||||
/>
|
||||
{descriptionError && <div>Error mutating description: {descriptionError}</div>}
|
||||
</React.Fragment>
|
||||
{descriptionError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(descriptionError)} />}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ const StudioFollow = ({
|
|||
handleFollow
|
||||
}) => {
|
||||
if (!canFollow) return null;
|
||||
const fieldClassName = classNames('button', {
|
||||
const fieldClassName = classNames('button', 'studio-follow-button', {
|
||||
'mod-mutating': isMutating
|
||||
});
|
||||
return (
|
||||
|
|
|
@ -2,19 +2,30 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioImage, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {
|
||||
mutateStudioImage, selectIsMutatingImage, selectImageMutationError
|
||||
Errors, mutateStudioImage, selectIsMutatingImage, selectImageMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.THUMBNAIL_INVALID: return 'studio.updateErrors.thumbnailInvalid';
|
||||
case Errors.THUMBNAIL_TOO_LARGE: return 'studio.updateErrors.thumbnailTooLarge';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const blankImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
const StudioImage = ({
|
||||
imageError, isFetching, isMutating, image, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-image', {
|
||||
const fieldClassName = classNames('studio-info-section', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
});
|
||||
|
@ -36,7 +47,10 @@ const StudioImage = ({
|
|||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
{imageError && <div>Error mutating image: {imageError}</div>}
|
||||
{imageError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(imageError)} />}
|
||||
/>}
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,9 @@ const StudioManagers = ({items, error, loading, moreToLoad, onLoadMore}) => {
|
|||
|
||||
return (
|
||||
<div className="studio-members">
|
||||
<h2><FormattedMessage id="studio.managersHeader" /></h2>
|
||||
<div className="studio-header-container">
|
||||
<h2><FormattedMessage id="studio.managersHeader" /></h2>
|
||||
</div>
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
|
|
|
@ -14,6 +14,10 @@ import {
|
|||
removeManager
|
||||
} from './lib/studio-member-actions';
|
||||
|
||||
import OverflowMenu from '../../components/overflow-menu/overflow-menu.jsx';
|
||||
import removeIcon from './icons/remove-icon.svg';
|
||||
import promoteIcon from './icons/curator-icon.svg';
|
||||
|
||||
const StudioMemberTile = ({
|
||||
canRemove, canPromote, onRemove, onPromote, isCreator, // mapState props
|
||||
username, image // own props
|
||||
|
@ -36,37 +40,47 @@ const StudioMemberTile = ({
|
|||
>{username}</a>
|
||||
{isCreator && <div className="studio-member-role"><FormattedMessage id="studio.creatorRole" /></div>}
|
||||
</div>
|
||||
{canRemove &&
|
||||
<button
|
||||
className={classNames('studio-member-remove', {
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onRemove(username).catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>✕</button>
|
||||
}
|
||||
{canPromote &&
|
||||
<button
|
||||
className={classNames('studio-member-promote', {
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onPromote(username).catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>🆙</button>
|
||||
{(canRemove || canPromote) &&
|
||||
<OverflowMenu>
|
||||
{canPromote && <li>
|
||||
<button
|
||||
className={classNames({
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onPromote(username).catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={promoteIcon} />
|
||||
<FormattedMessage id="studio.promote" />
|
||||
</button>
|
||||
</li>}
|
||||
{canRemove && <li>
|
||||
<button
|
||||
className={classNames({
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onRemove(username).catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={removeIcon} />
|
||||
<FormattedMessage id="studio.remove" />
|
||||
</button>
|
||||
</li>}
|
||||
</OverflowMenu>
|
||||
}
|
||||
{error && <div>{error}</div>}
|
||||
</div>
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {selectStudioOpenToAll, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {
|
||||
mutateStudioOpenToAll, selectIsMutatingOpenToAll, selectOpenToAllMutationError
|
||||
} from '../../redux/studio-mutations';
|
||||
|
||||
import ToggleSlider from '../../components/forms/toggle-slider.jsx';
|
||||
|
||||
const StudioOpenToAll = ({
|
||||
openToAllError, isFetching, isMutating, openToAll, handleUpdate
|
||||
}) => (
|
||||
|
@ -16,16 +20,16 @@ const StudioOpenToAll = ({
|
|||
<h4>Fetching...</h4>
|
||||
) : (
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
disabled={isMutating}
|
||||
type="checkbox"
|
||||
checked={openToAll}
|
||||
onChange={e => handleUpdate(e.target.checked)}
|
||||
/>
|
||||
<span>{openToAll ? 'Open to all' : 'Not open to all'}</span>
|
||||
{openToAllError && <div>Error mutating openToAll: {openToAllError}</div>}
|
||||
</label>
|
||||
<FormattedMessage id="studio.openToAll" />
|
||||
<ToggleSlider
|
||||
disabled={isMutating}
|
||||
checked={openToAll}
|
||||
className={classNames('open-to-all-input', {
|
||||
'mod-mutating': isMutating
|
||||
})}
|
||||
onChange={e => handleUpdate(e.target.checked)}
|
||||
/>
|
||||
{openToAllError && <div>Error mutating openToAll: {openToAllError}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -3,13 +3,13 @@ import React, {useState} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, intlShape, injectIntl} from 'react-intl';
|
||||
|
||||
import {addProject} from './lib/studio-project-actions';
|
||||
import UserProjectsModal from './modals/user-projects-modal.jsx';
|
||||
import FlexRow from '../../components/flex-row/flex-row.jsx';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
|
||||
const StudioProjectAdder = ({onSubmit}) => {
|
||||
const StudioProjectAdder = ({intl, onSubmit}) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
@ -25,11 +25,19 @@ const StudioProjectAdder = ({onSubmit}) => {
|
|||
return (
|
||||
<div className="studio-adder-section">
|
||||
<h3><FormattedMessage id="studio.addProjectsHeader" /></h3>
|
||||
<FlexRow>
|
||||
<div className="studio-adder-row">
|
||||
{error && <div className="studio-adder-error">
|
||||
<ValidationMessage
|
||||
mode="error"
|
||||
className="validation-left"
|
||||
message={<FormattedMessage id="studio.projectErrors.checkUrl" />}
|
||||
/>
|
||||
</div>}
|
||||
<input
|
||||
className={classNames({'mod-form-error': error})}
|
||||
disabled={submitting}
|
||||
type="text"
|
||||
placeholder="<project id>"
|
||||
placeholder={intl.formatMessage({id: 'studio.addProjectPlaceholder'})}
|
||||
value={value}
|
||||
onKeyDown={e => e.key === 'Enter' && submit()}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
|
@ -38,10 +46,9 @@ const StudioProjectAdder = ({onSubmit}) => {
|
|||
className={classNames('button', {
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
disabled={submitting || value === ''}
|
||||
onClick={submit}
|
||||
><FormattedMessage id="studio.addProject" /></button>
|
||||
{error && <div>{error}</div>}
|
||||
<div className="studio-adder-vertical-divider" />
|
||||
<button
|
||||
className="button"
|
||||
|
@ -50,13 +57,14 @@ const StudioProjectAdder = ({onSubmit}) => {
|
|||
<FormattedMessage id="studio.browseProjects" />
|
||||
</button>
|
||||
{modalOpen && <UserProjectsModal onRequestClose={() => setModalOpen(false)} />}
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StudioProjectAdder.propTypes = {
|
||||
onSubmit: PropTypes.func
|
||||
onSubmit: PropTypes.func,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
|
@ -65,4 +73,4 @@ const mapDispatchToProps = ({
|
|||
onSubmit: addProject
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StudioProjectAdder);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(StudioProjectAdder));
|
||||
|
|
|
@ -3,10 +3,14 @@ import React, {useState} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectCanRemoveProject} from '../../redux/studio-permissions';
|
||||
import {removeProject} from './lib/studio-project-actions';
|
||||
|
||||
import OverflowMenu from '../../components/overflow-menu/overflow-menu.jsx';
|
||||
import removeIcon from './icons/remove-icon.svg';
|
||||
|
||||
const StudioProjectTile = ({
|
||||
canRemove, onRemove, // mapState props
|
||||
id, title, image, avatar, username // own props
|
||||
|
@ -41,23 +45,29 @@ const StudioProjectTile = ({
|
|||
>{username}</a>
|
||||
</div>
|
||||
{canRemove &&
|
||||
<button
|
||||
className={classNames('studio-project-remove', {
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onRemove(id)
|
||||
.catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>✕</button>
|
||||
<OverflowMenu>
|
||||
<li>
|
||||
<button
|
||||
className={classNames({
|
||||
'mod-mutating': submitting
|
||||
})}
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
onRemove(id)
|
||||
.catch(e => {
|
||||
setError(e);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={removeIcon} />
|
||||
<FormattedMessage id="studio.remove" />
|
||||
</button></li>
|
||||
</OverflowMenu>
|
||||
}
|
||||
{error && <div>{error}</div>}
|
||||
{error && <div>{error}</div>} {/* TODO where do these errors go? */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -21,8 +21,10 @@ const StudioProjects = ({
|
|||
|
||||
return (
|
||||
<div className="studio-projects">
|
||||
<h2><FormattedMessage id="studio.projectsHeader" /></h2>
|
||||
{canEditOpenToAll && <StudioOpenToAll />}
|
||||
<div className="studio-header-container">
|
||||
<h2><FormattedMessage id="studio.projectsHeader" /></h2>
|
||||
{canEditOpenToAll && <StudioOpenToAll />}
|
||||
</div>
|
||||
{canAddProjects && <StudioProjectAdder />}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
|
|
|
@ -2,21 +2,38 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectStudioTitle, selectIsFetchingInfo} from '../../redux/studio';
|
||||
import {selectCanEditInfo} from '../../redux/studio-permissions';
|
||||
import {mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
|
||||
import classNames from 'classnames';
|
||||
import {Errors, mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
|
||||
import ValidationMessage from '../../components/forms/validation-message.jsx';
|
||||
/*
|
||||
TODO
|
||||
- no newlines in studio title
|
||||
- Correct display in read-only mode
|
||||
- validation message
|
||||
*/
|
||||
const errorToMessageId = error => {
|
||||
switch (error) {
|
||||
case Errors.INAPPROPRIATE: return 'studio.updateErrors.inappropriate';
|
||||
case Errors.TEXT_TOO_LONG: return 'studio.updateErrors.textTooLong';
|
||||
case Errors.REQUIRED_FIELD: return 'studio.updateErrors.requiredField';
|
||||
default: return 'studio.updateErrors.generic';
|
||||
}
|
||||
};
|
||||
|
||||
const StudioTitle = ({
|
||||
titleError, isFetching, isMutating, title, canEditInfo, handleUpdate
|
||||
}) => {
|
||||
const fieldClassName = classNames('studio-title', {
|
||||
'mod-fetching': isFetching,
|
||||
'mod-mutating': isMutating
|
||||
'mod-mutating': isMutating,
|
||||
'mod-form-error': !!titleError
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="studio-info-section">
|
||||
<textarea
|
||||
className={fieldClassName}
|
||||
disabled={isMutating || !canEditInfo || isFetching}
|
||||
|
@ -24,8 +41,11 @@ const StudioTitle = ({
|
|||
onBlur={e => e.target.value !== title &&
|
||||
handleUpdate(e.target.value)}
|
||||
/>
|
||||
{titleError && <div>Error mutating title: {titleError}</div>}
|
||||
</React.Fragment>
|
||||
{titleError && <ValidationMessage
|
||||
mode="error"
|
||||
message={<FormattedMessage id={errorToMessageId(titleError)} />}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,14 @@ $radius: 8px;
|
|||
min-width: auto;
|
||||
margin: 50px auto;
|
||||
display: block;
|
||||
padding-top: 40px;
|
||||
|
||||
/* WAT Why does everything center at smaller widths??!! */
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& {
|
||||
text-align: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-shell {
|
||||
padding: 0 20px;
|
||||
|
@ -47,17 +55,37 @@ $radius: 8px;
|
|||
border: 2px dashed $ui-blue-25percent;
|
||||
border-radius: $radius;
|
||||
resize: none;
|
||||
width: 300px;
|
||||
|
||||
&:disabled { border-color: transparent; }
|
||||
}
|
||||
|
||||
.studio-info-section {
|
||||
position: relative;
|
||||
.validation-message {
|
||||
margin-top: .5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.studio-title {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.studio-title:disabled {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.studio-description:disabled {
|
||||
background: $ui-blue-10percent;
|
||||
}
|
||||
|
||||
.studio-follow-button {
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-report-modal {
|
||||
|
@ -110,6 +138,7 @@ $radius: 8px;
|
|||
.studio-tab-nav {
|
||||
border-bottom: 1px solid $active-dark-gray;
|
||||
padding-bottom: 8px;
|
||||
font-size: 14px;
|
||||
li { background: rgba(0, 0, 0, 0.15); }
|
||||
.active > li { background: $ui-blue; }
|
||||
}
|
||||
|
@ -150,7 +179,7 @@ $radius: 8px;
|
|||
}
|
||||
.studio-project-bottom {
|
||||
display: flex;
|
||||
padding: 10px 6px 10px 12px;
|
||||
padding: 6px 4px 6px 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.studio-project-avatar {
|
||||
|
@ -267,30 +296,50 @@ $radius: 8px;
|
|||
color: #4C97FF;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
margin: 0 -6px;
|
||||
& > * {
|
||||
margin: 0 6px;
|
||||
.studio-adder-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse; /* so error goes below at small sizes */
|
||||
|
||||
.studio-adder-error {
|
||||
position: relative;
|
||||
|
||||
.validation-message {
|
||||
transform: none;
|
||||
width: 200px;
|
||||
}
|
||||
@media #{$intermediate-and-smaller} {
|
||||
& {
|
||||
width: 100%;
|
||||
margin-top: .5rem;
|
||||
.validation-message {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
margin: .5em 0;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: .5rem;
|
||||
padding: 1em 1.25em;
|
||||
font-size: .8rem;
|
||||
}
|
||||
input {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: .5rem;
|
||||
padding: 1em 1.25em;
|
||||
font-size: .8rem;
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-grow: 0;
|
||||
}
|
||||
button {
|
||||
flex-grow: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.studio-adder-vertical-divider {
|
||||
border: 1px solid $ui-border;
|
||||
align-self: stretch;
|
||||
.studio-adder-vertical-divider {
|
||||
margin: 0 6px;
|
||||
border: 1px solid $ui-border;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,6 +363,23 @@ $radius: 8px;
|
|||
}
|
||||
}
|
||||
|
||||
.studio-header-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-compose-container {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.studio-empty {
|
||||
grid-column: 1 / -1; /* take up all columns */
|
||||
text-align: center;
|
||||
|
@ -362,3 +428,7 @@ $radius: 8px;
|
|||
.mod-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mod-form-error { /* When a field contains a value is causing an error */
|
||||
border-color: $ui-orange !important;
|
||||
}
|
Loading…
Reference in a new issue