| @@ -0,0 +1,43 @@ | ||
| .TopBar { | ||
| width: 100vw; | ||
| height: 48px; | ||
| color: #fff; | ||
| z-index: 11; | ||
| /* background: linear-gradient(-45deg, #313358, #1D1F31); */ | ||
| /* background: #1d1f31; */ | ||
| /* background: #202231; */ | ||
| /* border-bottom: 1px solid #32335a; */ | ||
| } | ||
|
|
||
| .left { | ||
| Flex: row flex-start center; | ||
| Size: 30% 100%; | ||
| padding: 0 24px; | ||
| } | ||
|
|
||
| .right { | ||
| Flex: row flex-end center; | ||
| Size: 70% 100%; | ||
| padding: 0 24px; | ||
| } | ||
|
|
||
| /* .editorTopBar { | ||
| Size: 100% 32px; | ||
| background: var(--darkBlue1); | ||
| box-shadow: var(--shadow0); | ||
| z-index: 4; | ||
| position: relative; | ||
| } */ | ||
|
|
||
| .inner { | ||
| height: 100%; | ||
| display: flex; | ||
| align-items: center; | ||
| width: 100%; | ||
| z-index: 4; | ||
| position: relative; | ||
| } | ||
|
|
||
| .logo { | ||
| font-weight: 700; | ||
| } |
| @@ -0,0 +1,25 @@ | ||
| import * as React from 'react' | ||
| import { action, computed } from 'mobx' | ||
| import { Link } from 'react-router-dom' | ||
|
|
||
| import { createComponent } from '#utilities/createComponent' | ||
| import { lastInArray as last } from '#utilities/lastInArray' | ||
|
|
||
| import './TopBar.css' | ||
|
|
||
| export const TopBar = createComponent((self) => { | ||
| return () => ( | ||
| <div styleName="TopBar"> | ||
| <div styleName="inner"> | ||
| <div styleName="left" data-white-theme> | ||
| <Link to="/"> | ||
| <p styleName="logo">bytesized</p> | ||
| </Link> | ||
| </div> | ||
| <div styleName="right" data-dark-theme> | ||
| <p>...</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ) | ||
| }) |
| @@ -6,5 +6,5 @@ import { TopBar } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1 @@ | ||
| export { TopBar } from './TopBar' |
| @@ -0,0 +1,26 @@ | ||
| import * as React from 'react' | ||
| import { Controlled as CodeMirror } from 'react-codemirror2' | ||
| import { observer } from 'mobx-react' | ||
|
|
||
| import { EDITOR_OPTIONS } from './consts' | ||
| import './Editor.css' | ||
|
|
||
| type PropsT = { | ||
| content: string, | ||
| onChange(): void | ||
| } | ||
|
|
||
| export const Editor = (props: PropsT) => { | ||
| return ( | ||
| <div styleName="Editor"> | ||
| <CodeMirror | ||
| autoFocus | ||
| autoCursor | ||
| value={props.content} | ||
| options={EDITOR_OPTIONS} | ||
| className="bytesize-CodeMirror" | ||
| onBeforeChange={props.onChange} | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
| @@ -6,5 +6,5 @@ import { Editor } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1,10 @@ | ||
| export const EDITOR_OPTIONS = { | ||
| autoCloseBrackets: true, | ||
| cursorScrollMargin: 48, | ||
| mode: 'javascript', | ||
| theme: 'shit', | ||
| lineNumbers: true, | ||
| indentUnit: 2, | ||
| tabSize: 2, | ||
| styleActiveLine: true | ||
| } |
| @@ -0,0 +1,31 @@ | ||
| import * as React from 'react' | ||
| import { render } from 'react-dom' | ||
|
|
||
| import { AppContainer } from 'react-hot-loader' | ||
| import { App } from '#components/App' | ||
|
|
||
| import './styles/normalize.css' | ||
| import './styles/index.css' | ||
| import 'react-mde/lib/styles/css/react-mde-all.css' | ||
|
|
||
| const _render = (Component) => { | ||
| render( | ||
| <AppContainer> | ||
| <Component /> | ||
| </AppContainer>, | ||
| document.getElementById('mountPoint') | ||
| ) | ||
| } | ||
|
|
||
| // TODO: Ensure this gets eliminated with minification. | ||
| if (__DEV__) { | ||
| console.log('!!__DEV__') | ||
| if (module.hot) { | ||
| console.log('!!module.hot') | ||
| module.hot.accept('./components/App/index.js', () => { | ||
| _render(require('./components/App/index.js').App) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| _render(App) |
| @@ -0,0 +1,45 @@ | ||
| import * as React from 'react' | ||
| import { inject, observer } from 'mobx-react' | ||
|
|
||
| import { SideBar } from '#components/SideBar' | ||
| import { ContentSection } from '#components/ContentSection' | ||
|
|
||
| import './AuthScene.css' | ||
|
|
||
| const stateTreeSelector = (tree) => { | ||
| return { | ||
| $auth: tree.state.auth | ||
| } | ||
| } | ||
|
|
||
| @inject(stateTreeSelector) | ||
| @observer | ||
| export class AuthScene extends React.Component { | ||
| async componentDidMount() { | ||
| // const done = await this.props.$auth.handleAuth() | ||
| const auth = this.props.$auth.auth0 | ||
|
|
||
| auth.auth0.parseHash(async (err, authResult) => { | ||
| if (err) reject(err) | ||
|
|
||
| const response = await fetch( | ||
| `$SERVER_ADDRESS$$API_PATH$/users/${authResult.idTokenPayload.nickname}` | ||
| ) | ||
|
|
||
| const user = await response.json() | ||
| console.log({ user }) | ||
| this.props.$auth.setAuthData(authResult, user) | ||
| // this.props.$auth.setUserData(user) | ||
| this.props.history.push('/dashboard') | ||
| }) | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <main styleName="AuthScene"> | ||
| <SideBar>wait a mment</SideBar> | ||
| <ContentSection>were authing</ContentSection> | ||
| </main> | ||
| ) | ||
| } | ||
| } |
| @@ -0,0 +1,54 @@ | ||
| import * as React from 'react' | ||
| import { observer, inject } from 'mobx-react' | ||
| import { Route, Link, Switch } from 'react-router-dom' | ||
| import { observable, action } from 'mobx' | ||
| import { select, user, editor, auth } from '#state/selectors' | ||
|
|
||
| import { SideBar as SideBarWrapper } from '#components/SideBar' | ||
| import { ContentSection } from '#components/ContentSection' | ||
| import { SideBar } from './SideBar' | ||
| import './DashboardScene.css' | ||
| import { ModulesView } from './ModulesView' | ||
|
|
||
| @inject((tree) => { | ||
| return { | ||
| $user: tree.state.user | ||
| } | ||
| }) | ||
| @observer | ||
| export class DashboardScene extends React.Component { | ||
| @observable view = '' | ||
| @action | ||
| setView = (which) => { | ||
| this.view = which | ||
| } | ||
| render() { | ||
| return ( | ||
| <main styleName="DashboardScene"> | ||
| <SideBarWrapper> | ||
| <SideBar setView={this.setView} /> | ||
| </SideBarWrapper> | ||
| <ContentSection> | ||
| <div styleName="sideBar"> | ||
| <Choose> | ||
| <When condition={this.view.includes('modules')}> | ||
| <ModulesView user={this.props.$user} /> | ||
| </When> | ||
| <When condition={this.view.includes('community')}> | ||
| <p>community</p> | ||
| </When> | ||
| <When condition={this.view.includes('settings')}> | ||
| <p>settings</p> | ||
| </When> | ||
| <Otherwise> | ||
| <ModulesView user={this.props.$user} /> | ||
| </Otherwise> | ||
| </Choose> | ||
| </div> | ||
| </ContentSection> | ||
| </main> | ||
| ) | ||
| } | ||
| } |
| @@ -6,5 +6,5 @@ import { ModuleScene } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1,41 @@ | ||
| import * as React from 'react' | ||
| import { observer, inject } from 'mobx-react' | ||
| import { Route, Link, Switch } from 'react-router-dom' | ||
| import { observable, action } from 'mobx' | ||
| import { select, user, editor } from '#state/selectors' | ||
| import './ModulesView.css' | ||
|
|
||
| const Module = (props) => { | ||
| return ( | ||
| <div> | ||
| <Link to={`/modules/${props.userName}/${props.module.uid}`}> | ||
| <h3>{props.module.title}</h3> | ||
| </Link> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| @observer | ||
| export class ModulesView extends React.Component { | ||
| @observable loading = true | ||
|
|
||
| render() { | ||
| console.log( | ||
| 'RENDERRRR', | ||
| { props: this.props.user }, | ||
| this.props.user.userName | ||
| ) | ||
| return ( | ||
| <div styleName="ModulesView"> | ||
| <h2 styleName="title">Modules</h2> | ||
| <For each="module" of={this.props.user.modules}> | ||
| <Module | ||
| key={module.uid} | ||
| userName={this.props.user.userName} | ||
| module={module} | ||
| /> | ||
| </For> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -0,0 +1,16 @@ | ||
| import * as React from 'react' | ||
| import './Selection.css' | ||
|
|
||
| export const Selection = (props) => { | ||
| return ( | ||
| <div onClick={() => props.setView(props.routeTo)} styleName="Item"> | ||
| <div styleName="Thing"> | ||
| <props.icon /> | ||
| </div> | ||
| <div styleName="info"> | ||
| <h3>{props.title}</h3> | ||
| <p>{props.info}</p> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
| @@ -0,0 +1,38 @@ | ||
| import * as React from 'react' | ||
| import { observer, inject } from 'mobx-react' | ||
| import { observable, action } from 'mobx' | ||
|
|
||
| import { Selection } from './Selection' | ||
| import ListIcon from '#assets/svgs/list-0.svg' | ||
| import SocialIcon from '#assets/svgs/social-0.svg' | ||
| import SettingsIcon from '#assets/svgs/settings-0.svg' | ||
| import './SideBar.css' | ||
|
|
||
| export const SideBar = (props) => { | ||
| return ( | ||
| <div styleName="sideBar"> | ||
| <h1>Dashboard</h1> | ||
| <Selection | ||
| icon={ListIcon} | ||
| setView={props.setView} | ||
| routeTo={'modules'} | ||
| title="Your Modules" | ||
| info="View, edit, and review your modules." | ||
| /> | ||
| <Selection | ||
| icon={SocialIcon} | ||
| setView={props.setView} | ||
| routeTo={'community'} | ||
| title="Your Circle" | ||
| info="See what others in your community are doing." | ||
| /> | ||
| <Selection | ||
| icon={SettingsIcon} | ||
| setView={props.setView} | ||
| routeTo={'settings'} | ||
| title="Your Settings" | ||
| info="I promise we don't sell your shit like Zuck." | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
| @@ -0,0 +1,51 @@ | ||
| import * as React from 'react' | ||
| import { observable, action, computed } from 'mobx' | ||
| import { observer } from 'mobx-react' | ||
| import { Link } from 'react-router-dom' | ||
| import './Authenticate.css' | ||
|
|
||
| @observer | ||
| export class Authenticate extends React.Component { | ||
| @observable userName = '' | ||
| @observable password0 = '' | ||
| @observable password1 = '' | ||
|
|
||
| @action | ||
| setUserName = (event) => { | ||
| this.userName = event.target.value | ||
| } | ||
|
|
||
| @action | ||
| setPassword0 = (event) => { | ||
| this.password0 = event.target.value | ||
| } | ||
|
|
||
| @action | ||
| setPassword1 = (event) => { | ||
| this.password1 = event.target.value | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <div styleName="Authenticate"> | ||
| <h1>Log In</h1> | ||
| {/* <input | ||
| styleName="input" | ||
| placeholder="user name" | ||
| value={this.userName} | ||
| onChange={this.setUserName} | ||
| /> | ||
| <input | ||
| styleName="input" | ||
| type="password" | ||
| placeholder="password" | ||
| value={this.password0} | ||
| onChange={this.setPassword0} | ||
| /> */} | ||
| <button styleName="submitButton" onClick={this.props.login}> | ||
| Log In With Github | ||
| </button> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -6,5 +6,5 @@ import { ModuleScene } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1,13 @@ | ||
| import * as React from 'react' | ||
|
|
||
| import './Intro.css' | ||
|
|
||
| export const Intro = (props) => { | ||
| return ( | ||
| <div styleName="Intro"> | ||
| <h1> | ||
| Join a community built on micro code lessons, passion, and friendliness. | ||
| </h1> | ||
| </div> | ||
| ) | ||
| } |
| @@ -0,0 +1,42 @@ | ||
| export const CUSTOM_BUTTON_CLASS = 'rte-editor-custom-button' | ||
|
|
||
| export const STYLE_MAP = { | ||
| STRIKETHROUGH: { | ||
| textDecoration: 'line-through' | ||
| }, | ||
| CODE: { | ||
| padding: '8px', | ||
| background: '#f2f2f2' | ||
| } | ||
| } | ||
|
|
||
| export const TOOLBAR_CONFIG = { | ||
| // Optionally specify the groups to display (displayed in the order listed). | ||
| display: [ | ||
| 'INLINE_STYLE_BUTTONS', | ||
| 'BLOCK_TYPE_BUTTONS', | ||
| 'LINK_BUTTONS', | ||
| 'BLOCK_TYPE_DROPDOWN', | ||
| 'HISTORY_BUTTONS' | ||
| ], | ||
| INLINE_STYLE_BUTTONS: [ | ||
| { | ||
| label: 'Bold', | ||
| style: 'BOLD', | ||
| className: CUSTOM_BUTTON_CLASS | ||
| }, | ||
| { label: 'Italic', style: 'ITALIC', className: CUSTOM_BUTTON_CLASS }, | ||
| { label: 'Underline', style: 'UNDERLINE', className: CUSTOM_BUTTON_CLASS }, | ||
| { label: 'Monospace', style: 'CODE', className: CUSTOM_BUTTON_CLASS } | ||
| ], | ||
| BLOCK_TYPE_DROPDOWN: [ | ||
| { label: 'Normal', style: 'unstyled' }, | ||
| { label: 'Heading Large', style: 'header-one' }, | ||
| { label: 'Heading Medium', style: 'header-two' }, | ||
| { label: 'Heading Small', style: 'header-three' } | ||
| ], | ||
| BLOCK_TYPE_BUTTONS: [ | ||
| { label: 'UL', style: 'unordered-list-item' }, | ||
| { label: 'OL', style: 'ordered-list-item' } | ||
| ] | ||
| } |
| @@ -0,0 +1,33 @@ | ||
| import * as React from 'react' | ||
| import { Controlled as CodeMirror } from 'react-codemirror2' | ||
| import { Observer } from 'mobx-react' | ||
|
|
||
| type SelfT = { | ||
| props: {} | ||
| } | ||
|
|
||
| export const EDITOR_OPTIONS = { | ||
| autoCloseBrackets: true, | ||
| cursorScrollMargin: 48, | ||
| mode: 'javascript', | ||
| theme: 'shit', | ||
| lineNumbers: true, | ||
| indentUnit: 2, | ||
| tabSize: 2, | ||
| styleActiveLine: true, | ||
| readOnly: true | ||
| } | ||
|
|
||
| export const CodeRenderer = (props) => { | ||
| return ( | ||
| <div> | ||
| <CodeMirror | ||
| value={props.value} | ||
| className="bytesize-CodeMirror ofh" | ||
| options={EDITOR_OPTIONS} | ||
| autoCursor | ||
| autoFocus | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
| @@ -7,7 +7,7 @@ | ||
| } | ||
|
|
||
| .actions { | ||
| height: 60px !important; | ||
| Flex: row center center; | ||
| flex-shrink: 0; | ||
| background: #f8f8f8; | ||
| @@ -0,0 +1,54 @@ | ||
| import * as React from 'react' | ||
| import Markdown from 'react-markdown' | ||
| import { observer, inject } from 'mobx-react' | ||
| import { observable, action, computed } from 'mobx' | ||
|
|
||
| import { CodeRenderer } from './CodeRenderer' | ||
| import { LessonEditor } from './LessonEditor' | ||
| import { StateInjector } from '#utilities/StateInjector' | ||
| import EditIcon from '#assets/svgs/pencil.svg' | ||
|
|
||
| import './InstructionPanel.css' | ||
|
|
||
| const stateSelector = (tree) => { | ||
| return { | ||
| $lesson: tree.state.lesson | ||
| } | ||
| } | ||
|
|
||
| @inject(stateSelector) | ||
| @observer | ||
| // TODO: Make not a class, man. | ||
| export class InstructionPanel extends React.Component { | ||
| render() { | ||
| return ( | ||
| <div | ||
| styleName={`InstructionPanel light`} | ||
| className={`bytesize-light-theme`} | ||
| > | ||
| <Choose> | ||
| <When condition={!this.props.$lesson.editing}> | ||
| <Markdown | ||
| source={this.props.$lesson.content} | ||
| styleName="markdown" | ||
| renderers={{ | ||
| code: CodeRenderer | ||
| }} | ||
| /> | ||
| <div styleName="actions"> | ||
| <If condition={this.props.$lesson.isEditableByUser}> | ||
| <EditIcon | ||
| styleName="editButton" | ||
| onClick={this.props.$lesson.toggleEditing} | ||
| /> | ||
| </If> | ||
| </div> | ||
| </When> | ||
| <Otherwise> | ||
| <LessonEditor lesson={this.props.$lesson} /> | ||
| </Otherwise> | ||
| </Choose> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -6,5 +6,5 @@ import { InstructionPanel } from './InstructionPanel' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -57,8 +57,8 @@ | ||
| .lessonEditorControls { | ||
| margin-top: auto; | ||
| padding: 12px; | ||
| width: 100%; | ||
| height: 60px !important; | ||
| flex-shrink: 0; | ||
| background: #f8f8f8; | ||
| Flex: row space-around center; | ||
| @@ -0,0 +1,84 @@ | ||
| import * as React from 'react' | ||
| import { createComponent } from '#utilities/createComponent' | ||
| import ReactMde from 'react-mde' | ||
| import * as Showdown from 'showdown' | ||
| import { inject, observer } from 'mobx-react' | ||
| import { EditorState } from 'draft-js' | ||
| import { CodeRenderer } from '../CodeRenderer' | ||
| import Markdown from 'react-markdown' | ||
|
|
||
| import './LessonEditor.css' | ||
|
|
||
| // NOTE: Experimental alternative React API I came up with. | ||
| // NOTE: I really like this API...... | ||
| export const LessonEditor = createComponent((self) => { | ||
| const { props } = self | ||
| const { lesson } = props | ||
|
|
||
| const converter = new Showdown.Converter({ | ||
| tables: true, | ||
| simplifiedAutoLink: true | ||
| }) | ||
|
|
||
| self.state = { | ||
| editorState: { | ||
| markdown: lesson.editedContent | ||
| } | ||
| } | ||
|
|
||
| const onChange = (editorState) => { | ||
| lesson.setContent(editorState) | ||
| self.setState((state) => ({ editorState })) | ||
| } | ||
|
|
||
| const generator = (markdown) => { | ||
| return Promise.resolve(converter.makeHtml(markdown)) | ||
| } | ||
|
|
||
| const styleName = (props) => { | ||
| return lesson.previewing ? 'previewing' : lesson.editing ? 'editing' : '' | ||
| } | ||
|
|
||
| return () => { | ||
| return ( | ||
| <div styleName={`LessonEditor ${styleName(self.props)}`}> | ||
| <Choose> | ||
| <When condition={lesson.previewing}> | ||
| <MD markdown={lesson.editedContent} /> | ||
| </When> | ||
| <Otherwise> | ||
| <ReactMde | ||
| onChange={onChange} | ||
| editorState={self.state.editorState} | ||
| generateMarkdownPreview={generator} | ||
| /> | ||
| </Otherwise> | ||
| </Choose> | ||
| <div styleName="lessonEditorControls"> | ||
| <i className="fas fa-ban fa-lg" onClick={() => {}} /> | ||
| <span onClick={lesson.togglePreviewing}> | ||
| <i className="fas fa-eye fa-lg" /> | ||
| </span> | ||
| <span onClick={lesson.saveContent}> | ||
| <i className="fas fa-save fa-lg" /> | ||
| </span> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| }) | ||
|
|
||
| class MD extends React.PureComponent { | ||
| render() { | ||
| return ( | ||
| <div styleName="lessonPreview"> | ||
| <Markdown | ||
| source={this.props.markdown} | ||
| renderers={{ | ||
| code: CodeRenderer | ||
| }} | ||
| /> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -0,0 +1,21 @@ | ||
| import * as React from 'react' | ||
|
|
||
| import './TagBox.css' | ||
|
|
||
| const Tag = (props) => { | ||
| return ( | ||
| <span styleName="Tag"> | ||
| <small>{props.name}</small> | ||
| </span> | ||
| ) | ||
| } | ||
|
|
||
| export const TagBox = (props) => { | ||
| return ( | ||
| <div styleName="TagBox"> | ||
| <For each="tag" of={props.tags} index="index"> | ||
| <Tag key={tag.name} name={tag.name} /> | ||
| </For> | ||
| </div> | ||
| ) | ||
| } |
| @@ -85,13 +85,13 @@ Example: \`remmy comp MyNewComponent\` | ||
| ` | ||
|
|
||
| export const MOCK_TAGS = [ | ||
| { | ||
| name: 'javascript' | ||
| }, | ||
| { | ||
| name: 'promises' | ||
| }, | ||
| { | ||
| name: 'beginner' | ||
| } | ||
| ] | ||
| @@ -0,0 +1,55 @@ | ||
| import * as React from 'react' | ||
| import PanelGroup from 'react-panelgroup' | ||
| import { inject, observer } from 'mobx-react' | ||
| import MarkdownInput from '@opuscapita/react-markdown' | ||
|
|
||
| import { Editor } from '#features/Editor' | ||
| import { InstructionPanel } from './InstructionPanel' | ||
| import { OutputPanel } from './OutputPanel' | ||
|
|
||
| import { PANEL_SETTINGS, ROW_PANEL_SETTINGS } from './consts' | ||
| import './ModuleScene.css' | ||
|
|
||
| const selector = (tree) => { | ||
| return { | ||
| $main: tree.state, | ||
| $editor: tree.state.editor | ||
| } | ||
| } | ||
|
|
||
| @inject(selector) | ||
| @observer | ||
| export class ModuleScene extends React.Component { | ||
| componentDidMount() { | ||
| this.props.$main.activateModule(this.props.match.params) | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <div styleName="ModuleScene"> | ||
| <InstructionPanel | ||
| source={this.props.$editor.instructions} | ||
| setInstructions={this.props.$editor.setInstructions} | ||
| /> | ||
| <div styleName="right"> | ||
| <PanelGroup | ||
| direction="column" | ||
| borderColor={'transparent'} | ||
| panelWidths={PANEL_SETTINGS} | ||
| > | ||
| <Editor | ||
| content={this.props.$editor.content} | ||
| onChange={(editor, data, content) => { | ||
| this.props.$editor.setContent(content) | ||
| }} | ||
| /> | ||
| <OutputPanel | ||
| ref={this.OutputPanel} | ||
| format={this.props.$editor.formatContent} | ||
| /> | ||
| </PanelGroup> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -6,5 +6,5 @@ import { ModuleScene } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1,23 @@ | ||
| import * as React from 'react' | ||
| import moment from 'moment' | ||
| import { inject, observer } from 'mobx-react' | ||
|
|
||
| import './ControlPanel.css' | ||
|
|
||
| const Control = (props) => { | ||
| return ( | ||
| <div onClick={props.action} styleName="Control"> | ||
| <small>{props.title}</small> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export const ControlPanel = (props) => { | ||
| return ( | ||
| <div styleName="ControlPanel"> | ||
| <Control title="Execute" action={props.execute} /> | ||
| <Control title="Format" action={props.format} /> | ||
| <Control title="Clear" action={props.clear} /> | ||
| </div> | ||
| ) | ||
| } |
| @@ -0,0 +1,61 @@ | ||
| import * as React from 'react' | ||
| import moment from 'moment' | ||
| import { inject, observer } from 'mobx-react' | ||
| import StayScrolled from 'react-stay-scrolled' | ||
| import Inspector from 'react-inspector' | ||
|
|
||
| import PlayButton from '#assets/svgs/play-0.svg' | ||
| import OptionsButton from '#assets/svgs/more-0.svg' | ||
|
|
||
| import ErrorIcon from '#assets/svgs/alert-0.svg' | ||
| import WarningIcon from '#assets/svgs/alert-1.svg' | ||
| import NormalIcon from '#assets/svgs/alert-2.svg' | ||
|
|
||
| import { theme, errorTheme } from '../theme' | ||
| import { warningTheme } from './warningTheme' | ||
| import './OutputBlock.css' | ||
|
|
||
| import { ObjectRootLabel } from 'react-inspector' | ||
| import { ObjectLabel } from 'react-inspector' | ||
|
|
||
| const LogIcon = (props) => { | ||
| switch (props.log.type) { | ||
| case 'ERROR': | ||
| return <ErrorIcon styleName="LogIcon" /> | ||
| case 'STDERR': | ||
| return <ErrorIcon styleName="LogIcon" /> | ||
| case 'WARNING': | ||
| return <WarningIcon styleName="LogIcon" /> | ||
| default: | ||
| return <NormalIcon styleName="LogIcon" /> | ||
| } | ||
| } | ||
|
|
||
| const getLogTheme = (log) => { | ||
| if (log.type === 'WARNING') { | ||
| return warningTheme | ||
| } | ||
|
|
||
| return ['INFO', 'STDOUT'].includes(log.type) ? theme : errorTheme | ||
| } | ||
|
|
||
| export const OutputBlock = (props) => { | ||
| return ( | ||
| <div styleName={`OutputBlock ${props.log.type}`}> | ||
| <Choose> | ||
| <When condition={props.log.type === 'INFO'}> | ||
| <LogIcon log={props.log} /> | ||
| <p key={props.log.uid}>{props.log.message}</p> | ||
| </When> | ||
| <Otherwise> | ||
| <LogIcon log={props.log} /> | ||
| <Inspector | ||
| key={props.log.uid} | ||
| theme={getLogTheme(props.log)} | ||
| data={props.log.message} | ||
| /> | ||
| </Otherwise> | ||
| </Choose> | ||
| </div> | ||
| ) | ||
| } |
| @@ -0,0 +1,48 @@ | ||
| export const warningTheme = { | ||
| BASE_FONT_FAMILY: 'Fira Code, monospace', | ||
| BASE_FONT_SIZE: '22px', | ||
| BASE_LINE_HEIGHT: '1.9', | ||
|
|
||
| // Background color matches TopBar. | ||
| BASE_BACKGROUND_COLOR: 'transparent', | ||
| // "Base font color." | ||
| BASE_COLOR: 'rgba(255, 255, 255, 0.7)', | ||
| // rgb(255, 239, 110) | ||
| // object.PROPERTY color. | ||
| OBJECT_NAME_COLOR: '#fff', | ||
|
|
||
| OBJECT_VALUE_NULL_COLOR: 'rgb(222, 127, 127)', | ||
| OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(127, 127, 127)', | ||
| OBJECT_VALUE_REGEXP_COLOR: '#fa9119', | ||
| OBJECT_VALUE_STRING_COLOR: '#fa9119', | ||
| OBJECT_VALUE_SYMBOL_COLOR: '#fa9119', | ||
| OBJECT_VALUE_NUMBER_COLOR: 'rgb(134, 255, 128)', | ||
| OBJECT_VALUE_BOOLEAN_COLOR: 'rgb(51, 213, 243)', | ||
| OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'rgb(242, 85, 217)', | ||
|
|
||
| HTML_TAG_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_TEXT_TRANSFORM: 'lowercase', | ||
| HTML_ATTRIBUTE_NAME_COLOR: 'rgb(155, 187, 220)', | ||
| HTML_ATTRIBUTE_VALUE_COLOR: 'rgb(242, 151, 102)', | ||
| HTML_COMMENT_COLOR: 'rgb(137, 137, 137)', | ||
| HTML_DOCTYPE_COLOR: 'rgb(192, 192, 192)', | ||
|
|
||
| ARROW_COLOR: 'rgb(145, 145, 145)', | ||
| ARROW_MARGIN_RIGHT: 3, | ||
| ARROW_FONT_SIZE: 12, | ||
|
|
||
| // Each <li> | ||
| TREENODE_FONT_FAMILY: 'Fira code, monospace', | ||
| TREENODE_FONT_SIZE: '13px', | ||
| TREENODE_LINE_HEIGHT: '1.7', | ||
| TREENODE_PADDING_LEFT: 24, | ||
|
|
||
| TABLE_BORDER_COLOR: 'rgb(85, 85, 85)', | ||
| TABLE_TH_BACKGROUND_COLOR: 'rgb(44, 44, 44)', | ||
| TABLE_TH_HOVER_COLOR: 'rgb(48, 48, 48)', | ||
| TABLE_SORT_ICON_COLOR: 'black', | ||
| TABLE_DATA_BACKGROUND_IMAGE: | ||
| 'linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0) 50%, rgba(51, 139, 255, 0.0980392) 50%, rgba(51, 139, 255, 0.0980392))', | ||
| TABLE_DATA_BACKGROUND_SIZE: '128px 32px' | ||
| } |
| @@ -1,6 +1,6 @@ | ||
| import { ObjectRootLabel } from 'react-inspector' | ||
| import { ObjectLabel } from 'react-inspector' | ||
|
|
||
| export const OutputError = (props) => { | ||
| return <ObjectRootLabel name={name} data={data} /> | ||
| } |
| @@ -0,0 +1,19 @@ | ||
| import { ObjectRootLabel } from 'react-inspector' | ||
| import { ObjectLabel } from 'react-inspector' | ||
|
|
||
| const defaultNodeRenderer = ({ | ||
| depth, | ||
| name, | ||
| data, | ||
| isNonenumerable, | ||
| expanded | ||
| }) => | ||
| depth === 0 ? ( | ||
| <ObjectRootLabel name={name} data={data} /> | ||
| ) : ( | ||
| <ObjectLabel name={name} data={data} isNonenumerable={isNonenumerable} /> | ||
| ) | ||
|
|
||
| export const OutputError = (props) => { | ||
| return <ObjectRootLabel name={name} data={data} /> | ||
| } |
| @@ -0,0 +1,78 @@ | ||
| import * as React from 'react' | ||
| import { inject, observer } from 'mobx-react' | ||
|
|
||
| import { createSocket } from './socket' | ||
| import PlayButton from '#assets/svgs/play-0.svg' | ||
| import MagicWand from '#assets/svgs/magic-0.svg' | ||
| import OptionsButton from '#assets/svgs/more-0.svg' | ||
| import { OutputBlock } from './OutputBlock' | ||
| import { ControlPanel } from './ControlPanel' | ||
|
|
||
| import './OutputPanel.css' | ||
|
|
||
| import { ObjectRootLabel } from 'react-inspector' | ||
|
|
||
| export const OutputError = (props) => { | ||
| return <ObjectRootLabel name={props.name} data={props.data} /> | ||
| } | ||
|
|
||
| const stateTreeSelector = (tree) => { | ||
| return { | ||
| $editor: tree.state.editor, | ||
| $output: tree.state.output | ||
| } | ||
| } | ||
|
|
||
| @inject(stateTreeSelector) | ||
| @observer | ||
| export class OutputPanel extends React.Component { | ||
| $output = this.props.$output | ||
|
|
||
| componentWillMount() { | ||
| this.props.$output.clearLogs() | ||
| this.scrollBox = React.createRef() | ||
| this.socket = createSocket(this) | ||
| } | ||
|
|
||
| componentWillUnmount() { | ||
| this.props.$output.clearLogs() | ||
| this.socket.close() | ||
| } | ||
|
|
||
| componentDidUpdate(oldProps) { | ||
| // TODO: Add condition to maintain scroll position if the user | ||
| // has manually scrolled from the bottom. | ||
| this.scrollBox.current.scrollTop = 9999 | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <div styleName="OutputPanel" data-bytesize-output-panel> | ||
| <ControlPanel | ||
| format={this.props.format} | ||
| execute={this.socket.execute} | ||
| clear={this.$output.clearLogs} | ||
| /> | ||
| <div styleName="output" ref={this.scrollBox}> | ||
| <Choose> | ||
| <When condition={!this.$output.logCount}> | ||
| <OutputBlock | ||
| log={{ | ||
| type: 'INFO', | ||
| message: '# output will appear here', | ||
| uid: '4y9fjuaeu3q9' | ||
| }} | ||
| key={'4y9fjuaeu3q9'} | ||
| /> | ||
| </When> | ||
| <Otherwise> | ||
| <For each="log" of={this.$output.logs} index="index"> | ||
| <OutputBlock log={log} key={log.uid} /> | ||
| </For> | ||
| </Otherwise> | ||
| </Choose> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| } |
| @@ -6,5 +6,5 @@ import { OutputPanel } from '../index' | ||
| const { it, expect } = global | ||
|
|
||
| it('works and stuff', () => { | ||
| expect(true).toBe(true) | ||
| }) | ||
| @@ -0,0 +1,54 @@ | ||
| export const createSocket = (self) => { | ||
| let socket = new WebSocket('$SOCKET_ADDRESS$/run') | ||
|
|
||
| socket.addEventListener('open', (event) => { | ||
| console.log('socket "/run": connected') | ||
| }) | ||
|
|
||
| socket.addEventListener('close', (event) => { | ||
| self.props.$output.addSocketDisconnectLog() | ||
| console.log('socket "/run": disconnected') | ||
| }) | ||
|
|
||
| let timeout = null | ||
| const cache = [] | ||
|
|
||
| const handleLog = (log) => { | ||
| timeout && (clearTimeout(timeout), console.log('clearing timeout')) | ||
| cache.push(log) | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| timeout = setTimeout(() => { | ||
| // console.log('done with timeout') | ||
| resolve(cache) | ||
| }, 350) | ||
| }) | ||
| } | ||
|
|
||
| socket.addEventListener('message', (event) => { | ||
| const data = JSON.parse(event.data) | ||
| handleLog(data).then((cache) => self.props.$output.updateLogs(cache)) | ||
| }) | ||
|
|
||
| return { | ||
| close() { | ||
| socket.close() | ||
| }, | ||
|
|
||
| execute() { | ||
| self.props.$output.addExecutionLog() | ||
|
|
||
| if (socket.readyState === 3) { | ||
| try { | ||
| socket = new WebSocket('$SOCKET_ADDRESS$/run') | ||
| socket.send(JSON.stringify({ code: self.props.$editor.content })) | ||
| } catch (error) { | ||
| self.props.$output.addSocketNotConnectedLog() | ||
| self.props.$output.addErrorLog(JSON.stringify(error)) | ||
| } | ||
| } else { | ||
| socket.send(JSON.stringify({ code: self.props.$editor.content })) | ||
| } | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,97 @@ | ||
| export const theme = { | ||
| BASE_FONT_FAMILY: 'Fira Code, monospace', | ||
| BASE_FONT_SIZE: '14px', | ||
| BASE_LINE_HEIGHT: '1.9', | ||
|
|
||
| // Background color matches TopBar. | ||
| BASE_BACKGROUND_COLOR: 'rgba(0,0,0,0)', | ||
| // "Base font color." | ||
| BASE_COLOR: 'rgba(255, 255, 255, 0.7)', | ||
| // rgb(255, 239, 110) | ||
| // object.PROPERTY color. | ||
| OBJECT_NAME_COLOR: '#fff', | ||
|
|
||
| OBJECT_VALUE_NULL_COLOR: 'rgb(222, 127, 127)', | ||
| OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(127, 127, 127)', | ||
| OBJECT_VALUE_REGEXP_COLOR: '#19fac5', | ||
| OBJECT_VALUE_STRING_COLOR: '#19fac5', | ||
| OBJECT_VALUE_SYMBOL_COLOR: '#19fac5', | ||
| OBJECT_VALUE_NUMBER_COLOR: 'rgb(134, 255, 128)', | ||
| OBJECT_VALUE_BOOLEAN_COLOR: 'rgb(51, 213, 243)', | ||
| OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'rgb(242, 85, 217)', | ||
|
|
||
| HTML_TAG_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_TEXT_TRANSFORM: 'lowercase', | ||
| HTML_ATTRIBUTE_NAME_COLOR: 'rgb(155, 187, 220)', | ||
| HTML_ATTRIBUTE_VALUE_COLOR: 'rgb(242, 151, 102)', | ||
| HTML_COMMENT_COLOR: 'rgb(137, 137, 137)', | ||
| HTML_DOCTYPE_COLOR: 'rgb(192, 192, 192)', | ||
|
|
||
| ARROW_COLOR: 'rgb(145, 145, 145)', | ||
| ARROW_MARGIN_RIGHT: 3, | ||
| ARROW_FONT_SIZE: 12, | ||
|
|
||
| // Each <li> | ||
| TREENODE_FONT_FAMILY: 'Fira Code, monospace', | ||
| TREENODE_FONT_SIZE: '13px', | ||
| TREENODE_LINE_HEIGHT: '1.4', | ||
| TREENODE_PADDING_LEFT: 12, | ||
|
|
||
| TABLE_BORDER_COLOR: 'rgb(85, 85, 85)', | ||
| TABLE_TH_BACKGROUND_COLOR: 'rgb(44, 44, 44)', | ||
| TABLE_TH_HOVER_COLOR: 'rgb(48, 48, 48)', | ||
| TABLE_SORT_ICON_COLOR: 'black', | ||
| TABLE_DATA_BACKGROUND_IMAGE: | ||
| 'linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0) 50%, rgba(51, 139, 255, 0.0980392) 50%, rgba(51, 139, 255, 0.0980392))', | ||
| TABLE_DATA_BACKGROUND_SIZE: '128px 32px' | ||
| } | ||
|
|
||
| export const errorTheme = { | ||
| BASE_FONT_FAMILY: 'Fira Code, monospace', | ||
| BASE_FONT_SIZE: '22px', | ||
| BASE_LINE_HEIGHT: '1.9', | ||
|
|
||
| // Background color matches TopBar. | ||
| BASE_BACKGROUND_COLOR: '#1c1325', | ||
| // "Base font color." | ||
| BASE_COLOR: 'rgba(255, 255, 255, 0.7)', | ||
| // rgb(255, 239, 110) | ||
| // object.PROPERTY color. | ||
| OBJECT_NAME_COLOR: '#fff', | ||
|
|
||
| OBJECT_VALUE_NULL_COLOR: 'rgb(222, 127, 127)', | ||
| OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(127, 127, 127)', | ||
| OBJECT_VALUE_REGEXP_COLOR: '#F86291', | ||
| OBJECT_VALUE_STRING_COLOR: '#F86291', | ||
| OBJECT_VALUE_SYMBOL_COLOR: '#F86291', | ||
| OBJECT_VALUE_NUMBER_COLOR: 'rgb(134, 255, 128)', | ||
| OBJECT_VALUE_BOOLEAN_COLOR: 'rgb(51, 213, 243)', | ||
| OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'rgb(242, 85, 217)', | ||
|
|
||
| HTML_TAG_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_COLOR: 'rgb(93, 176, 215)', | ||
| HTML_TAGNAME_TEXT_TRANSFORM: 'lowercase', | ||
| HTML_ATTRIBUTE_NAME_COLOR: 'rgb(155, 187, 220)', | ||
| HTML_ATTRIBUTE_VALUE_COLOR: 'rgb(242, 151, 102)', | ||
| HTML_COMMENT_COLOR: 'rgb(137, 137, 137)', | ||
| HTML_DOCTYPE_COLOR: 'rgb(192, 192, 192)', | ||
|
|
||
| ARROW_COLOR: 'rgb(145, 145, 145)', | ||
| ARROW_MARGIN_RIGHT: 3, | ||
| ARROW_FONT_SIZE: 12, | ||
|
|
||
| // Each <li> | ||
| TREENODE_FONT_FAMILY: 'Fira code, monospace', | ||
| TREENODE_FONT_SIZE: '13px', | ||
| TREENODE_LINE_HEIGHT: '1.7', | ||
| TREENODE_PADDING_LEFT: 24, | ||
|
|
||
| TABLE_BORDER_COLOR: 'rgb(85, 85, 85)', | ||
| TABLE_TH_BACKGROUND_COLOR: 'rgb(44, 44, 44)', | ||
| TABLE_TH_HOVER_COLOR: 'rgb(48, 48, 48)', | ||
| TABLE_SORT_ICON_COLOR: 'black', | ||
| TABLE_DATA_BACKGROUND_IMAGE: | ||
| 'linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0) 50%, rgba(51, 139, 255, 0.0980392) 50%, rgba(51, 139, 255, 0.0980392))', | ||
| TABLE_DATA_BACKGROUND_SIZE: '128px 32px' | ||
| } |
| @@ -0,0 +1,9 @@ | ||
| export const PANEL_SETTINGS = [ | ||
| { size: '50', resize: 'stretch' }, | ||
| { size: '50', resize: 'stretch' } | ||
| ] | ||
|
|
||
| export const ROW_PANEL_SETTINGS = [ | ||
| { size: '50%', minSize: 500, resize: 'stretch' }, | ||
| { size: '50%', minSize: 500, resize: 'stretch' } | ||
| ] |
| @@ -0,0 +1,56 @@ | ||
| import { types, flow, getParent } from 'mobx-state-tree' | ||
| import queryString from 'query-string' | ||
|
|
||
| import { auth } from './auth0' | ||
|
|
||
| const model = { | ||
| authenticated: types.optional(types.boolean, false), | ||
| accessToken: types.optional(types.string, ''), | ||
| expiresIn: types.optional(types.number, 0), | ||
| idToken: types.optional(types.string, ''), | ||
| state: types.optional(types.string, ''), | ||
| tokenType: types.optional(types.string, '') | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| return { | ||
| logIn() { | ||
| auth.logIn() | ||
| }, | ||
|
|
||
| logOut() { | ||
| auth.logOut() | ||
| }, | ||
|
|
||
| setAuthData(authData, user) { | ||
| self.authenticated = true | ||
| self.accessToken = authData.accessToken | ||
| self.expiresIn = authData.expiresIn | ||
| self.tokenType = authData.tokenType | ||
| self.idToken = authData.idToken | ||
| self.state = authData.state | ||
|
|
||
| auth.setLocalStorageAuth(authData) | ||
| getParent(self, 1).user.setData(user) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const views = (self) => { | ||
| return { | ||
| get isAuthenticated() { | ||
| return self.authenticated | ||
| ? new Date().getTime() < JSON.parse(localStorage.getItem('expires_at')) | ||
| : false | ||
| }, | ||
|
|
||
| get auth0() { | ||
| return auth | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export default types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,42 @@ | ||
| import auth0 from 'auth0-js' | ||
|
|
||
| class Auth { | ||
| auth0 = new auth0.WebAuth({ | ||
| domain: 'bytesized.auth0.com', | ||
| clientID: 'wt4u1X_a7fPOkQ_3RUDJoHdMvFSyOGPo', | ||
| redirectUri: 'http://localhost:9000/authenticating', | ||
| audience: 'https://bytesized.auth0.com/userinfo', | ||
| responseType: 'token id_token', | ||
| scope: 'openid profile' | ||
| }) | ||
|
|
||
| logIn = () => { | ||
| this.auth0.authorize() | ||
| } | ||
|
|
||
| logOut = () => { | ||
| localStorage.removeItem('accessToken') | ||
| localStorage.removeItem('idToken') | ||
| localStorage.removeItem('expires_at') | ||
| window.location.assign('/') | ||
| } | ||
|
|
||
| handleAuth = () => { | ||
| return this.auth0.parseHash((err, authResult) => { | ||
| return err ? { error: err } : authResult | ||
| }) | ||
| } | ||
|
|
||
| setLocalStorageAuth = (authData) => { | ||
| localStorage.setItem('accessToken', authData.accessToken) | ||
| localStorage.setItem('idToken', authData.idToken) | ||
| localStorage.setItem('userData', JSON.stringify(authData.idTokenPayload)) | ||
|
|
||
| localStorage.setItem( | ||
| 'expires_at', | ||
| JSON.stringify(authData.expiresIn * 1000 + new Date().getTime()) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| export const auth = new Auth() |
| @@ -0,0 +1,17 @@ | ||
| import { types } from 'mobx-state-tree' | ||
| import queryString from 'query-string' | ||
|
|
||
| const model = {} | ||
|
|
||
| const actions = (self) => { | ||
| return {} | ||
| } | ||
|
|
||
| const views = (self) => { | ||
| return {} | ||
| } | ||
|
|
||
| export default types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,34 @@ | ||
| import { types, flow } from 'mobx-state-tree' | ||
| import { prettier } from '#utilities/api/prettier' | ||
|
|
||
| import { INFINITE_LOOP_DEFAULT_CONTENT } from './consts' | ||
|
|
||
| const model = { | ||
| content: types.optional(types.string, '// placeholder') | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| const history = [] | ||
|
|
||
| return { | ||
| setContent(content) { | ||
| self.content = content | ||
| }, | ||
|
|
||
| formatContent: flow(function*() { | ||
| const { code, error } = yield prettier(self.content) | ||
| self.setContent(code) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| const views = (self) => ({ | ||
| get characterCount() { | ||
| return self.content.length | ||
| } | ||
| }) | ||
|
|
||
| export default types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,65 @@ | ||
| import { types, getParent } from 'mobx-state-tree' | ||
| import { autorun } from 'mobx' | ||
| import uuid from 'uuid/v4' | ||
| import { Editor, EditorState } from 'draft-js' | ||
|
|
||
| const model = { | ||
| uid: types.optional(types.string, () => uuid()), | ||
| moduleUid: types.optional(types.string, '0'), | ||
| editing: types.optional(types.boolean, false), | ||
| previewing: types.optional(types.boolean, false), | ||
| editedContent: types.optional(types.string, () => '# hello'), | ||
| content: types.optional(types.string, () => '# hello') | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| return { | ||
| toggleEditing() { | ||
| const editing = !self.editing | ||
| self.editing = editing | ||
| editing && (self.previewing = false) | ||
| }, | ||
|
|
||
| togglePreviewing() { | ||
| self.previewing = !self.previewing | ||
| }, | ||
|
|
||
| setContent(content) { | ||
| self.editing | ||
| ? (self.editedContent = content.markdown) | ||
| : (self.content = content.markdown) | ||
| }, | ||
|
|
||
| setModuleUid(uid) { | ||
| console.log('setting module uid', uid) | ||
| self.moduleUid = String(uid) | ||
| }, | ||
|
|
||
| saveContent() { | ||
| self.editing = false | ||
| self.content = self.editedContent | ||
| // console.log('SAVED') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const views = (self) => { | ||
| return { | ||
| get markdown() { | ||
| return self.editing ? self.editedContent : self.content | ||
| }, | ||
|
|
||
| // TODO: Figure out why this won't work as a view. | ||
| isEditableByUser() { | ||
| return getParent(self, 1).user.modules.some((module) => { | ||
| return module.uid == self.moduleUid | ||
| }) | ||
| // return JSON.parse(localStorage.getItem('userData')).uid | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export const LessonState = types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,14 @@ | ||
| import { flow } from 'mobx-state-tree' | ||
| import * as utilities from '../utilities' | ||
|
|
||
| export const activateModule = (self) => | ||
| flow(function*(options) { | ||
| const response = yield fetch(utilities.modulePath(options)) | ||
| const data = yield response.json() | ||
|
|
||
| console.log('activateModule', { data }) | ||
|
|
||
| self.editor.setContent(data.module.editorContent) | ||
| self.lesson.setContent(data.module.lessonContent) | ||
| self.lesson.setModuleUid(data.module.uid) | ||
| }) |
| @@ -0,0 +1,3 @@ | ||
| export const modulePath = ({ userName, id }) => { | ||
| return '$SERVER_ADDRESS$$API_PATH$/module/' + userName + '/' + id | ||
| } |
| @@ -0,0 +1,23 @@ | ||
| import { types } from 'mobx-state-tree' | ||
| import queryString from 'query-string' | ||
|
|
||
| const model = { | ||
| editorContent: types.optional(types.string, ''), | ||
| lessonContent: types.optional(types.string, ''), | ||
| title: types.optional(types.string, ''), | ||
| uid: types.optional(types.number, 0), | ||
| _id: types.optional(types.string, '') | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| return {} | ||
| } | ||
|
|
||
| const views = (self) => { | ||
| return {} | ||
| } | ||
|
|
||
| export default types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,108 @@ | ||
| import { types } from 'mobx-state-tree' | ||
| import { autorun } from 'mobx' | ||
|
|
||
| import moment from 'moment' | ||
| import { applyPatch } from 'mobx-state-tree' | ||
| import { LogState } from '../Log' | ||
|
|
||
| const model = { | ||
| logs: types.optional(types.array(LogState), []) | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| return { | ||
| clearLogs() { | ||
| self.logs.forEach((log) => self.logs.pop()) | ||
| }, | ||
|
|
||
| updateLogs(newLogs) { | ||
| newLogs.forEach((log) => { | ||
| log.messages.forEach((message) => { | ||
| if (self.logs.length > 35) { | ||
| self.logs.shift() | ||
| } | ||
|
|
||
| self.logs.push({ | ||
| type: log.type, | ||
| message: message | ||
| }) | ||
| }) | ||
| }) | ||
| }, | ||
|
|
||
| removeOldestLog() { | ||
| if (self.logsCount > 50) { | ||
| self.logs.shift() | ||
| } | ||
| }, | ||
|
|
||
| addLog(log) { | ||
| log.messages.forEach((message) => { | ||
| self.logs.push({ | ||
| type: log.type, | ||
| message: message | ||
| }) | ||
| }) | ||
| }, | ||
|
|
||
| addInfoLog(log) { | ||
| self.logs.push({ | ||
| type: 'INFO', | ||
| message: log | ||
| }) | ||
| }, | ||
|
|
||
| addErrorLog(error) { | ||
| self.logs.push({ | ||
| type: 'ERROR', | ||
| message: error | ||
| }) | ||
| }, | ||
|
|
||
| addExecutionLog() { | ||
| self.logs.push({ | ||
| type: 'INFO', | ||
| message: `[${moment().format('h:mm:ss')}] executing` | ||
| }) | ||
| }, | ||
|
|
||
| addSocketNotConnectedLog() { | ||
| self.logs.push({ | ||
| type: 'WARNING', | ||
| message: `[${moment().format('h:mm:ss')}] socket not connected` | ||
| }) | ||
| }, | ||
|
|
||
| addSocketDisconnectLog() { | ||
| self.logs.push({ | ||
| type: 'WARNING', | ||
| message: `[${moment().format('h:mm:ss')}] socket disconnected` | ||
| }) | ||
| }, | ||
|
|
||
| addCustomLog(type, message) { | ||
| self.logs.push({ | ||
| type: type, | ||
| message | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const views = (self) => ({ | ||
| get logCount() { | ||
| return self.logs.length | ||
| }, | ||
|
|
||
| get textValue() { | ||
| return self.logs.reduce((final, log) => { | ||
| final += `${log.value}\n` | ||
| return final | ||
| }, '') | ||
| } | ||
| }) | ||
|
|
||
| export const OutputState = types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,44 @@ | ||
| import { types, flow } from 'mobx-state-tree' | ||
|
|
||
| import { ModuleState } from '../Module' | ||
|
|
||
| const model = { | ||
| nickname: types.optional(types.string, ''), | ||
| name: types.optional(types.string, ''), | ||
| picture: types.optional(types.string, ''), | ||
| updated_at: types.optional(types.string, ''), | ||
| iss: types.optional(types.string, ''), | ||
| sub: types.optional(types.string, ''), | ||
| aud: types.optional(types.string, ''), | ||
| iat: types.optional(types.number, 0), | ||
| exp: types.optional(types.number, 0), | ||
| at_hash: types.optional(types.string, ''), | ||
| nonce: types.optional(types.string, ''), | ||
| userName: types.optional(types.string, ''), | ||
| uid: types.optional(types.string, ''), | ||
| _id: types.optional(types.string, ''), | ||
| modules: types.optional(types.array(ModuleState), []) | ||
| } | ||
|
|
||
| const actions = (self) => { | ||
| return { | ||
| setData(user) { | ||
| self.userName = user.userName | ||
| self.uid = String(user.uid) | ||
| self._id = String(user._id) | ||
|
|
||
| user.modules.forEach((module) => { | ||
| self.modules.push(ModuleState.create(module)) | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const views = (self) => { | ||
| return {} | ||
| } | ||
|
|
||
| export default types | ||
| .model(model) | ||
| .actions(actions) | ||
| .views(views) |
| @@ -0,0 +1,24 @@ | ||
| export const auth = (tree) => { | ||
| return { $auth: tree.state.auth } | ||
| } | ||
|
|
||
| export const user = (tree) => { | ||
| return { $user: tree.state.auth.user } | ||
| } | ||
|
|
||
| export const editor = (tree) => { | ||
| return { $editor: tree.state.editor } | ||
| } | ||
|
|
||
| export const output = (tree) => { | ||
| return { $output: tree.state.editor.output } | ||
| } | ||
|
|
||
| export const select = (selectors: []) => { | ||
| return (tree) => { | ||
| return selectors.reduce((final, selector) => { | ||
| final = { ...final, ...selector(tree) } | ||
| return final | ||
| }, {}) | ||
| } | ||
| } |
| @@ -0,0 +1,34 @@ | ||
| @import './variables.css'; | ||
| @import './themes.css'; | ||
|
|
||
| * { | ||
| box-sizing: border-box; | ||
| padding: 0; | ||
| margin: 0; | ||
| } | ||
|
|
||
| :global #mountPoint { | ||
| background: #f2f2f2; | ||
| width: 100vw; | ||
| height: 100vh; | ||
| max-width: 100vw; | ||
| max-height: 100vh; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| body { | ||
| width: 100vw; | ||
| height: 100vh; | ||
| max-width: 100vw; | ||
| max-height: 100vh; | ||
| display: flex; | ||
| flex-direction: column; | ||
| font-family: 'Raleway', 'Open Sans', sans-serif; | ||
| } | ||
|
|
||
| a { | ||
| text-decoration: none; | ||
| color: inherit; | ||
| } |
| @@ -0,0 +1,20 @@ | ||
| Size(width, height = width) { | ||
| width: width; | ||
| height: height; | ||
| } | ||
|
|
||
| Flex($dir, $justify, $align) { | ||
| display: flex; | ||
| flex-direction: $dir; | ||
| justify-content: $justify; | ||
| align-items: $align; | ||
| } | ||
|
|
||
| Font(which) { | ||
| if (which is button) { | ||
| font-family: 'Open Sans', sans-serif; | ||
| text-transform: uppercase; | ||
| letter-spacing: 2px; | ||
| font-size: 12px; | ||
| } | ||
| } |
| @@ -0,0 +1,252 @@ | ||
| @import '~codemirror/lib/codemirror.css'; | ||
| @import '~codemirror/theme/material.css'; | ||
|
|
||
| * { | ||
| box-sizing: border-box; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| html, | ||
| body, | ||
| #root { | ||
| } | ||
|
|
||
| #root { | ||
| font-feature-settings: 'liga' 1; | ||
| } | ||
|
|
||
| /* .CodeMirror-vscrollbar::-webkit-scrollbar { | ||
| background-color: rgba(0,0,0,0.15); | ||
| width: 100%; | ||
| } | ||
|
|
||
| */ | ||
|
|
||
| .CodeMirror-vscrollbar::-webkit-scrollbar { | ||
| background-color: rgba(0, 0, 0, 0.1); | ||
| } | ||
|
|
||
| .CodeMirror-vscrollbar::-webkit-scrollbar-thumb { | ||
| background-color: rgba(0, 0, 0, 0.2); | ||
| } | ||
|
|
||
| #cool-widget { | ||
| background: #e1def1; | ||
| position: absolute; | ||
| width: 100%; | ||
| color: var(--black); | ||
| height: 24px; | ||
| padding: 0 16px; | ||
| font-family: 'Open Sans'; | ||
| } | ||
|
|
||
| .CodeMirror { | ||
| /* background: radial-gradient(circle at 50% -70%, #57589e40, transparent 65%), */ | ||
| /* linear-gradient(200deg, #3b3c6b -10%, #292947); */ | ||
| /* background: linear-gradient(200deg, #3B3C6B -10%, #292947); */ | ||
| background-size: 100%; | ||
| padding: 0px 0; | ||
| /* background: #32335a; */ | ||
| background: #292d45; | ||
| /* font-family: 'Overpass Mono', monospace !important; */ | ||
| /* font-family: 'Inconsolata', monospace !important; */ | ||
| /* font-family: 'Source Code Pro', monospace !important; */ | ||
| /* font-family: 'Overpass Mono', monospace !important; */ | ||
| font-family: 'Fira Mono', monospace !important; | ||
| /* font-family: 'Roboto Mono', monospace !important; */ | ||
| font-size: 14px; | ||
| font-weight: 500; | ||
| letter-spacing: 0.75px; | ||
| line-height: 1.7; | ||
| text-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); | ||
| } | ||
|
|
||
| .CodeMirror-code { | ||
| /* padding: 8px 0 0 0; */ | ||
| } | ||
|
|
||
| .CodeMirror-code div pre.CodeMirror-line { | ||
| left: 2px; | ||
| position: relative; | ||
| } | ||
|
|
||
| .cm-matchhighlight { | ||
| background: #4f4c62; | ||
| } | ||
|
|
||
| .CodeMirror-gutters { | ||
| background: var(--bgColor); | ||
| border-right: none !important; | ||
| /* padding-right: 2px; */ | ||
| padding: 4px; | ||
| background: transparent; | ||
| } | ||
|
|
||
| .CodeMirror-code div pre.CodeMirror-line { | ||
| padding: 0 0 0 8px; | ||
| } | ||
|
|
||
| .CodeMirror-cursors { | ||
| color: #8e74ca; | ||
| } | ||
|
|
||
| .cm-s-shit .CodeMirror-selected { | ||
| background: #1d1f313b; | ||
| } | ||
|
|
||
| .cm-s-shit .CodeMirror-activeline-gutter { | ||
| background: #1d1f313b; | ||
| } | ||
|
|
||
| .CodeMirror-linenumbers { | ||
| display: flex; | ||
| justify-content: center; | ||
| text-align: center; | ||
| background: transparent; | ||
| } | ||
|
|
||
| .bytesize-CodeMirror { | ||
| height: 100%; | ||
| max-height: 100%; | ||
| /* overflow-y: scroll; */ | ||
| } | ||
| /* delimiter tokens */ | ||
| .cm-s-shit { | ||
| color: #ffffff; | ||
| height: 100%; | ||
| max-height: 100%; | ||
| } | ||
|
|
||
| /* let, const, if, else, this, new, function ... */ | ||
| .cm-s-shit .cm-keyword { | ||
| color: #b475ff; | ||
| /* font-style: oblique; */ | ||
|
|
||
| /* color: #465d83; */ | ||
| } | ||
|
|
||
| /* e.g. #id */ | ||
| .cm-s-shit .cm-atom { | ||
| color: #00f3f3; | ||
| } | ||
|
|
||
| /* e.g. 20 */ | ||
| .cm-s-shit .cm-number { | ||
| color: #f7f616; | ||
| } | ||
|
|
||
| .cm-s-shit .CodeMirror-cursor { | ||
| /* border-color: #49499c; */ | ||
| border-color: #02ebec; | ||
| } | ||
|
|
||
| .cm-s-shit .CodeMirror-activeline-background { | ||
| /* background: linear-gradient(45deg, rgba(32, 34, 54, 0), rgba(32, 34, 54, 0.25)); */ | ||
| /* margin: -1px; */ | ||
| /* box-shadow: 0 0 8px rgba(0,0,0,0.025); */ | ||
| background: #1d1f313b; | ||
| } | ||
|
|
||
| /* e.g. px, em */ | ||
| .cm-s-shit .cm-unit { | ||
| color: #ff0000; | ||
| } | ||
|
|
||
| .cm-s-shit .cm-type { | ||
| color: green; | ||
| } | ||
|
|
||
| /* identifiers */ | ||
| .cm-s-shit .cm-def { | ||
| color: #fff; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| /* e.g. $, i */ | ||
| .cm-s-shit .cm-variable { | ||
| color: #ff68c4; | ||
| } | ||
|
|
||
| /* e.g. for key in [this], Markdown li */ | ||
| .cm-s-shit .cm-variable-2 { | ||
| color: #ff9d14; | ||
| } | ||
|
|
||
| /* e.g. getElementbyId, CSS properties */ | ||
| .cm-s-shit .cm-property { | ||
| color: #709eff; | ||
| /* color: #ff506d; */ | ||
| } | ||
|
|
||
| /* e.g. CoffeeScript ->, CSS colons */ | ||
| .cm-s-shit .cm-operator { | ||
| color: #729cf3; | ||
| font-weight: 700; | ||
| /* text-shadow: 0px 1px 16px rgba(38, 247, 230, 0.25); */ | ||
| } | ||
|
|
||
| .ofh .cm-s-shit.CodeMirror { | ||
| overflow: hidden; | ||
| height: 100%; | ||
| } | ||
|
|
||
| /* e.g. // comments */ | ||
| .cm-s-shit .cm-comment { | ||
| color: #5b4e8b; | ||
| } | ||
|
|
||
| /* "strings" and 'strings' */ | ||
| .cm-s-shit .cm-string { | ||
| color: #76f0dd; | ||
| } | ||
|
|
||
| /* `template strings */ | ||
| .cm-s-shit .cm-string-2 { | ||
| color: #84d8ff; | ||
| } | ||
|
|
||
| /* e.g. @font-face */ | ||
| .cm-s-shit .cm-meta { | ||
| color: #79ffa5; | ||
| } | ||
|
|
||
| /* */ | ||
| .cm-s-shit .cm-header { | ||
| color: #49d320; | ||
| } | ||
|
|
||
| /* e.g. HTML elements */ | ||
| .cm-s-shit .cm-tag { | ||
| color: #60ee02; | ||
| } | ||
|
|
||
| /* e.g. class="" id="" */ | ||
| .cm-s-shit .cm-attribute { | ||
| color: #17eb04; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shit .cm-strong { | ||
| color: #345038; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shit .cm-em { | ||
| color: #2aee24; | ||
| } | ||
|
|
||
| /* e.g. .box */ | ||
| .cm-s-shit .cm-qualifier { | ||
| color: #51e624; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shit .cm-builtin { | ||
| color: #16f016; | ||
| } | ||
|
|
||
| .CodeMirror-linenumber { | ||
| color: #5c5a96; | ||
| opacity: 0.75; | ||
| } |
| @@ -0,0 +1,222 @@ | ||
| @import '~codemirror/lib/codemirror.css'; | ||
| @import '~codemirror/theme/material.css'; | ||
|
|
||
| * { | ||
| box-sizing: border-box; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| html, | ||
| body, | ||
| #root { | ||
| } | ||
|
|
||
| #root { | ||
| font-feature-settings: 'liga' 1; | ||
| } | ||
|
|
||
| /* TODO: Make specific to terminal */ | ||
| .CodeMirror-vscrollbar::-webkit-scrollbar { | ||
| background-color: rgba(0, 0, 0, 0.1); | ||
| } | ||
|
|
||
| .CodeMirror-vscrollbar::-webkit-scrollbar-thumb { | ||
| background-color: rgb(46, 40, 67); | ||
| } | ||
|
|
||
| #cool-widget { | ||
| background: #e1def1; | ||
| position: absolute; | ||
| width: 100%; | ||
| color: var(--black); | ||
| height: 24px; | ||
| padding: 0 16px; | ||
| font-family: 'Open Sans'; | ||
| } | ||
|
|
||
| .CodeMirror { | ||
| background: linear-gradient(215deg, #3b3c64, #1d1f31); | ||
| background-size: 100%; | ||
| padding: 0px 0; | ||
| font-family: 'Overpass Mono', monospace !important; | ||
| font-size: 14px; | ||
| font-weight: 300; | ||
| letter-spacing: 1px; | ||
| line-height: 1.75; | ||
| } | ||
|
|
||
| .CodeMirror-code { | ||
| /* padding: 8px 0 0 0; */ | ||
| } | ||
|
|
||
| .CodeMirror-code div pre.CodeMirror-line { | ||
| left: 2px; | ||
| position: relative; | ||
| } | ||
|
|
||
| .cm-matchhighlight { | ||
| background: #4f4c62; | ||
| } | ||
|
|
||
| .CodeMirror-gutters { | ||
| background: transparent; | ||
| border-right: none !important; | ||
| /* padding-right: 2px; */ | ||
| padding: 4px; | ||
| } | ||
|
|
||
| .CodeMirror-cursors { | ||
| color: #8e74ca; | ||
| } | ||
|
|
||
| .cm-s-shellShit .CodeMirror-selected { | ||
| background: #382b54 !important; | ||
| } | ||
|
|
||
| .CodeMirror-linenumbers { | ||
| display: flex; | ||
| justify-content: center; | ||
| text-align: center; | ||
| background: transparent; | ||
| } | ||
|
|
||
| .bytesize-CodeMirror-shell { | ||
| height: 100%; | ||
| max-height: 100%; | ||
| padding: 0 0 0 16px; | ||
| /* overflow-y: scroll; */ | ||
| } | ||
|
|
||
| /* delimiter tokens */ | ||
| .cm-s-shellShit { | ||
| color: #ffffff; | ||
| background: transparent; | ||
| height: 100%; | ||
| max-height: 100%; | ||
| /* margin: 4px 0; */ | ||
| padding: 8px 0; | ||
| } | ||
|
|
||
| /* let, const, if, else, this, new, function ... */ | ||
| .cm-s-shellShit .cm-keyword { | ||
| color: #5e5eb9; | ||
| font-style: oblique; | ||
|
|
||
| /* color: #465d83; */ | ||
| } | ||
|
|
||
| /* e.g. #id */ | ||
| .cm-s-shellShit .cm-atom { | ||
| color: #00f3f3; | ||
| } | ||
|
|
||
| /* e.g. 20 */ | ||
| .cm-s-shellShit .cm-number { | ||
| color: #fc41ae; | ||
| } | ||
|
|
||
| .cm-s-shellShit .CodeMirror-cursor { | ||
| border-color: transparent; | ||
| } | ||
|
|
||
| .cm-s-shellShit .CodeMirror-activeline-background { | ||
| background: #2f2845; | ||
| margin: -1px; | ||
| box-shadow: 0 0 12px rgba(0, 0, 0, 0.05); | ||
| } | ||
|
|
||
| /* e.g. px, em */ | ||
| .cm-s-shellShit .cm-unit { | ||
| color: #ff0000; | ||
| } | ||
|
|
||
| .cm-s-shellShit .cm-type { | ||
| color: green; | ||
| } | ||
|
|
||
| /* identifiers */ | ||
| .cm-s-shellShit .cm-def { | ||
| color: #cf6e9c; | ||
| } | ||
|
|
||
| /* e.g. $, i */ | ||
| .cm-s-shellShit .cm-variable { | ||
| color: #b173eb; | ||
| } | ||
|
|
||
| /* e.g. for key in [this], Markdown li */ | ||
| .cm-s-shellShit .cm-variable-2 { | ||
| color: #fd8738; | ||
| } | ||
|
|
||
| /* e.g. getElementbyId, CSS properties */ | ||
| .cm-s-shellShit .cm-property { | ||
| color: #ff506d; | ||
| } | ||
|
|
||
| /* e.g. CoffeeScript ->, CSS colons */ | ||
| .cm-s-shellShit .cm-operator { | ||
| color: #5e5eb9; | ||
| /* text-shadow: 0px 1px 16px rgba(38, 247, 230, 0.25); */ | ||
| } | ||
|
|
||
| /* e.g. // comments */ | ||
| .cm-s-shellShit .cm-comment { | ||
| color: #5b4e8b; | ||
| } | ||
|
|
||
| /* "strings" and 'strings' */ | ||
| .cm-s-shellShit .cm-string { | ||
| color: #55ece0; | ||
| } | ||
|
|
||
| /* `template strings */ | ||
| .cm-s-shellShit .cm-string-2 { | ||
| color: #84d8ff; | ||
| } | ||
|
|
||
| /* e.g. @font-face */ | ||
| .cm-s-shellShit .cm-meta { | ||
| color: #79ffa5; | ||
| } | ||
|
|
||
| /* */ | ||
| .cm-s-shellShit .cm-header { | ||
| color: #49d320; | ||
| } | ||
|
|
||
| /* e.g. HTML elements */ | ||
| .cm-s-shellShit .cm-tag { | ||
| color: #60ee02; | ||
| } | ||
|
|
||
| /* e.g. class="" id="" */ | ||
| .cm-s-shellShit .cm-attribute { | ||
| color: #17eb04; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shellShit .cm-strong { | ||
| color: #345038; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shellShit .cm-em { | ||
| color: #2aee24; | ||
| } | ||
|
|
||
| /* e.g. .box */ | ||
| .cm-s-shellShit .cm-qualifier { | ||
| color: #51e624; | ||
| } | ||
|
|
||
| /* not sure if used */ | ||
| .cm-s-shellShit .cm-builtin { | ||
| color: #16f016; | ||
| } | ||
|
|
||
| .CodeMirror-linenumber { | ||
| color: #978bb8; | ||
| opacity: 0.75; | ||
| } |
| @@ -0,0 +1,330 @@ | ||
| .mde-header { | ||
| padding: 10px; | ||
| } | ||
| .mde-header button:focus { | ||
| outline: 0; | ||
| } | ||
| .mde-header ul.mde-header-group { | ||
| display: inline-block; | ||
| margin: 0 1rem 0 0; | ||
| padding: 0; | ||
| list-style: none; | ||
| } | ||
| .mde-header ul.mde-header-group li.mde-header-item { | ||
| display: inline-block; | ||
| position: relative; | ||
| margin: 0 4px; | ||
| } | ||
| .mde-header ul.mde-header-group li.mde-header-item button { | ||
| text-align: left; | ||
| cursor: pointer; | ||
| height: 22px; | ||
| padding: 4px; | ||
| margin: 0; | ||
| border: none; | ||
| background: none; | ||
| color: #242729; | ||
| } | ||
|
|
||
| @keyframes tooltip-appear { | ||
| from { | ||
| opacity: 0; | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| } | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| button.tooltipped:hover::before { | ||
| animation-name: tooltip-appear; | ||
| animation-duration: 0.2s; | ||
| animation-delay: 0.5s; | ||
| animation-fill-mode: forwards; | ||
| opacity: 0; | ||
| position: absolute; | ||
| z-index: 1000001; | ||
| width: 0; | ||
| height: 0; | ||
| color: rgba(0, 0, 0, 0.8); | ||
| pointer-events: none; | ||
| content: ''; | ||
| border: 5px solid transparent; | ||
| top: -5px; | ||
| right: 50%; | ||
| bottom: auto; | ||
| margin-right: -5px; | ||
| border-top-color: rgba(0, 0, 0, 0.8); | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| button.tooltipped:hover::after { | ||
| animation-name: tooltip-appear; | ||
| animation-duration: 0.2s; | ||
| animation-delay: 0.5s; | ||
| animation-fill-mode: forwards; | ||
| font-size: 11px; | ||
| opacity: 0; | ||
| position: absolute; | ||
| z-index: 1000000; | ||
| padding: 5px 8px; | ||
| color: #fff; | ||
| pointer-events: none; | ||
| content: attr(aria-label); | ||
| background: rgba(0, 0, 0, 0.8); | ||
| border-radius: 3px; | ||
| right: 50%; | ||
| bottom: 100%; | ||
| transform: translateX(50%); | ||
| margin-bottom: 5px; | ||
| white-space: nowrap; | ||
| } | ||
| .mde-header ul.mde-header-group li.mde-header-item ul.react-mde-dropdown { | ||
| position: absolute; | ||
| left: 0; | ||
| top: 30px; | ||
| background-color: white; | ||
| border: 1px solid #c8ccd0; | ||
| padding: 5px; | ||
| z-index: 2; | ||
| transform: translateX(-9px); | ||
| } | ||
| .mde-header ul.mde-header-group li.mde-header-item ul.react-mde-dropdown li { | ||
| margin: 0; | ||
| white-space: nowrap; | ||
| list-style: none; | ||
| display: block; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button { | ||
| display: block; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button | ||
| p { | ||
| display: block; | ||
| margin: 0; | ||
| padding: 0; | ||
| font-weight: bold; | ||
| line-height: 1em; | ||
| background: none; | ||
| border: 0; | ||
| text-align: left; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button | ||
| p:hover { | ||
| color: #4078c0; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button | ||
| p.header-1 { | ||
| font-size: 20px; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button | ||
| p.header-2 { | ||
| font-size: 18px; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown | ||
| li | ||
| button | ||
| p.header-3 { | ||
| font-size: 14px; | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown::before { | ||
| position: absolute; | ||
| content: ''; | ||
| width: 0; | ||
| height: 0; | ||
| border: 8px solid transparent; | ||
| border-bottom-color: rgba(0, 0, 0, 0.15); | ||
| top: -16px; | ||
| left: 3px; | ||
| transform: translateX(50%); | ||
| } | ||
| .mde-header | ||
| ul.mde-header-group | ||
| li.mde-header-item | ||
| ul.react-mde-dropdown::after { | ||
| position: absolute; | ||
| content: ''; | ||
| width: 0; | ||
| height: 0; | ||
| border: 7px solid transparent; | ||
| border-bottom-color: white; | ||
| top: -14px; | ||
| left: 5px; | ||
| transform: translateX(50%); | ||
| } | ||
|
|
||
| .mde-text { | ||
| border-width: 1px 0 1px 0; | ||
| border-style: solid; | ||
| border-color: #c8ccd0; | ||
| } | ||
| .mde-text .public-DraftEditor-content { | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| min-height: 200px; | ||
| max-height: 200px; | ||
| padding: 10px; | ||
| overflow-y: scroll; | ||
| } | ||
|
|
||
| .mde-preview .mde-preview-content { | ||
| padding: 10px 10px 20px 10px; | ||
| } | ||
| .mde-preview .mde-preview-content p, | ||
| .mde-preview .mde-preview-content blockquote, | ||
| .mde-preview .mde-preview-content ul, | ||
| .mde-preview .mde-preview-content ol, | ||
| .mde-preview .mde-preview-content dl, | ||
| .mde-preview .mde-preview-content table, | ||
| .mde-preview .mde-preview-content pre { | ||
| margin-top: 0; | ||
| margin-bottom: 16px; | ||
| } | ||
| .mde-preview .mde-preview-content h1, | ||
| .mde-preview .mde-preview-content h2, | ||
| .mde-preview .mde-preview-content h3 { | ||
| margin-top: 24px; | ||
| margin-bottom: 16px; | ||
| font-weight: 600; | ||
| line-height: 1.25; | ||
| border-bottom: 1px solid #eee; | ||
| padding-bottom: 0.3em; | ||
| } | ||
| .mde-preview .mde-preview-content h1 { | ||
| font-size: 1.6em; | ||
| } | ||
| .mde-preview .mde-preview-content h2 { | ||
| font-size: 1.4em; | ||
| } | ||
| .mde-preview .mde-preview-content h3 { | ||
| font-size: 1.2em; | ||
| } | ||
| .mde-preview .mde-preview-content ul, | ||
| .mde-preview .mde-preview-content ol { | ||
| padding-left: 2em; | ||
| } | ||
| .mde-preview .mde-preview-content blockquote { | ||
| margin-left: 0; | ||
| padding: 0 1em; | ||
| color: #777; | ||
| border-left: 0.25em solid #ddd; | ||
| } | ||
| .mde-preview .mde-preview-content blockquote > :first-child { | ||
| margin-top: 0; | ||
| } | ||
| .mde-preview .mde-preview-content blockquote > :last-child { | ||
| margin-bottom: 0; | ||
| } | ||
| .mde-preview .mde-preview-content code { | ||
| padding: 0.2em 0 0.2em 0; | ||
| margin: 0; | ||
| font-size: 90%; | ||
| background-color: rgba(0, 0, 0, 0.04); | ||
| border-radius: 3px; | ||
| } | ||
| .mde-preview .mde-preview-content code::before, | ||
| .mde-preview .mde-preview-content code::after { | ||
| letter-spacing: -0.2em; | ||
| content: '\00a0'; | ||
| } | ||
| .mde-preview .mde-preview-content pre { | ||
| padding: 16px; | ||
| overflow: auto; | ||
| font-size: 85%; | ||
| line-height: 1.45; | ||
| background-color: #f7f7f7; | ||
| border-radius: 3px; | ||
| } | ||
| .mde-preview .mde-preview-content pre code { | ||
| display: inline; | ||
| padding: 0; | ||
| margin: 0; | ||
| overflow: visible; | ||
| line-height: inherit; | ||
| word-wrap: normal; | ||
| background-color: transparent; | ||
| border: 0; | ||
| } | ||
| .mde-preview .mde-preview-content pre code::before, | ||
| .mde-preview .mde-preview-content pre code::after { | ||
| content: none; | ||
| } | ||
| .mde-preview .mde-preview-content pre > code { | ||
| padding: 0; | ||
| margin: 0; | ||
| font-size: 100%; | ||
| word-break: normal; | ||
| white-space: pre; | ||
| background: transparent; | ||
| border: 0; | ||
| } | ||
| .mde-preview .mde-preview-content a { | ||
| color: #4078c0; | ||
| text-decoration: none; | ||
| } | ||
| .mde-preview .mde-preview-content a:hover { | ||
| text-decoration: underline; | ||
| } | ||
| .mde-preview .mde-preview-content > *:first-child { | ||
| margin-top: 0 !important; | ||
| } | ||
| .mde-preview .mde-preview-content > *:last-child { | ||
| margin-bottom: 0 !important; | ||
| } | ||
| .mde-preview .mde-preview-content::after { | ||
| display: table; | ||
| clear: both; | ||
| content: ''; | ||
| } | ||
| .mde-preview .mde-preview-content table { | ||
| display: block; | ||
| width: 100%; | ||
| border-spacing: 0; | ||
| border-collapse: collapse; | ||
| } | ||
| .mde-preview .mde-preview-content table thead th { | ||
| font-weight: bold; | ||
| } | ||
| .mde-preview .mde-preview-content table th, | ||
| .mde-preview .mde-preview-content table td { | ||
| padding: 6px 13px; | ||
| border: 1px solid #c8ccd0; | ||
| } | ||
|
|
||
| .react-mde { | ||
| border: 1px solid #c8ccd0; | ||
| border-radius: 2px; | ||
| } |
| @@ -0,0 +1,9 @@ | ||
| :global [data-dark-theme] { | ||
| background: #202231; | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| :global [data-white-theme] { | ||
| background: #ffffff; | ||
| color: #202231; | ||
| } |
| @@ -0,0 +1,18 @@ | ||
| :root { | ||
| /* --black: #18161b; */ | ||
| /* --black: #1a1a20; */ | ||
| /* --black: #180e22; */ | ||
| --black: #191726; | ||
| --otherBlack: #131116; | ||
| --gray: #1e1e1e; | ||
| --purple0: #612bd3; | ||
| } | ||
|
|
||
| :root { | ||
| /* --bgColor: #1d1922; */ | ||
| --bgColor: linear-gradient(-150deg, rgb(47, 41, 68), rgb(24, 27, 43)); | ||
| --bgColorReverse: linear-gradient(150deg, rgb(41, 44, 68), rgb(24, 27, 43)); | ||
| --darkBlue0: #181b2b; | ||
| --darkBlue1: #272c42; | ||
| --shadow0: 0px 0px 8px 0px rgba(24, 27, 43, 1); | ||
| } |
| @@ -0,0 +1,10 @@ | ||
| import * as React from 'react' | ||
| import { inject, observer } from 'mobx-react' | ||
|
|
||
| export class StateInjector extends React.Component { | ||
| render() { | ||
| const Injector = inject(this.props.selector)(observer(this.props.children)) | ||
| Injector.displayName = 'StateInjector' | ||
| return <Injector {...this.props} /> | ||
| } | ||
| } |
| @@ -0,0 +1,16 @@ | ||
| const headers = new Headers({ | ||
| 'Content-Type': 'application/json' | ||
| }) | ||
|
|
||
| export const prettier = async (code) => { | ||
| const response = await fetch('$SERVER_ADDRESS$$API_PATH$/prettier', { | ||
| headers, | ||
| method: 'POST', | ||
| body: JSON.stringify({ | ||
| config: null, | ||
| code: code | ||
| }) | ||
| }) | ||
|
|
||
| return await response.json() | ||
| } |
| @@ -0,0 +1,33 @@ | ||
| import React from 'react' | ||
| import { observable, action, computed } from 'mobx' | ||
| import { observer, inject } from 'mobx-react' | ||
|
|
||
| const withObservableData = (component) => { | ||
| component.hasOwnProperty('$data') && | ||
| (component.$data = observable.box(component.$data)) | ||
| return component | ||
| } | ||
|
|
||
| export const createComponent = (creator) => { | ||
| return inject('state')((props) => { | ||
| const component = new React.Component(props) | ||
|
|
||
| Object.defineProperty(component, 'reactiveData', { | ||
| enumerable: false, | ||
| value: (data) => { | ||
| component.data = observable(data) | ||
| return component.data | ||
| } | ||
| }) | ||
|
|
||
| Object.defineProperty(component, 'action', { | ||
| enumerable: false, | ||
| value: (func) => { | ||
| return action((...args) => func(...args)) | ||
| } | ||
| }) | ||
|
|
||
| component.render = creator(component) | ||
| return observer(component) | ||
| }) | ||
| } |
| @@ -0,0 +1,16 @@ | ||
| export const gorgeous = async (code) => { | ||
| const response = await fetch('https://gorgeous-jvkoayanir.now.sh/', { | ||
| headers: new Headers({ | ||
| 'Content-Type': 'application/json' | ||
| }), | ||
| method: 'POST', | ||
| body: JSON.stringify({ | ||
| config: null, | ||
| code | ||
| }) | ||
| }) | ||
|
|
||
| const json = await response.json() | ||
| // json.error && (throw new Error(json.error)) | ||
| return json.pretty | ||
| } |
| @@ -0,0 +1,3 @@ | ||
| export const lastInArray = (target: any) => { | ||
| return target[target.length - 1] | ||
| } |