mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 01:25:52 -05:00
Merge pull request #2404 from rschamp/mod-panel-phase-1
Admin panel for the project page
This commit is contained in:
commit
8e75b8c6cf
7 changed files with 165 additions and 123 deletions
|
@ -1,10 +1,9 @@
|
||||||
const bindAll = require('lodash.bindall');
|
const bindAll = require('lodash.bindall');
|
||||||
|
const classNames = require('classnames');
|
||||||
const connect = require('react-redux').connect;
|
const connect = require('react-redux').connect;
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
const Button = require('../forms/button.jsx');
|
|
||||||
|
|
||||||
require('./adminpanel.scss');
|
require('./adminpanel.scss');
|
||||||
|
|
||||||
class AdminPanel extends React.Component {
|
class AdminPanel extends React.Component {
|
||||||
|
@ -23,64 +22,37 @@ class AdminPanel extends React.Component {
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
if (!this.props.isAdmin) return false;
|
if (!this.props.isAdmin) return false;
|
||||||
|
return (
|
||||||
if (this.state.showPanel) {
|
<div
|
||||||
return (
|
className={classNames(
|
||||||
<div
|
'admin-panel', this.props.className, {
|
||||||
className="visible"
|
hidden: !this.state.showPanel
|
||||||
id="admin-panel"
|
}
|
||||||
>
|
)}
|
||||||
|
>
|
||||||
|
{this.state.showPanel ? (
|
||||||
|
<React.Fragment>
|
||||||
|
<span
|
||||||
|
className="toggle"
|
||||||
|
onClick={this.handleToggleVisibility}
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</span>
|
||||||
|
<div className="admin-header">
|
||||||
|
<h3>Admin Panel</h3>
|
||||||
|
</div>
|
||||||
|
<div className="admin-content">
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
<span
|
<span
|
||||||
className="toggle"
|
className="toggle"
|
||||||
onClick={this.handleToggleVisibility}
|
onClick={this.handleToggleVisibility}
|
||||||
>
|
>
|
||||||
x
|
>
|
||||||
</span>
|
</span>
|
||||||
<div className="admin-header">
|
)}
|
||||||
<h3>Admin Panel</h3>
|
|
||||||
</div>
|
|
||||||
<div className="admin-content">
|
|
||||||
<dl>
|
|
||||||
{this.props.children}
|
|
||||||
<dt>Page Cache</dt>
|
|
||||||
<dd>
|
|
||||||
<ul className="cache-list">
|
|
||||||
<li>
|
|
||||||
<form
|
|
||||||
action="/scratch_admin/page/clear-anon-cache/"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
name="path"
|
|
||||||
type="hidden"
|
|
||||||
value="/"
|
|
||||||
/>
|
|
||||||
<div className="button-row">
|
|
||||||
<span>For anonymous users:</span>
|
|
||||||
<Button type="submit">
|
|
||||||
<span>Clear</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="hidden"
|
|
||||||
id="admin-panel"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="toggle"
|
|
||||||
onClick={this.handleToggleVisibility}
|
|
||||||
>
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +60,7 @@ class AdminPanel extends React.Component {
|
||||||
|
|
||||||
AdminPanel.propTypes = {
|
AdminPanel.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
className: PropTypes.string,
|
||||||
isAdmin: PropTypes.bool
|
isAdmin: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@import "../../colors";
|
@import "../../colors";
|
||||||
|
|
||||||
#admin-panel {
|
.admin-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -9,16 +9,11 @@
|
||||||
box-shadow: 0 2px 5px $box-shadow-gray;
|
box-shadow: 0 2px 5px $box-shadow-gray;
|
||||||
background-color: $ui-gray;
|
background-color: $ui-gray;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
width: 230px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
|
|
||||||
&.visible {
|
|
||||||
width: 20%;
|
|
||||||
min-width: 180px;
|
|
||||||
max-width: 230px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
@ -28,33 +23,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-content {
|
|
||||||
dl {
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
dt {
|
|
||||||
margin: 2rem 0 1rem 0;
|
|
||||||
border-bottom: 1px solid $ui-dark-gray;
|
|
||||||
font-size: large;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row {
|
.button-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
|
|
@ -11,6 +11,7 @@ const classNames = require('classnames');
|
||||||
const GUI = require('scratch-gui').default;
|
const GUI = require('scratch-gui').default;
|
||||||
const IntlGUI = injectIntl(GUI);
|
const IntlGUI = injectIntl(GUI);
|
||||||
|
|
||||||
|
const AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
|
||||||
const decorateText = require('../../lib/decorate-text.jsx');
|
const decorateText = require('../../lib/decorate-text.jsx');
|
||||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||||
const Button = require('../../components/forms/button.jsx');
|
const Button = require('../../components/forms/button.jsx');
|
||||||
|
@ -45,6 +46,7 @@ const onKeyPress = e => {
|
||||||
|
|
||||||
const PreviewPresentation = ({
|
const PreviewPresentation = ({
|
||||||
addToStudioOpen,
|
addToStudioOpen,
|
||||||
|
adminModalOpen,
|
||||||
assetHost,
|
assetHost,
|
||||||
backpackHost,
|
backpackHost,
|
||||||
canAddToStudio,
|
canAddToStudio,
|
||||||
|
@ -160,6 +162,18 @@ const PreviewPresentation = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="preview">
|
<div className="preview">
|
||||||
|
<AdminPanel
|
||||||
|
className={classNames('project-admin-panel', {
|
||||||
|
'modal-open': adminModalOpen
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
className={classNames('admin-iframe', {
|
||||||
|
'modal-open': adminModalOpen
|
||||||
|
})}
|
||||||
|
src={`/scratch2/${projectId}/adminpanel/`}
|
||||||
|
/>
|
||||||
|
</AdminPanel>
|
||||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{banner}
|
{banner}
|
||||||
|
@ -530,6 +544,7 @@ const PreviewPresentation = ({
|
||||||
|
|
||||||
PreviewPresentation.propTypes = {
|
PreviewPresentation.propTypes = {
|
||||||
addToStudioOpen: PropTypes.bool,
|
addToStudioOpen: PropTypes.bool,
|
||||||
|
adminModalOpen: PropTypes.bool,
|
||||||
assetHost: PropTypes.string,
|
assetHost: PropTypes.string,
|
||||||
backpackHost: PropTypes.string,
|
backpackHost: PropTypes.string,
|
||||||
canAddToStudio: PropTypes.bool,
|
canAddToStudio: PropTypes.bool,
|
||||||
|
|
|
@ -35,6 +35,21 @@ $stage-width: 480px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
width: 252px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-iframe.modal-open,
|
||||||
|
.project-admin-panel.modal-open {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -45,6 +45,7 @@ class Preview extends React.Component {
|
||||||
'handleFavoriteToggle',
|
'handleFavoriteToggle',
|
||||||
'handleLoadMore',
|
'handleLoadMore',
|
||||||
'handleLoveToggle',
|
'handleLoveToggle',
|
||||||
|
'handleMessage',
|
||||||
'handlePopState',
|
'handlePopState',
|
||||||
'handleReportClick',
|
'handleReportClick',
|
||||||
'handleReportClose',
|
'handleReportClose',
|
||||||
|
@ -79,6 +80,7 @@ class Preview extends React.Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
addToStudioOpen: false,
|
addToStudioOpen: false,
|
||||||
|
adminModalOpen: false,
|
||||||
extensions: [],
|
extensions: [],
|
||||||
favoriteCount: 0,
|
favoriteCount: 0,
|
||||||
invalidProject: parts.length === 1,
|
invalidProject: parts.length === 1,
|
||||||
|
@ -92,10 +94,12 @@ class Preview extends React.Component {
|
||||||
reportOpen: false,
|
reportOpen: false,
|
||||||
singleCommentId: singleCommentId
|
singleCommentId: singleCommentId
|
||||||
};
|
};
|
||||||
this.addEventListeners();
|
|
||||||
/* In the beginning, if user is on mobile and landscape, go to fullscreen */
|
/* In the beginning, if user is on mobile and landscape, go to fullscreen */
|
||||||
this.setScreenFromOrientation();
|
this.setScreenFromOrientation();
|
||||||
}
|
}
|
||||||
|
componentDidMount () {
|
||||||
|
this.addEventListeners();
|
||||||
|
}
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
if (this.state.projectId > 0 &&
|
if (this.state.projectId > 0 &&
|
||||||
((this.props.sessionStatus !== prevProps.sessionStatus &&
|
((this.props.sessionStatus !== prevProps.sessionStatus &&
|
||||||
|
@ -139,10 +143,12 @@ class Preview extends React.Component {
|
||||||
addEventListeners () {
|
addEventListeners () {
|
||||||
window.addEventListener('popstate', this.handlePopState);
|
window.addEventListener('popstate', this.handlePopState);
|
||||||
window.addEventListener('orientationchange', this.setScreenFromOrientation);
|
window.addEventListener('orientationchange', this.setScreenFromOrientation);
|
||||||
|
window.addEventListener('message', this.handleMessage);
|
||||||
}
|
}
|
||||||
removeEventListeners () {
|
removeEventListeners () {
|
||||||
window.removeEventListener('popstate', this.handlePopState);
|
window.removeEventListener('popstate', this.handlePopState);
|
||||||
window.removeEventListener('orientationchange', this.setScreenFromOrientation);
|
window.removeEventListener('orientationchange', this.setScreenFromOrientation);
|
||||||
|
window.removeEventListener('message', this.handleMessage);
|
||||||
}
|
}
|
||||||
fetchCommunityData () {
|
fetchCommunityData () {
|
||||||
if (this.props.userPresent) {
|
if (this.props.userPresent) {
|
||||||
|
@ -258,6 +264,18 @@ class Preview extends React.Component {
|
||||||
handleDeleteComment (id, topLevelCommentId) {
|
handleDeleteComment (id, topLevelCommentId) {
|
||||||
this.props.handleDeleteComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
|
this.props.handleDeleteComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
|
||||||
}
|
}
|
||||||
|
handleMessage (messageEvent) {
|
||||||
|
if (messageEvent.data === 'showDialog') {
|
||||||
|
this.setState({
|
||||||
|
adminModalOpen: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (messageEvent.data === 'hideDialog') {
|
||||||
|
this.setState({
|
||||||
|
adminModalOpen: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
handleReportComment (id, topLevelCommentId) {
|
handleReportComment (id, topLevelCommentId) {
|
||||||
this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
|
this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
|
||||||
}
|
}
|
||||||
|
@ -464,6 +482,7 @@ class Preview extends React.Component {
|
||||||
<Page>
|
<Page>
|
||||||
<PreviewPresentation
|
<PreviewPresentation
|
||||||
addToStudioOpen={this.state.addToStudioOpen}
|
addToStudioOpen={this.state.addToStudioOpen}
|
||||||
|
adminModalOpen={this.state.adminModalOpen}
|
||||||
assetHost={this.props.assetHost}
|
assetHost={this.props.assetHost}
|
||||||
backpackHost={this.props.backpackHost}
|
backpackHost={this.props.backpackHost}
|
||||||
canAddToStudio={this.props.canAddToStudio}
|
canAddToStudio={this.props.canAddToStudio}
|
||||||
|
|
|
@ -529,41 +529,66 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
|
||||||
>
|
>
|
||||||
{featured}
|
{featured}
|
||||||
|
|
||||||
{this.props.isAdmin ? [
|
{this.props.isAdmin && (
|
||||||
<AdminPanel key="admin-panel">
|
<AdminPanel className="splash-admin-panel">
|
||||||
<dt>Tools</dt>
|
<dl>
|
||||||
<dd>
|
<dt>Tools</dt>
|
||||||
<ul>
|
<dd>
|
||||||
<li>
|
<ul>
|
||||||
<a href="/scratch_admin/tickets">Ticket Queue</a>
|
<li>
|
||||||
</li>
|
<a href="/scratch_admin/tickets">Ticket Queue</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="/scratch_admin/ip-search/">IP Search</a>
|
<li>
|
||||||
</li>
|
<a href="/scratch_admin/ip-search/">IP Search</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="/scratch_admin/email-search/">Email Search</a>
|
<li>
|
||||||
</li>
|
<a href="/scratch_admin/email-search/">Email Search</a>
|
||||||
</ul>
|
</li>
|
||||||
</dd>
|
</ul>
|
||||||
<dt>Homepage Cache</dt>
|
</dd>
|
||||||
<dd>
|
<dt>Homepage Cache</dt>
|
||||||
<ul className="cache-list">
|
<dd>
|
||||||
<li>
|
<ul className="cache-list">
|
||||||
<div className="button-row">
|
<li>
|
||||||
<span>Refresh row data:</span>
|
<div className="button-row">
|
||||||
<Button
|
<span>Refresh row data:</span>
|
||||||
className={this.props.refreshCacheStatus.status}
|
<Button
|
||||||
disabled={this.props.refreshCacheStatus.disabled}
|
className={this.props.refreshCacheStatus.status}
|
||||||
onClick={this.props.onRefreshHomepageCache}
|
disabled={this.props.refreshCacheStatus.disabled}
|
||||||
|
onClick={this.props.onRefreshHomepageCache}
|
||||||
|
>
|
||||||
|
<span>{this.props.refreshCacheStatus.content}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
<dt>Page Cache</dt>
|
||||||
|
<dd>
|
||||||
|
<ul className="cache-list">
|
||||||
|
<li>
|
||||||
|
<form
|
||||||
|
action="/scratch_admin/page/clear-anon-cache/"
|
||||||
|
method="post"
|
||||||
>
|
>
|
||||||
<span>{this.props.refreshCacheStatus.content}</span>
|
<input
|
||||||
</Button>
|
name="path"
|
||||||
</div>
|
type="hidden"
|
||||||
</li>
|
value="/"
|
||||||
</ul>
|
/>
|
||||||
</dd>
|
<div className="button-row">
|
||||||
|
<span>For anonymous users:</span>
|
||||||
|
<Button type="submit">
|
||||||
|
<span>Clear</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
</AdminPanel>
|
</AdminPanel>
|
||||||
] : []}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -47,6 +47,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.splash-admin-panel {
|
||||||
|
dl {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
dt {
|
||||||
|
margin: 2rem 0 1rem 0;
|
||||||
|
border-bottom: 1px solid $ui-dark-gray;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content.mod-confirmation {
|
.modal-content.mod-confirmation {
|
||||||
width: 31.25rem;
|
width: 31.25rem;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue