Basic project admin panel support

Just in case we don't do anything else, this "proxies" the old admin panel to the new project page.

Requires https://github.com/LLK/scratchr2/pull/5219 to work.
This commit is contained in:
Ray Schamp 2018-12-05 15:40:18 -05:00
parent 4a57d36112
commit 726a2005bb
7 changed files with 164 additions and 123 deletions

View file

@ -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 &gt;
</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}
>
&gt;
</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
}; };

View file

@ -1,6 +1,6 @@
@import "../../colors"; @import "../../colors";
#admin-panel { .admin-panel {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@ -8,17 +8,12 @@
border: 1px solid $ui-gray; border: 1px solid $ui-gray;
box-shadow: 0 2px 5px $box-shadow-gray; box-shadow: 0 2px 5px $box-shadow-gray;
background-color: $ui-gray; background-color: $ui-gray;
width: 230px;
padding: 1rem; padding: 1rem;
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;

View file

@ -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>
{ projectInfo && projectInfo.author && projectInfo.author.id && ( { projectInfo && projectInfo.author && projectInfo.author.id && (
<React.Fragment> <React.Fragment>
{banner} {banner}

View file

@ -35,6 +35,21 @@ $stage-width: 480px;
} }
} }
.admin-iframe {
position: absolute;
width: 252px;
height: 100%;
z-index: 100;
top: 0;
left: 0;
}
.admin-iframe.modal-open,
.project-admin-panel.modal-open {
width: 100%
}
.project-title { .project-title {
font-size: 1.75rem; font-size: 1.75rem;
font-weight: 500; font-weight: 500;

View file

@ -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,
justShared: false, justShared: false,
@ -91,10 +93,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 &&
@ -138,10 +142,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) {
@ -257,6 +263,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);
} }
@ -463,6 +481,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}

View file

@ -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>
); );

View file

@ -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;
} }