ui-default: init

将ui-default合并至主存储库便于维护
This commit is contained in:
undefined committed 2021-04-03 01:44:22 +08:00
1 parent 6317c9057f
commit 5faa54c7ca
564 files changed
+23083 -14

No files matched your search

@@ -0,0 +1,17 @@
.scratchpad__data-input
border: 0
border-left: 3px solid #E0E0E0
background: #F8F8F8
padding: rem(8px 4px)
font-family: $code-font-family
font-size: rem($font-size-secondary)
outline: 0
resize: none
color: #666
height: 100%
width: 100%
transition: background-color .2s linear, border-color .2s linear
&:focus
background: #FFF
border-color: lighten($primary-color, 70%)
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default function DataInputComponent(props) {
const {
title,
value,
onChange,
className,
...rest
} = props;
const cn = classNames(className, 'flex-col flex-fill');
return (
<div {...rest} className={cn}>
<textarea
className="scratchpad__data-input"
wrap="off"
value={value}
onChange={(ev) => {
ev.stopPropagation();
onChange(ev.target.value);
}}
placeholder={title}
/>
</div>
);
}
DataInputComponent.propTypes = {
title: PropTypes.node,
value: PropTypes.string,
onChange: PropTypes.func,
className: PropTypes.string,
};
@@ -0,0 +1,14 @@
.ScratchpadMonacoEditor
flex: 1
position: relative
overflow: hidden
.cm-s-vjcm
position: absolute
left: 0
top: 0
width: 100%
height: 100%
font-family: $code-font-family
font-size: rem($font-size-secondary)
line-height: 1.2
@@ -0,0 +1,8 @@
@import './var.inc.styl'
.scratchpad__panel-title
background: $primary-color
line-height: rem($panel-header-height)
padding: rem(0 5px)
font-size: rem($font-size-small)
color: #FFF
@@ -0,0 +1,15 @@
@import './var.inc.styl'
.scratchpad__panel-button
background: none
border: 0
line-height: rem($panel-header-height)
margin: 0
color: #FFF
padding: rem(0 5px)
cursor: pointer
font-size: rem($font-size-small)
outline: 0
&:hover
color: $tab-color
@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default function PanelButtonComponent(props) {
const {
className,
children,
...rest
} = props;
const cn = classNames(className, 'scratchpad__panel-button');
return (
<button {...rest} className={cn}>{children}</button>
);
}
PanelButtonComponent.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent';
export default function PanelComponent(props) {
const {
title,
className,
children,
...rest
} = props;
const cn = classNames(className, 'flex-col');
return (
<SplitPaneFillOverlay {...rest} className={cn}>
<div className="scratchpad__panel-title">{title}</div>
<div className="flex-col flex-fill">{children}</div>
</SplitPaneFillOverlay>
);
}
PanelComponent.propTypes = {
title: PropTypes.node,
className: PropTypes.string,
children: PropTypes.node,
};
@@ -0,0 +1,103 @@
import React from 'react';
import { connect } from 'react-redux';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
const mapStateToProps = (state) => ({
value: state.editor.code,
language: window.MONACO_MODES[state.editor.lang],
theme: 'vs-light',
mainSize: state.ui.main.size,
pretestSize: state.ui.pretest.size,
recordSize: state.ui.records.size,
});
const mapDispatchToProps = (dispatch) => ({
handleUpdateCode: (code) => {
dispatch({
type: 'SCRATCHPAD_EDITOR_UPDATE_CODE',
payload: code,
});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor extends React.PureComponent {
componentDidMount() {
const value = this.props.value || '';
const { language, theme } = this.props;
this.model = monaco.editor.createModel(value, language, monaco.Uri.parse('file://model'));
if (this.containerElement) {
this.editor = monaco.editor.create(
this.containerElement,
{
theme,
lineNumbers: true,
glyphMargin: true,
lightbulb: {
enabled: true,
},
model: this.model,
}
);
this._subscription = this.editor.onDidChangeModelContent((event) => {
if (!this.__prevent_trigger_change_event) {
this.props.handleUpdateCode(this.editor.getValue(), event);
}
});
window.editor = this.editor;
}
}
componentDidUpdate(prevProps) {
const {
value, language, theme, mainSize, recordSize, pretestSize,
} = this.props;
const { editor, model } = this;
if (this.props.value != null && this.props.value !== model.getValue()) {
this.__prevent_trigger_change_event = true;
editor.pushUndoStop();
model.pushEditOperations(
[],
[
{
range: model.getFullModelRange(),
text: value,
},
]
);
editor.pushUndoStop();
this.__prevent_trigger_change_event = false;
}
if (prevProps.language !== language) {
monaco.editor.setModelLanguage(model, language);
editor.updateOptions({ mode: language });
}
if (prevProps.theme !== theme) monaco.editor.setTheme(theme);
if (editor) {
if (prevProps.mainSize !== mainSize
|| prevProps.recordSize !== recordSize
|| prevProps.pretestSize !== pretestSize) editor.layout();
}
}
componentWillUnmount() {
if (this.editor) this.editor.dispose();
if (this.model) this.model.dispose();
if (this._subscription) this._subscription.dispose();
}
assignRef = (component) => {
this.containerElement = component;
};
render() {
return (
<div
ref={this.assignRef}
style={{
height: '100%',
width: '100%',
}}
className="ScratchpadMonacoEditor"
/>
);
}
});
@@ -0,0 +1,53 @@
import React from 'react';
import { connect } from 'react-redux';
import i18n from 'vj/utils/i18n';
import Icon from 'vj/components/react/IconComponent';
import Panel from './PanelComponent';
import DataInput from './DataInputComponent';
const mapStateToProps = (state) => ({
input: state.pretest.input,
output: state.pretest.output,
rid: state.pretest.rid,
});
const mapDispatchToProps = (dispatch) => ({
handleDataChange(type, value) {
dispatch({
type: 'SCRATCHPAD_PRETEST_DATA_CHANGE',
payload: {
type,
value,
},
});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadPretestContainer extends React.PureComponent {
render() {
return (
<Panel
title={(
<span>
<Icon name="edit" />
{' '}
{i18n('Pretest')}
</span>
)}
>
<div className="flex-row flex-fill">
<DataInput
title={i18n('Input')}
value={this.props.input}
onChange={(v) => this.props.handleDataChange('input', v)}
/>
<DataInput
title={i18n('Output')}
value={this.props.output}
/>
</div>
</Panel>
);
}
});
@@ -0,0 +1,45 @@
import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import i18n from 'vj/utils/i18n';
import Icon from 'vj/components/react/IconComponent';
import Panel from './PanelComponent';
import ScratchpadRecordsRow from './ScratchpadRecordsRowContainer';
const mapStateToProps = (state) => ({
rows: state.records.rows,
isLoading: state.ui.records.isLoading,
});
export default connect(mapStateToProps)(class ScratchpadRecordsContainer extends React.PureComponent {
render() {
const cn = classNames('data-table is--full-row scratchpad__records__table', {
loading: this.props.isLoading,
});
return (
<Panel
title={(
<span>
<Icon name="flag" />
{' '}
{i18n('Records')}
</span>
)}
>
<table className={cn}>
<colgroup>
<col className="col--detail" />
<col className="col--memory" />
<col className="col--time" />
<col className="col--at" />
</colgroup>
<tbody>
{this.props.rows.map((rowId) => (
<ScratchpadRecordsRow key={rowId} id={rowId} />
))}
</tbody>
</table>
</Panel>
);
}
});
@@ -0,0 +1,84 @@
import React from 'react';
import classNames from 'classnames';
import { connect } from 'react-redux';
import TimeAgo from 'timeago-react';
import moment from 'moment';
import _ from 'lodash';
import substitute from 'vj/utils/substitute';
import emulateAnchorClick from 'vj/utils/emulateAnchorClick';
import { parse as parseMongoId } from 'vj/utils/mongoId';
import i18n from 'vj/utils/i18n';
import * as recordEnum from 'vj/constant/record';
const shouldShowDetail = (data) => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status];
const getRecordDetail = (data) => {
if (!shouldShowDetail(data)) {
return (
<span className={`record-status--text ${recordEnum.STATUS_CODES[data.status]}`}>
{recordEnum.STATUS_TEXTS[data.status]}
</span>
);
}
const stat = _.pick(
_.groupBy(data.testCases || [], 'status'),
_.keys(recordEnum.STATUS_SCRATCHPAD_SHORT_TEXTS),
);
return _.map(recordEnum.STATUS_SCRATCHPAD_SHORT_TEXTS, (text, status) => {
const count = (stat[status] && stat[status].length) || 0;
const cn = classNames('icol icol--stat', {
'record-status--text': count > 0,
[recordEnum.STATUS_CODES[data.status]]: count > 0,
});
return (
<span key={text} className={cn}>
{text}: {count}
</span>
);
});
};
const mapStateToProps = (state) => ({
items: state.records.items,
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...dispatchProps,
data: stateProps.items[ownProps.id],
});
export default connect(mapStateToProps, null, mergeProps)(class ScratchpadRecordsRowContainer extends React.PureComponent {
handleRowClick(ev, id) {
const url = substitute(
decodeURIComponent(UiContext.getRecordDetailUrl),
{ rid: id }
);
emulateAnchorClick(ev, url, true);
}
render() {
const { data } = this.props;
const submitAt = parseMongoId(data._id).timestamp * 1000;
return data.hidden ? null : (
<tr onClick={(ev) => this.handleRowClick(ev, data._id)}>
<td className={`col--detail record-status--border ${recordEnum.STATUS_CODES[data.status]}`}>
<span className={`icon record-status--icon ${recordEnum.STATUS_CODES[data.status]}`}></span>
<span className="icol icol--pretest"></span>
{getRecordDetail(data)}
</td>
<td className="col--memory">
{shouldShowDetail(data) ? `${Math.ceil(data.memory / 1000)} MB` : '-'}
</td>
<td className="col--time">
{shouldShowDetail(data) ? `${(data.time / 1000).toFixed(1)}s` : '-'}
</td>
<td className="col--at">
<time data-tooltip={moment(submitAt).format('YYYY-MM-DD HH:mm:ss')}>
<TimeAgo datetime={submitAt} locale={i18n('timeago_locale')} />
</time>
</td>
</tr>
);
}
});
@@ -0,0 +1,168 @@
/* eslint-disable react/static-property-placement */
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import i18n from 'vj/utils/i18n';
import request from 'vj/utils/request';
import Icon from 'vj/components/react/IconComponent';
import Toolbar, {
ToolbarItemComponent as ToolbarItem,
ToolbarButtonComponent as ToolbarButton,
ToolbarSplitComponent as ToolbarSplit,
} from './ToolbarComponent';
const mapStateToProps = (state) => ({
pretestVisible: state.ui.pretest.visible,
recordsVisible: state.ui.records.visible,
isPosting: state.ui.isPosting,
isRunning: state.pretest.isRunning,
editorLang: state.editor.lang,
editorCode: state.editor.code,
pretestInput: state.pretest.input,
});
const mapDispatchToProps = (dispatch) => ({
togglePanel(uiElement) {
dispatch({
type: 'SCRATCHPAD_UI_TOGGLE_VISIBILITY',
payload: { uiElement },
});
},
setEditorLanguage(lang) {
dispatch({
type: 'SCRATCHPAD_EDITOR_SET_LANG',
payload: lang,
});
},
postPretest(props) {
const req = request.post(UiContext.postPretestUrl, {
lang: props.editorLang,
code: props.editorCode,
input: props.pretestInput,
});
dispatch({
type: 'SCRATCHPAD_POST_PRETEST',
payload: req,
});
},
postSubmit(props) {
const req = request.post(UiContext.postSubmitUrl, {
lang: props.editorLang,
code: props.editorCode,
});
dispatch({
type: 'SCRATCHPAD_POST_SUBMIT',
payload: req,
});
},
loadSubmissions() {
dispatch({
type: 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS',
payload: request.get(UiContext.getSubmissionsUrl),
});
},
handleClickRefresh() {
this.loadSubmissions();
},
});
export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadToolbarContainer extends React.PureComponent {
static contextTypes = {
store: PropTypes.object,
};
componentDidMount() {
this.props.loadSubmissions();
}
render() {
return (
<Toolbar>
<ToolbarButton
disabled={this.props.isPosting || this.props.isRunning}
className="scratchpad__toolbar__pretest"
onClick={() => this.props.postPretest(this.props)}
data-global-hotkey="f9"
data-tooltip={`${i18n('Pretest Your Code')} (F9)`}
>
<Icon name="debug" />
{' '}
{i18n('Run Pretest')}
{' '}
(F9)
</ToolbarButton>
<ToolbarButton
disabled={this.props.isPosting}
className="scratchpad__toolbar__submit"
onClick={() => this.props.postSubmit(this.props)}
data-global-hotkey="f10"
data-tooltip={`${i18n('Submit Your Code')} (F10)`}
>
<Icon name="play" />
{' '}
{i18n('Submit Solution')}
{' '}
(F10)
</ToolbarButton>
<ToolbarButton
data-tooltip={i18n('Refresh Records')}
className="scratchpad__toolbar__refresh"
data-global-hotkey="alt+f"
onClick={() => this.props.handleClickRefresh()}
>
<Icon name="refresh" />
{' '}
{i18n('Refresh')}
{' '}
(Alt+F)
</ToolbarButton>
<ToolbarButton
data-global-hotkey="alt+q"
data-tooltip={`${i18n('Quit Scratchpad')} (Alt+Q)`}
name="problem-sidebar__quit-scratchpad"
>
<Icon name="close" />
{' '}
{i18n('Exit')}
{' '}
(Alt+Q)
</ToolbarButton>
<ToolbarItem>
<select
className="select"
disabled={this.props.isPosting}
value={this.props.editorLang}
onChange={(ev) => this.props.setEditorLanguage(ev.target.value)}
>
{_.map(window.LANG_TEXTS, (val, key) => (
<option value={key} key={key}>{val}</option>
))}
</select>
</ToolbarItem>
<ToolbarSplit />
<ToolbarButton
activated={this.props.pretestVisible}
onClick={() => this.props.togglePanel('pretest')}
data-global-hotkey="alt+p"
data-tooltip={`${i18n('Toggle Pretest Panel')} (Alt+P)`}
>
<Icon name="edit" />
{' '}
{i18n('Pretest')}
</ToolbarButton>
<ToolbarButton
activated={this.props.recordsVisible}
onClick={() => this.props.togglePanel('records')}
data-global-hotkey="alt+r"
data-tooltip={`${i18n('Toggle Records Panel')} (Alt+R)`}
>
<Icon name="flag" />
{' '}
{i18n('Records')}
</ToolbarButton>
</Toolbar>
);
}
});
@@ -0,0 +1,53 @@
.scratchpad__toolbar
background: $toolbar-bg
padding: rem(8px)
font-size: rem($font-size-small)
font-weight: normal
.scratchpad__toolbar__item
margin-right: rem(5px)
.select
margin: 0
height: auto
line-height: 1
.scratchpad__toolbar__button
padding: rem(5px)
font-size: rem($font-size-small)
font-weight: normal
background: none
border: 1px solid transparent
color: $toolbar-fore
outline: 0
transition: .2s linear opacity
// Hack for Firefox bug #984869
&>div
display: flex
flex-direction: row
align-items: center
&.enabled
cursor: pointer
.icon
font-size: rem($font-size-icon)
margin-right: 2px
&.enabled:hover
color: $toolbar-fore-hover
background: $toolbar-bg-hover
&.activated
color: $toolbar-fore-selected !important
background: $toolbar-bg-selected !important
&.disabled
pointer-events: none
opacity: $toolbar-disabled-opacity
.scratchpad__toolbar__split
width: 1px
height: 1rem
background: #DDD
@@ -0,0 +1,90 @@
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default function ToolbarComponent(props) {
const {
className,
children,
...rest
} = props;
const cn = classNames(className, 'scratchpad__toolbar flex-row flex-cross-center');
return (
<div {...rest} className={cn}>{children}</div>
);
}
ToolbarComponent.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
export function ToolbarButtonComponent(props) {
const {
activated,
disabled,
onClick,
className,
children,
...rest
} = props;
const cn = classNames(className, 'scratchpad__toolbar__item scratchpad__toolbar__button', {
activated,
disabled,
enabled: !disabled,
});
return (
<button
{...rest}
tabIndex="-1"
className={cn}
onClick={() => !disabled && onClick && onClick()}
>
<div>{children}</div>
</button>
);
}
ToolbarButtonComponent.propTypes = {
activated: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func,
className: PropTypes.string,
children: PropTypes.node,
};
ToolbarButtonComponent.defaultProps = {
activated: false,
disabled: false,
};
export function ToolbarSplitComponent(props) {
const {
className,
...rest
} = props;
const cn = classNames(className, 'scratchpad__toolbar__item scratchpad__toolbar__split');
return (
<div {...rest} className={cn} />
);
}
ToolbarSplitComponent.propTypes = {
className: PropTypes.string,
};
export function ToolbarItemComponent(props) {
const {
className,
children,
...rest
} = props;
const cn = classNames(className, 'scratchpad__toolbar__item');
return (
<div {...rest} className={cn}>{children}</div>
);
}
ToolbarItemComponent.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
@@ -0,0 +1,88 @@
import React from 'react';
import SplitPane from 'react-split-pane';
import { connect } from 'react-redux';
import _ from 'lodash';
import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent';
import Dom from 'vj/components/react/DomComponent';
import ScratchpadToolbar from './ScratchpadToolbarContainer';
import ScratchpadEditor from './ScratchpadEditorContainer';
import ScratchpadPretest from './ScratchpadPretestContainer';
import ScratchpadRecords from './ScratchpadRecordsContainer';
function buildNestedPane([a, ...panes]) {
const elements = [
a,
...panes.filter((p) => p.props.visible),
];
if (elements.length === 1) {
return a;
}
return elements
.reduce((prev, curr) => (
<SplitPane
split="horizontal"
primary="second"
defaultSize={curr.props.size}
key={curr.element.key}
onChange={curr.onChange}
>
{prev}
{curr.element}
</SplitPane>
));
}
const mapStateToProps = (state) => ({
ui: state.ui,
});
const mapDispatchToProps = (dispatch) => ({
changeUiSize: _.debounce((uiElement, size) => {
dispatch({
type: 'SCRATCHPAD_UI_CHANGE_SIZE',
payload: {
uiElement,
size,
},
});
}, 500),
});
export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadContainer extends React.PureComponent {
handleChangeSize(uiElement, size) {
this.props.changeUiSize(uiElement, size);
$('#scratchpad').trigger('vjScratchpadRelayout');
this.props.ui.pretest.visible = !!this.props.ui.pretest.visible;
this.forceUpdate();
}
render() {
return (
<SplitPane
defaultSize={this.props.ui.main.size}
minSize={250}
split="vertical"
primary="second"
onChange={(size) => this.handleChangeSize('main', size)}
>
<Dom className="scratchpad__problem" childDom={$('.problem-content').get(0)} />
{buildNestedPane([
<SplitPaneFillOverlay key="editor" className="flex-col">
<ScratchpadToolbar />
<ScratchpadEditor />
</SplitPaneFillOverlay>,
{
props: this.props.ui.pretest,
onChange: (size) => this.handleChangeSize('pretest', size),
element: <ScratchpadPretest key="pretest" />,
},
{
props: this.props.ui.records,
onChange: (size) => this.handleChangeSize('records', size),
element: <ScratchpadRecords key="records" />,
},
])}
</SplitPane>
);
}
});
@@ -0,0 +1,21 @@
export default function reducer(state = {
lang: UiContext.codeLang,
code: UiContext.codeTemplate,
}, action) {
switch (action.type) {
case 'SCRATCHPAD_EDITOR_UPDATE_CODE': {
return {
...state,
code: action.payload,
};
}
case 'SCRATCHPAD_EDITOR_SET_LANG': {
return {
...state,
lang: action.payload,
};
}
default:
return state;
}
}
@@ -0,0 +1,14 @@
import { combineReducers } from 'redux';
import ui from './ui';
import editor from './editor';
import pretest from './pretest';
import records from './records';
const reducer = combineReducers({
ui,
editor,
pretest,
records,
});
export default reducer;
@@ -0,0 +1,29 @@
export default function reducer(state = {
input: '',
output: '',
rid: null,
}, action) {
if (action.type === 'SCRATCHPAD_PRETEST_DATA_CHANGE') {
const { type, value } = action.payload;
return {
...state,
[type]: value,
};
}
if (action.type === 'SCRATCHPAD_RECORDS_PUSH') {
const { rdoc } = action.payload;
if (rdoc._id === state.rid && rdoc.testCases[0]) {
return {
...state,
output: rdoc.testCases[0].message,
};
}
}
if (action.type === 'SCRATCHPAD_POST_PRETEST_FULFILLED') {
return {
...state,
rid: action.payload.rid,
};
}
return state;
}
@@ -0,0 +1,40 @@
import _ from 'lodash';
export default function reducer(state = {
rows: [],
items: {},
}, action) {
switch (action.type) {
case 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS_FULFILLED': {
const { rdocs } = action.payload;
return {
...state,
rows: _.map(rdocs, '_id'),
items: _.keyBy(rdocs, '_id'),
};
}
case 'SCRATCHPAD_RECORDS_PUSH': {
const { rdoc } = action.payload;
const rows = [...state.rows];
if (!rows.includes(rdoc._id)) {
return {
...state,
rows: [rdoc._id, ...state.rows],
items: {
...state.items,
[rdoc._id]: rdoc,
},
};
}
return {
...state,
items: {
...state.items,
[rdoc._id]: rdoc,
},
};
}
default:
return state;
}
}
@@ -0,0 +1,102 @@
import Notification from 'vj/components/notification';
export default function reducer(state = {
main: {
size: '50%',
},
pretest: {
visible: false,
size: 200,
},
records: {
visible: true,
size: 200,
isLoading: false,
},
isPosting: false,
}, action) {
switch (action.type) {
case 'SCRATCHPAD_UI_CHANGE_SIZE': {
const { uiElement, size } = action.payload;
return {
...state,
[uiElement]: {
...state[uiElement],
size,
},
};
}
case 'SCRATCHPAD_UI_SET_VISIBILITY': {
const { uiElement, visibility } = action.payload;
return {
...state,
[uiElement]: {
...state[uiElement],
visible: visibility,
},
};
}
case 'SCRATCHPAD_UI_TOGGLE_VISIBILITY': {
const { uiElement } = action.payload;
return {
...state,
[uiElement]: {
...state[uiElement],
visible: !state[uiElement].visible,
},
};
}
case 'SCRATCHPAD_POST_PRETEST_PENDING':
case 'SCRATCHPAD_POST_SUBMIT_PENDING': {
return {
...state,
isPosting: true,
};
}
case 'SCRATCHPAD_POST_PRETEST_FULFILLED':
case 'SCRATCHPAD_POST_SUBMIT_FULFILLED': {
return {
...state,
isPosting: false,
};
}
case 'SCRATCHPAD_POST_PRETEST_REJECTED':
case 'SCRATCHPAD_POST_SUBMIT_REJECTED': {
Notification.error(action.payload.message);
return {
...state,
isPosting: false,
};
}
case 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS_PENDING': {
return {
...state,
records: {
...state.records,
isLoading: true,
},
};
}
case 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS_REJECTED': {
Notification.error(action.payload.message);
return {
...state,
records: {
...state.records,
isLoading: false,
},
};
}
case 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS_FULFILLED': {
return {
...state,
records: {
...state.records,
isLoading: false,
},
};
}
default:
return state;
}
}
@@ -0,0 +1,55 @@
.scratchpad-container
position: absolute
background: #FFF
#scratchpad
position: absolute
left: 0
top: $nav-item-height
width: 100%
bottom: 0
.scratchpad__problem
position: absolute
left: 0
top: 0
width: 100%
height: 100%
overflow: auto
.scratchpad__toolbar__pretest.enabled:hover .icon
color: red
.scratchpad__toolbar__submit.enabled:hover .icon
color: green
.scratchpad__records__table
&.loading
// make a flash by setting opacity, otherwise users
// won't think that refreshing is completed!
opacity: 0.6
.col--at
width: rem(120px)
.col--time,
.col--memory
width: rem(70px)
.col--detail
border-right: 1px solid $table-border-color
.icol
display: inline-block
.icol--pretest
width: rem(60px)
.flag
font-size: rem(12px)
color: #FFF
padding: rem(2px 3px)
.icol--stat
color: #AAA
width: rem(60px)
@@ -0,0 +1,2 @@
$panel-header-height = 34px
$tab-color = #fff170