├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── .nvmrc ├── .postcssrc ├── .sassrc ├── .vscode └── settings.json ├── Blockly.js ├── LICENCE ├── README.md ├── alias └── electron │ └── index.js ├── package.json ├── src ├── App.js ├── components │ ├── BlocklyComponent │ │ ├── index.js │ │ └── index.scss │ ├── NavigationBar │ │ ├── index.js │ │ └── logo.png │ ├── ReduxProvider │ │ └── index.js │ ├── Toasts │ │ ├── index.js │ │ └── index.module.scss │ ├── ToolBox │ │ ├── Block.js │ │ ├── Category.js │ │ ├── categories │ │ │ ├── CoreCategory.js │ │ │ └── DiscordBlocks │ │ │ │ ├── StarterCategory.js │ │ │ │ └── autogen.js │ │ └── index.js │ ├── TransportSign │ │ ├── index.js │ │ └── styles.module.scss │ ├── TransportSignBadge │ │ ├── index.js │ │ └── styles.module.scss │ └── TransportSignTitle │ │ ├── index.js │ │ └── styles.module.scss ├── index.html ├── index.js ├── modules │ ├── Blockly.js │ └── ConstructCSS.js ├── pages │ ├── edit │ │ └── index.js │ └── index │ │ ├── firefox_PRVJSwMtRm.png │ │ ├── index.js │ │ └── index.scss ├── redux │ ├── actions │ │ ├── document.js │ │ └── toasts.js │ ├── reducers │ │ ├── document.js │ │ ├── index.js │ │ └── toasts.js │ └── store.js └── scss │ ├── index.scss │ └── settings.scss ├── static ├── _redirects └── example1.dbl └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .github/ 3 | .vscode/ 4 | dist/ 5 | node_modules/ 6 | src/modules/blockly/ 7 | src/modules/Blockly.js 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended" 9 | ], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2018, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react" 23 | ], 24 | "rules": { 25 | 'react/prop-types': 0, 26 | 'semi': 'always' 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://paypal.me/7coil"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache/ 3 | dist/ 4 | 5 | # How to not deadname someone 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/components/ToolBox/categories/DiscordBlocks/discord.js"] 2 | path = src/components/ToolBox/categories/DiscordBlocks/discord.js 3 | url = https://github.com/discordjs/discord.js 4 | branch = docs 5 | [submodule "src/modules/blockly"] 6 | path = src/modules/blockly 7 | url = https://github.com/google/blockly 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 13 2 | -------------------------------------------------------------------------------- /.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "modules": true, 3 | "plugins": { 4 | "autoprefixer": { 5 | "grid": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.sassrc: -------------------------------------------------------------------------------- 1 | { 2 | "includePaths": [ 3 | "src/scss/" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.detectIndentation": false, 3 | "editor.tabSize": 2 4 | } -------------------------------------------------------------------------------- /Blockly.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path') 3 | 4 | const script = ` 5 | ${fs.readFileSync(path.join(__dirname, '/src/modules/blockly/blockly_compressed.js'), { 6 | encoding: 'utf-8' 7 | })} 8 | ${fs.readFileSync(path.join(__dirname, '/src/modules/blockly/blocks_compressed.js'), { 9 | encoding: 'utf-8' 10 | })} 11 | ${fs.readFileSync(path.join(__dirname, '/src/modules/blockly/msg/js/en-gb.js'), { 12 | encoding: 'utf-8' 13 | })} 14 | ${fs.readFileSync(path.join(__dirname, '/src/modules/blockly/javascript_compressed.js'), { 15 | encoding: 'utf-8' 16 | })} 17 | export default Blockly; 18 | ` 19 | 20 | fs.writeFileSync('./src/modules/Blockly.js', script) 21 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 7coil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DiscordBlocks v3.0 2 | A Google Blockly (like MIT Scratch) to Discord.js compiler 3 | 4 | ## What is the `DBL` file format? 5 | The DBL file format is simply a `.zip` archive containing a `blocks.xml` file, which defines the blocks that are stored in the workspace. 6 | In the future, additional files, such as images and metadata can be included. 7 | 8 | ## To Implement 9 | - [ ] Additional modules 10 | - [ ] Example bot 11 | 12 | ## Thanks 13 | - **google**/Blockly - https://github.com/google/blockly/blob/master/LICENSE 14 | - **hydrabolt**/discord.js - https://github.com/hydrabolt/discord.js/blob/master/LICENSE 15 | -------------------------------------------------------------------------------- /alias/electron/index.js: -------------------------------------------------------------------------------- 1 | class ElectronWindow { 2 | constructor() { 3 | this.maximised = true; 4 | } 5 | 6 | isMaximized() { 7 | return this.maximised; 8 | } 9 | 10 | unmaximize() { 11 | this.maximised = false; 12 | } 13 | 14 | maximize() { 15 | this.maximised = true; 16 | } 17 | 18 | minimize() {} 19 | isMaximizable() {} 20 | isFocused() { return true } 21 | addListener() {} 22 | removeListener() {} 23 | on() {} 24 | close() {} 25 | getBounds() { 26 | return { 27 | x: null, 28 | y: null 29 | } 30 | } 31 | setBounds() {} 32 | } 33 | const electronWindow = new ElectronWindow(); 34 | 35 | class Screen { 36 | getDisplayNearestPoint() { 37 | return { 38 | workArea: null 39 | } 40 | } 41 | } 42 | const electronScreen = new Screen(); 43 | 44 | class Remote { 45 | getCurrentWindow() { 46 | return electronWindow 47 | } 48 | } 49 | const electronRemote = new Remote(); 50 | 51 | module.exports = { 52 | window: electronWindow, 53 | remote: electronRemote, 54 | screen: electronScreen, 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-blocks", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/moustacheminer/discord-blocks", 6 | "author": "Leondro Lio ", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "parcel src/index.html", 10 | "build": "parcel build src/index.html -d dist/client --public-url /" 11 | }, 12 | "dependencies": { 13 | "@babel/polyfill": "^7.4.3", 14 | "autoprefixer": "^9.5.1", 15 | "blockly": "^1.0.0", 16 | "discord.js": "^12.1.1", 17 | "frameless-titlebar": "^1.0.8", 18 | "js-beautify": "^1.10.3", 19 | "jszip": "^3.2.2", 20 | "postcss-modules": "^1.4.1", 21 | "react": ">=15", 22 | "react-app-polyfill": "^1.0.0", 23 | "react-dom": "^16.8.6", 24 | "react-helmet": "6.0.0-beta", 25 | "react-redux": "^7.0.2", 26 | "react-router": "^5.1.2", 27 | "react-router-dom": "^5.1.2", 28 | "redux": "^4.0.1", 29 | "redux-thunk": "^2.3.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.9.0", 33 | "@babel/plugin-proposal-class-properties": "^7.8.3", 34 | "babel-plugin-transform-runtime": "^6.23.0", 35 | "babel-preset-env": "^1.7.0", 36 | "eslint": "^6.8.0", 37 | "eslint-plugin-react": "^7.19.0", 38 | "parcel": "^1.12.3", 39 | "parcel-plugin-static-files-copy": "^2.3.1", 40 | "sass": "^1.19.0" 41 | }, 42 | "alias": { 43 | "electron": "./alias/electron" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | import React, { Component } from 'react'; 3 | import { Route, Router } from 'react-router'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import ReduxProvider from './components/ReduxProvider'; 6 | import EditPage from './pages/edit'; 7 | import IndexPage from './pages/index'; 8 | import './scss/index.scss'; 9 | 10 | const history = createBrowserHistory() 11 | 12 | class App extends Component { 13 | render() { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /src/components/BlocklyComponent/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Blockly from '../../modules/Blockly'; 3 | 4 | import styles from './index.scss'; 5 | 6 | class BlocklyComponent extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | workspace: null, 11 | blocklyDiv: null, 12 | } 13 | this.resize = this.resize.bind(this); 14 | this.newPositionDiv = React.createRef(); 15 | } 16 | componentDidMount() { 17 | const blocklyDiv = document.createElement('div'); 18 | blocklyDiv.classList.add(styles.blockly) 19 | 20 | if (typeof window !== 'undefined') { 21 | window.document.body.appendChild(blocklyDiv); 22 | } 23 | 24 | const workspace = Blockly.inject(blocklyDiv, { 25 | toolbox: this.props.toolbox.current 26 | }) 27 | 28 | this.setState({ 29 | workspace, 30 | blocklyDiv 31 | }) 32 | 33 | if (typeof window !== 'undefined') { 34 | window.addEventListener('resize', this.resize); 35 | this.resize(); 36 | 37 | window.workspace = workspace; 38 | window.toolbox = this.props.toolbox.current; 39 | } 40 | 41 | if (typeof this.props.onCreated === 'function') { 42 | this.props.onCreated({ 43 | workspace 44 | }) 45 | } 46 | } 47 | componentWillUnmount() { 48 | if (this.state.workspace) { 49 | this.state.workspace.dispose(); 50 | } 51 | if (typeof window !== 'undefined') { 52 | window.removeEventListener('resize', this.resize); 53 | window.document.body.removeChild(this.state.blocklyDiv) 54 | } 55 | } 56 | resize() { 57 | // Overlay the Blockly component 58 | if (this.state.blocklyDiv) { 59 | this.state.blocklyDiv.style.top = `${this.newPositionDiv.current.offsetTop}px`; 60 | this.state.blocklyDiv.style.left = `${this.newPositionDiv.current.offsetLeft}px`; 61 | this.state.blocklyDiv.style.width = `${this.newPositionDiv.current.clientWidth}px`; 62 | this.state.blocklyDiv.style.height = `${this.newPositionDiv.current.clientHeight}px`; 63 | } else { 64 | setTimeout(() => { 65 | this.resize(); 66 | }, 10) 67 | } 68 | 69 | if (this.state.workspace) { 70 | Blockly.svgResize(this.state.workspace); 71 | } 72 | } 73 | render() { 74 | return ( 75 |
76 | ) 77 | } 78 | } 79 | 80 | 81 | export default BlocklyComponent; 82 | -------------------------------------------------------------------------------- /src/components/BlocklyComponent/index.scss: -------------------------------------------------------------------------------- 1 | .blockly { 2 | z-index: 0; 3 | position: absolute; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/NavigationBar/index.js: -------------------------------------------------------------------------------- 1 | import TitleBar from 'frameless-titlebar'; 2 | import JSZip from 'jszip'; 3 | import React, { Component } from 'react'; 4 | import Blockly from '../../modules/Blockly'; 5 | import logo from './logo.png'; 6 | import { modifyModules } from '../../redux/actions/document'; 7 | import { connect } from 'react-redux'; 8 | 9 | class NavigationBar extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.save = this.save.bind(this); 13 | this.loadWithJSZip = this.loadWithJSZip.bind(this); 14 | this.loadFromFile = this.loadFromFile.bind(this); 15 | this.loadFromText = this.loadFromText.bind(this); 16 | this.export = this.export.bind(this); 17 | this.loadButton = React.createRef(); 18 | this.temporarySaveFile = React.createRef(); 19 | 20 | this.state = { 21 | temporarySaveLink: '', 22 | temporarySaveFilename: '' 23 | } 24 | } 25 | save() { 26 | const { workspace } = this.props 27 | const xmlContent = Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(workspace)); 28 | const fileName = `Untitled.dbl`; 29 | 30 | const zip = new JSZip(); 31 | 32 | zip.file('blocks.xml', xmlContent); 33 | 34 | zip.generateAsync({ 35 | type: 'blob' 36 | }) 37 | .then((blob) => { 38 | const a = document.createElement('a'); 39 | a.style = 'display: none'; 40 | document.body.appendChild(a); 41 | const url = window.URL.createObjectURL(blob); 42 | a.href = url; 43 | a.download = fileName; 44 | a.click(); 45 | window.URL.revokeObjectURL(url); 46 | document.body.removeChild(a); 47 | }) 48 | } 49 | loadWithJSZip(data) { 50 | // If there is a blocks.xml file, open it 51 | if (data.file('blocks.xml')) { 52 | data.file('blocks.xml').async('string') 53 | .then((text) => { 54 | this.loadFromText({ text }) 55 | }) 56 | } 57 | } 58 | loadFromFile(event) { 59 | const [file] = event.target.files; 60 | const reader = new FileReader(); 61 | reader.onload = (e) => { 62 | // Open the zip file 63 | JSZip.loadAsync(e.target.result) 64 | .then(this.loadWithJSZip) 65 | }; 66 | if (file) { 67 | reader.readAsArrayBuffer(file); 68 | } 69 | } 70 | loadFromUrl(url) { 71 | fetch(url) 72 | .then(res => res.blob()) 73 | .then(res => { 74 | JSZip.loadAsync(res) 75 | .then(this.loadWithJSZip) 76 | }) 77 | } 78 | loadFromText({ text }) { 79 | const { workspace } = this.props 80 | const xml = Blockly.Xml.textToDom(text); 81 | Blockly.Xml.domToWorkspace(xml, workspace); 82 | } 83 | export() { 84 | const { workspace } = this.props 85 | const code = Blockly.JavaScript.workspaceToCode(workspace); 86 | 87 | const blob = new Blob([code], { type: 'text/javascript' }); 88 | const url = window.URL.createObjectURL(blob); 89 | 90 | this.setState({ 91 | temporarySaveLink: url, 92 | temporarySaveFilename: 'untitled.js' 93 | }, () => { 94 | this.temporarySaveFile.current.click(); 95 | window.URL.revokeObjectURL(url); 96 | }) 97 | } 98 | render() { 99 | const workspace = this.props.workspace 100 | const { dispatch } = this.props; 101 | return ( 102 | <> 103 | 104 | 105 | this.loadButton.current.click() }, 110 | { label: 'Save', click: () => this.save() }, 111 | { label: 'Export', click: () => this.export() }, 112 | ] 113 | }, 114 | { 115 | label: 'Edit', 116 | submenu: [ 117 | { label: 'Undo', click: () => workspace.undo(false) }, 118 | { label: 'Redo', click: () => workspace.undo(true) }, 119 | { label: 'Clean up Workspace', click: () => workspace.cleanUp() }, 120 | ] 121 | }, 122 | { 123 | label: 'Help', 124 | submenu: [ 125 | { label: 'Discord.js Documentation', click: () => window.open('https://discord.js.org/') }, 126 | { 127 | label: 'Examples', submenu: [ 128 | { label: 'Broken Example', click: () => this.loadFromUrl('/example1.dbl') }, 129 | ] 130 | }, 131 | { type: 'separator' }, 132 | { label: 'DiscordBlocks on GitHub', click: () => window.open('https://github.com/7coil/discord-blocks') }, 133 | ] 134 | }, 135 | ]} /> 136 | 137 | ) 138 | } 139 | } 140 | 141 | const mapStateToProps = (state) => { 142 | const { document } = state; 143 | return { document }; 144 | } 145 | 146 | export default connect(mapStateToProps)(NavigationBar); 147 | 148 | -------------------------------------------------------------------------------- /src/components/NavigationBar/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7coil/discord-blocks/37d6a1da643bced96dfb6fe89dee7612a134b620/src/components/NavigationBar/logo.png -------------------------------------------------------------------------------- /src/components/ReduxProvider/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import configureStore from '../../redux/store'; 4 | 5 | class ReduxProvider extends Component { 6 | render() { 7 | return ( 8 | 9 | {this.props.children} 10 | 11 | ) 12 | } 13 | } 14 | 15 | export default ReduxProvider; 16 | -------------------------------------------------------------------------------- /src/components/Toasts/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import styles from './index.module.scss'; 4 | 5 | class Toasts extends Component { 6 | render() { 7 | const { toasts } = this.props.toasts; 8 | return ( 9 |
10 | {toasts.map(toast => ( 11 |
12 | {toast.content} 18 |
19 | ))} 20 |
21 | ); 22 | } 23 | } 24 | 25 | 26 | const mapStateToProps = (state) => { 27 | const { toasts } = state; 28 | return { toasts }; 29 | } 30 | 31 | export default connect(mapStateToProps)(Toasts); 32 | -------------------------------------------------------------------------------- /src/components/Toasts/index.module.scss: -------------------------------------------------------------------------------- 1 | .toastContainer { 2 | position: fixed; 3 | text-align: right; 4 | right: 5px; 5 | top: 40px; 6 | z-index: 2000; 7 | .toastDiv { 8 | margin-bottom: 20px; 9 | } 10 | .toast { 11 | padding: 5px; 12 | color: white; 13 | // font-family: 'Comic Sans MS', 'Comic Sans', serif; 14 | font-family: "Segoe WPC", "Segoe UI", sans-serif; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/ToolBox/Block.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Blockly from '../../modules/Blockly'; 3 | 4 | const Block = (props) => { 5 | const id = props.name.replace(/[ .]/g, '-').toLowerCase() 6 | 7 | const func = props.func; 8 | // If the block is truthy 9 | if (func) { 10 | // If hidden, don't add to the inventory. 11 | if (func.hidden === true || props.hidden === true) return null; 12 | 13 | // Add the block to the list of blocks in the category 14 | return ( 15 | 16 | ) 17 | } 18 | 19 | return null; 20 | } 21 | 22 | export { 23 | Block 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ToolBox/Category.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Block } from './Block'; 3 | 4 | const Category = (props) => props.hidden === true ? null : ( 5 | 6 | {props.blocks ? Object.entries(props.blocks).map(([name, func]) => { 7 | let id = name.replace(/[ .]/g, '-').toLowerCase(); 8 | 9 | return ( 10 | 15 | ) 16 | 17 | export default Category; 18 | -------------------------------------------------------------------------------- /src/components/ToolBox/categories/CoreCategory.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Block } from '../Block'; 3 | import Blockly from '../../../modules/Blockly' 4 | 5 | const CoreCategory = () => ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 10 31 | 32 | 33 | 34 | 35 | 36 | i 37 | 38 | 39 | 1 40 | 41 | 42 | 43 | 44 | 10 45 | 46 | 47 | 48 | 49 | 1 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 1 70 | 71 | 72 | 73 | 74 | 100 75 | 76 | 77 | 78 | 79 | 80 | 81 | 1 82 | 83 | 84 | 85 | 86 | 100 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 5 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {listVariable} 110 | 111 | 112 | 113 | 114 | 115 | 116 | {listVariable} 117 | 118 | 119 | 120 | 121 | 122 | 123 | {listVariable} 124 | 125 | 126 | 127 | 128 | 129 | 130 | {listVariable} 131 | 132 | 133 | 134 | 135 | 136 | 137 | , 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | abc 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | {textVariable} 169 | 170 | 171 | 172 | 173 | abc 174 | 175 | 176 | 177 | 178 | 179 | 180 | {textVariable} 181 | 182 | 183 | 184 | 185 | 186 | 187 | {textVariable} 188 | 189 | 190 | 191 | 192 | 193 | 194 | abc 195 | 196 | 197 | 198 | 199 | 200 | 201 | abc 202 | 203 | 204 | 205 | {/* 206 | 207 | 208 | abc 209 | 210 | 211 | */} 212 | 213 | 214 | 215 | abc 216 | 217 | 218 | 219 | 220 | 221 | 222 | abc 223 | 224 | 225 | 226 | 253 | 254 | 255 | ) 256 | 257 | export default CoreCategory; 258 | -------------------------------------------------------------------------------- /src/components/ToolBox/categories/DiscordBlocks/StarterCategory.js: -------------------------------------------------------------------------------- 1 | import Blockly from '../../../../modules/Blockly'; 2 | 3 | const StarterCategory = { 4 | name: 'Starter', 5 | blocks: { 6 | 'com_moustacheminer_discordblocks_init': { 7 | block: { 8 | init() { 9 | this.appendDummyInput() 10 | .appendField("import Discord.js"); 11 | this.setColour(230); 12 | this.setTooltip(""); 13 | this.setHelpUrl(""); 14 | } 15 | }, 16 | generator() { 17 | return '# TODO: Init Script' 18 | } 19 | }, 20 | 'com_moustacheminer_discordblocks_client-login': { 21 | block: { 22 | init() { 23 | this.appendValueInput("token") 24 | .setCheck("String") 25 | .appendField("connect with token"); 26 | this.setColour(230); 27 | this.setTooltip(""); 28 | this.setHelpUrl(""); 29 | } 30 | }, 31 | generator(block) { 32 | var value_name = Blockly.JavaScript.valueToCode(block, 'token', Blockly.JavaScript.ORDER_ATOMIC); 33 | var code = `discordblocks.client.login(${value_name});\n`; 34 | return code; 35 | } 36 | }, 37 | 'com_moustacheminer_discordblocks_on-message': { 38 | block: { 39 | init() { 40 | this.appendDummyInput() 41 | .appendField("on message"); 42 | this.appendStatementInput("statements") 43 | .setCheck(null); 44 | this.setColour(230); 45 | this.setTooltip(""); 46 | this.setHelpUrl(""); 47 | } 48 | }, 49 | generator(block) { 50 | var statements = Blockly.JavaScript.statementToCode(block, 'statements'); 51 | // TODO: Assemble JavaScript into code variable. 52 | var code = `discordblocks.client.on('message', (param1) => {\ndiscordblocks.message = param1;\n${statements}discordblocks.message = null\n});\n`; 53 | return code; 54 | } 55 | }, 56 | 'com_moustacheminer_discordblocks_message-reply-then-but': { 57 | block: { 58 | init() { 59 | this.appendValueInput('input') 60 | .setCheck("String") 61 | .appendField("reply to message with the text"); 62 | this.appendStatementInput("then") 63 | .setCheck(null) 64 | .appendField("afterwards"); 65 | this.appendStatementInput("but") 66 | .setCheck(null) 67 | .appendField("but on error"); 68 | this.setPreviousStatement(true, null); 69 | this.setNextStatement(true, null); 70 | this.setColour(230); 71 | this.setTooltip(""); 72 | this.setHelpUrl(""); 73 | } 74 | }, 75 | generator(block) { 76 | var value = Blockly.JavaScript.valueToCode(block, 'input', Blockly.JavaScript.ORDER_ATOMIC); 77 | var statements_then = Blockly.JavaScript.statementToCode(block, 'then'); 78 | var statements_but = Blockly.JavaScript.statementToCode(block, 'but'); 79 | // TODO: Assemble JavaScript into code variable. 80 | var code = `discordblocks.checkMessageExists()\ndiscordblocks.message.reply(${value})\n.then(() => {\n${statements_then}\n})\n.catch(() => {\n${statements_but}\n})\n`; 81 | return code; 82 | } 83 | }, 84 | 'com_moustacheminer_discordblocks_message-contents': { 85 | block: { 86 | init: function () { 87 | this.appendDummyInput() 88 | .appendField("message contents"); 89 | this.setOutput(true, null); 90 | this.setColour(230); 91 | this.setTooltip(""); 92 | this.setHelpUrl(""); 93 | } 94 | }, 95 | generator() { 96 | return ['discordblocks.message.contents', Blockly.JavaScript.ORDER_NONE] 97 | } 98 | } 99 | } 100 | }; 101 | 102 | export default StarterCategory; 103 | -------------------------------------------------------------------------------- /src/components/ToolBox/categories/DiscordBlocks/autogen.js: -------------------------------------------------------------------------------- 1 | import docs from './discord.js/12.1.1.json'; 2 | import Blockly from '../../../../modules/Blockly'; 3 | 4 | const categoryDefinitions = []; 5 | 6 | const colours = { 7 | class: 160, 8 | props: 230, 9 | method: 40, 10 | events: 100 11 | }; 12 | 13 | docs.classes.forEach((discordjsClass) => { 14 | const categoryDefinition = { 15 | name: discordjsClass.name, 16 | blocks: {} 17 | } 18 | 19 | if (discordjsClass.access !== 'private' || discordjsClass.name === 'MessageEmbed') { 20 | // If the class is a constructor, add a block for constructing 21 | if (discordjsClass.construct) { 22 | let numberOfParams = discordjsClass.construct.params.length; 23 | 24 | // Add blocks with an increasing number of parameters 25 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor`] = { 26 | block: { 27 | init() { 28 | this.appendDummyInput() 29 | .appendField(`create a new ${discordjsClass.name.toLowerCase()}`) 30 | this.setOutput(true, null) 31 | this.setColour(colours.class) 32 | this.setTooltip(discordjsClass.description) 33 | this.setHelpUrl(`https://discord.js.org/#/docs/main/stable/class/${discordjsClass.construct.name}`); 34 | this._enabledParams = [] 35 | this._updateShape() 36 | this.setMutator(new Blockly.Mutator(discordjsClass.construct.params.map((val, index) => `com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor-mutator-${index}`))) 37 | }, 38 | mutationToDom() { 39 | const mutation = document.createElement('mutation'); 40 | mutation.setAttribute('enabled-params', JSON.stringify(this._enabledParams)) 41 | return mutation; 42 | }, 43 | domToMutation(xml) { 44 | this._enabledParams = JSON.parse(xml.getAttribute('enabled-params')) 45 | this._updateShape() 46 | }, 47 | decompose(workspace) { 48 | const containerBlock = workspace.newBlock(`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor-mutator-container`); 49 | containerBlock.initSvg(); 50 | let connection = containerBlock.getInput('STACK').connection; 51 | 52 | for (let enabledParam in this._enabledParams) { 53 | const itemBlock = workspace.newBlock(`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor-mutator-${enabledParam}`); 54 | itemBlock.initSvg(); 55 | connection.connect(itemBlock.previousConnection); 56 | connection = itemBlock.nextConnection; 57 | } 58 | return containerBlock 59 | }, 60 | compose(containerBlock) { 61 | let itemBlock = containerBlock.getInputTargetBlock('STACK'); 62 | this._enabledParams = []; 63 | 64 | while (itemBlock) { 65 | let numberString = /(\d+)$/.exec(itemBlock.type); 66 | if (numberString) { 67 | let number = parseInt(numberString[0], 10); 68 | if (!this._enabledParams.includes(number)) { 69 | this._enabledParams.push(number) 70 | } 71 | } 72 | itemBlock = itemBlock.nextConnection && itemBlock.nextConnection.targetBlock(); 73 | } 74 | 75 | this._updateShape(); 76 | }, 77 | _updateShape() { 78 | for (let i = 0; i < numberOfParams; i += 1) { 79 | const currentInput = this.getInput(`${i}`) 80 | 81 | if (!currentInput && this._enabledParams.includes(i)) { 82 | this.appendValueInput(`${i}`) 83 | .setCheck(null) 84 | .appendField(`${discordjsClass.props[i].name}`); 85 | } 86 | if (currentInput && !this._enabledParams.includes(i)) { 87 | this.removeInput(`${i}`) 88 | } 89 | } 90 | } 91 | }, 92 | generator: function (block) { 93 | const enabledParams = block._enabledParams; 94 | let paramArray = []; 95 | 96 | for (let i = 0; i < numberOfParams; i += 1) { 97 | if (enabledParams.includes(i)) { 98 | paramArray.push(Blockly.JavaScript.valueToCode(block, `${i}`, Blockly.JavaScript.ORDER_ATOMIC) || 'undefined'); 99 | } else { 100 | paramArray.push('undefined') 101 | } 102 | } 103 | 104 | return [ 105 | `new Discord.${discordjsClass.construct.name}(${paramArray.join(',')})`, 106 | Blockly.JavaScript.ORDER_NONE 107 | ]; 108 | } 109 | }; 110 | 111 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor-mutator-container`] = { 112 | hidden: true, 113 | block: { 114 | init() { 115 | this.setStyle('list_blocks'); 116 | this.appendDummyInput() 117 | .appendField(Blockly.Msg['LISTS_CREATE_WITH_CONTAINER_TITLE_ADD']); 118 | this.appendStatementInput('STACK'); 119 | this.setTooltip(Blockly.Msg['LISTS_CREATE_WITH_CONTAINER_TOOLTIP']); 120 | this.contextMenu = false; 121 | } 122 | } 123 | } 124 | 125 | for (let paramNumber = 0; paramNumber < numberOfParams; paramNumber += 1) { 126 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-constructor-mutator-${paramNumber}`] = { 127 | hidden: true, 128 | block: { 129 | init() { 130 | this.setStyle('list_blocks'); 131 | this.appendDummyInput() 132 | .appendField(discordjsClass.props[paramNumber].name); 133 | this.setPreviousStatement(true); 134 | this.setNextStatement(true); 135 | this.setTooltip(Blockly.Msg['LISTS_CREATE_WITH_ITEM_TOOLTIP']); 136 | this.contextMenu = false; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | if (discordjsClass.props) { 144 | // For each property of the class... 145 | discordjsClass.props 146 | .filter(method => method.access !== 'private') 147 | .forEach((prop) => { 148 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-prop-${prop.name}`] = { 149 | block: { 150 | init: function () { 151 | this.appendValueInput(prop.name) 152 | .setCheck(null) 153 | .appendField(`get ${prop.name}`); 154 | this.setOutput(true, null); 155 | this.setColour(colours.props); 156 | this.setTooltip(prop.description); 157 | this.setHelpUrl(`https://discord.js.org/#/docs/main/stable/class/${discordjsClass.name.toLowerCase()}?scrollTo=${prop.name}`); 158 | } 159 | }, 160 | generator: function () { 161 | return `.${prop.name}`; 162 | } 163 | } 164 | }); 165 | } 166 | 167 | if (discordjsClass.methods) { 168 | discordjsClass.methods 169 | .filter(method => method.access !== 'private') 170 | .forEach((method) => { 171 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-method-${method.name}`] = { 172 | block: { 173 | init: function () { 174 | this.appendValueInput(method.name) 175 | .setCheck(method.description) 176 | .appendField(`${method.name}`); 177 | this.setOutput(true, null); 178 | this.setColour(colours.method); 179 | this.setTooltip(''); 180 | this.setHelpUrl(`https://discord.js.org/#/docs/main/stable/class/${discordjsClass.name.toLowerCase()}?scrollTo=${method.name}`); 181 | } 182 | }, 183 | generator: function () { 184 | return `.${method.name}()`; 185 | } 186 | } 187 | 188 | // If there are any params, add them as well 189 | if (method.params) { 190 | for (let numberOfParams = 1; numberOfParams < method.params.length; numberOfParams += 1) { 191 | // If any of the potential parameters has a "." in it, skip it entirely 192 | for (let paramNumber = 0; paramNumber < numberOfParams; paramNumber += 1) { 193 | if (method.params[paramNumber].name.includes('.')) continue; 194 | } 195 | 196 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-method-${method.name}-with-${numberOfParams}-params`] = { 197 | block: { 198 | init: function () { 199 | this.appendValueInput(method.name) 200 | .setCheck(null) 201 | .appendField(`${method.name}`); 202 | this.setOutput(true, null); 203 | this.setColour(colours.method); 204 | this.setTooltip(method.description); 205 | this.setHelpUrl(`https://discord.js.org/#/docs/main/stable/class/${discordjsClass.name}?scrollTo=${method.name}`); 206 | for (let paramNumber = 0; paramNumber < numberOfParams; paramNumber += 1) { 207 | this.appendValueInput(method.params[paramNumber].name) 208 | .setCheck(null) 209 | .appendField(`${paramNumber > 0 ? 'and ' : ''}with ${method.params[paramNumber].name}`); 210 | } 211 | } 212 | }, 213 | generator: function () { 214 | return `.${method.name}()`; 215 | } 216 | } 217 | } 218 | } 219 | }); 220 | } 221 | 222 | if (discordjsClass.events) { 223 | // For each property of the class... 224 | discordjsClass.events 225 | .filter(method => method.access !== 'private') 226 | .forEach((event) => { 227 | categoryDefinition.blocks[`com_moustacheminer_discordjs_${discordjsClass.name.toLowerCase()}-event-${event.name}`] = { 228 | block: { 229 | init: function () { 230 | this.appendValueInput("thing") 231 | .setCheck(null) 232 | .appendField("when"); 233 | this.appendDummyInput() 234 | .appendField(`emits '${event.name}'`); 235 | this.appendStatementInput('statement') 236 | .setCheck(null); 237 | this.setPreviousStatement(true, null); 238 | this.setNextStatement(true, null); 239 | this.setColour(colours.props); 240 | this.setTooltip(event.description); 241 | this.setHelpUrl(`https://discord.js.org/#/docs/main/stable/class/${discordjsClass.name}?scrollTo=e-${event.name}`); 242 | } 243 | }, 244 | generator: function (block) { 245 | const thing = Blockly.JavaScript.valueToCode(block, 'thing', Blockly.JavaScript.ORDER_ATOMIC); 246 | const statement = Blockly.JavaScript.statementToCode(block, 'statement'); 247 | return `${thing}.on('${event.name}', () => {\n${statement}\n})\n`; 248 | } 249 | } 250 | }); 251 | } 252 | } 253 | 254 | // Add the category if there are blocks defined for the category 255 | if (Object.keys(categoryDefinition.blocks).length > 0) { 256 | categoryDefinitions.push(categoryDefinition); 257 | } 258 | }); 259 | 260 | export default categoryDefinitions; 261 | -------------------------------------------------------------------------------- /src/components/ToolBox/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Category from './Category'; 3 | import CoreCategory from './categories/CoreCategory'; 4 | import categoryDefinitions from './categories/DiscordBlocks/autogen'; 5 | import { connect } from 'react-redux'; 6 | import Blockly from '../../modules/Blockly'; 7 | // import StarterCategory from './categories/DiscordBlocks/StarterCategory'; 8 | 9 | if (typeof Blockly.BlockSvg !== 'undefined') { 10 | Blockly.BlockSvg.START_HAT = true; 11 | } 12 | 13 | if (typeof Blockly.JavaScript !== 'undefined') { 14 | Blockly.JavaScript.addReservedWords('discordblocks'); 15 | Blockly.JavaScript.addReservedWords('param1'); 16 | } 17 | 18 | categoryDefinitions.map(category => Object.entries(category.blocks).map(([name, func]) => { 19 | let id = name.replace(/[ .]/g, '-').toLowerCase(); 20 | 21 | if (func) { 22 | // If it has a block generator, add the block 23 | if (func.block && typeof Blockly.Blocks !== 'undefined') { 24 | Blockly.Blocks[id] = func.block; 25 | } 26 | 27 | // If it has a javascript generator, add the generator 28 | if (func.generator && typeof Blockly.JavaScript !== 'undefined') { 29 | Blockly.JavaScript[id] = func.generator; 30 | } 31 | } 32 | })) 33 | 34 | const Tools = (props, ref) => { 35 | const enabledModules = props.document.modules; 36 | return ( 37 | 38 | 39 | 40 | 41 | {/* */} 42 | 43 | 47 | ) 48 | } 49 | 50 | const ToolBox = React.forwardRef(Tools); 51 | 52 | const mapStateToProps = (state) => { 53 | const { document } = state; 54 | return { document }; 55 | } 56 | 57 | export default connect(mapStateToProps, null, null, { 58 | forwardRef: true 59 | })(ToolBox); 60 | -------------------------------------------------------------------------------- /src/components/TransportSign/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './styles.module.scss' 3 | 4 | const TransportSign = ({ children }) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ) 10 | } 11 | 12 | export default TransportSign 13 | -------------------------------------------------------------------------------- /src/components/TransportSign/styles.module.scss: -------------------------------------------------------------------------------- 1 | @use 'settings'; 2 | 3 | .links { 4 | position: relative; 5 | display: inline-grid; 6 | @include settings.motorway; 7 | flex-direction: column; 8 | padding: 1em; 9 | border-radius: 0.7em; 10 | gap: 0.5em; 11 | grid-gap: 0.5em; 12 | font-family: settings.$main-font; 13 | 14 | box-shadow: 15 | inset 0 0 0 0.1em rgb(0, 121, 193), 16 | inset 0 0 0 0.3em white; 17 | 18 | a { 19 | // text-decoration: none; 20 | color: white; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/TransportSignBadge/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './styles.module.scss' 3 | 4 | const mapping = Object.assign(styles, { 5 | a: styles.green, 6 | m: styles.blue, 7 | b: styles.white, 8 | attraction: styles.brown 9 | }) 10 | 11 | const TransportSignBadge = ({ children, type }) => { 12 | return ( 13 | 14 | {children} 15 | 16 | ) 17 | } 18 | 19 | export default TransportSignBadge 20 | -------------------------------------------------------------------------------- /src/components/TransportSignBadge/styles.module.scss: -------------------------------------------------------------------------------- 1 | @use 'settings'; 2 | 3 | .white, .brown, .green { 4 | padding: 0.2em; 5 | border-radius: 0.2em; 6 | } 7 | 8 | .green { 9 | @include settings.primary-road 10 | } 11 | 12 | .brown { 13 | @include settings.attraction; 14 | } 15 | 16 | .white { 17 | @include settings.non-primary-road; 18 | } 19 | 20 | .blue { 21 | @include settings.motorway; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/TransportSignTitle/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './styles.module.scss' 3 | 4 | const TransportSignTitle = ({ children }) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ) 10 | } 11 | 12 | export default TransportSignTitle 13 | -------------------------------------------------------------------------------- /src/components/TransportSignTitle/styles.module.scss: -------------------------------------------------------------------------------- 1 | @use 'settings'; 2 | 3 | .header { 4 | text-align: center; 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | margin: 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | const element = document.getElementById('app') 6 | 7 | const app = ( 8 | 9 | ) 10 | 11 | ReactDOM.render(app, element) 12 | 13 | // Enable Hot Module Reloading 14 | if (module.hot) { 15 | module.hot.accept(); 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/ConstructCSS.js: -------------------------------------------------------------------------------- 1 | const ConstructCSS = (...args) => args 2 | .filter(argument => argument) // Get truthy only 3 | .map(argument => argument.split(' ')) // Split by spaces 4 | .reduce((accmulator, currentClasses) => { // Combine all classes 5 | accmulator.push(...currentClasses); 6 | return accmulator; 7 | }, []) 8 | .filter(argument => argument) // Get truthy only 9 | .join(' '); // Join back into a single class 10 | 11 | export default ConstructCSS; 12 | -------------------------------------------------------------------------------- /src/pages/edit/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | import { connect } from 'react-redux'; 4 | import BlocklyComponent from '../../components/BlocklyComponent'; 5 | import NavigationBar from '../../components/NavigationBar'; 6 | import Toasts from '../../components/Toasts'; 7 | import ToolBox from '../../components/ToolBox'; 8 | 9 | class EditPage extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.toolbox = React.createRef(); 13 | this.onCreated = this.onCreated.bind(this); 14 | 15 | this.state = { 16 | workspace: null 17 | } 18 | } 19 | 20 | onCreated({ workspace }) { 21 | this.setState({ 22 | workspace 23 | }) 24 | } 25 | 26 | render() { 27 | return ( 28 | <> 29 | 33 | {this.props.document.name} 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | } 43 | 44 | const mapStateToProps = (state) => { 45 | const { document } = state; 46 | return { document }; 47 | } 48 | 49 | export default connect(mapStateToProps)(EditPage); 50 | -------------------------------------------------------------------------------- /src/pages/index/firefox_PRVJSwMtRm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7coil/discord-blocks/37d6a1da643bced96dfb6fe89dee7612a134b620/src/pages/index/firefox_PRVJSwMtRm.png -------------------------------------------------------------------------------- /src/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import TransportSignTitle from '../../components/TransportSignTitle'; 5 | import TransportSignBadge from '../../components/TransportSignBadge'; 6 | import TransportSign from '../../components/TransportSign'; 7 | 8 | import styles from './index.scss'; 9 | import picture from './firefox_PRVJSwMtRm.png'; 10 | 11 | const places = [ 12 | { 13 | label: 'Shopmobility', 14 | style: 'white', 15 | link: 'https://www.google.com/maps/search/shopmobility/@54.6284018,-5.944877,7z' 16 | },{ 17 | label: 'Top Secret Bunker', 18 | style: 'attraction', 19 | link: 'https://secretnuclearbunker.com/' 20 | },{ 21 | label: 'Chessington World of Adventures', 22 | style: 'attraction', 23 | link: 'https://www.chessington.com/' 24 | },{ 25 | label: 'Tower of London', 26 | style: 'attraction', 27 | link: 'https://www.hrp.org.uk/tower-of-london/' 28 | },{ 29 | label: 'Newport Transporter Bridge', 30 | style: 'attraction', 31 | link: 'https://www.newport.gov.uk/heritage/Transporter-Bridge/Transporter-Bridge.aspx' 32 | },{ 33 | label: 'Queen Elizabeth Olympic Park', 34 | style: 'attraction', 35 | link: 'https://www.queenelizabetholympicpark.co.uk' 36 | } 37 | ] 38 | 39 | class IndexPage extends Component { 40 | constructor(props) { 41 | super(props); 42 | 43 | this.state = { 44 | label: null, 45 | style: null 46 | } 47 | } 48 | 49 | componentDidMount() { 50 | const place = places[Math.floor(Math.random() * places.length)] 51 | 52 | this.setState(place) 53 | } 54 | 55 | render() { 56 | return ( 57 |
58 |
59 | // TODO: Enter a header for the user to navigate between pages 60 |
61 |
62 |
63 |

DiscordBlocks (Alpha)

64 |

65 | An easy to use* block based Discord Bot creator and editor, based on Discord.js and Google Blockly
66 | *citation needed 67 |

68 |
69 | 70 |
71 | Enter the Editor 72 |
73 |
74 |
75 | // TODO: Enter some examples that users can click on here 76 |
77 | 82 |
83 | ) 84 | } 85 | } 86 | 87 | const mapStateToProps = (state) => { 88 | const { document } = state; 89 | return { document }; 90 | } 91 | 92 | export default connect(mapStateToProps)(IndexPage); 93 | -------------------------------------------------------------------------------- /src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | @use 'settings'; 2 | 3 | .jumbotron { 4 | font-family: settings.$main-font; 5 | max-width: settings.$page-width; 6 | width: 100%; 7 | margin-left: auto; 8 | margin-right: auto; 9 | box-sizing: border-box; 10 | 11 | display: grid; 12 | grid-template-areas: 'content image' 13 | 'links image'; 14 | grid-template-columns: 50% 50%; 15 | 16 | @include settings.mobile { 17 | grid-template-areas: 'content' 18 | 'links' 19 | 'image'; 20 | grid-template-columns: 1fr; 21 | } 22 | 23 | .jumbotronContent { 24 | grid-area: content; 25 | } 26 | 27 | .jumbotronImage { 28 | grid-area: image; 29 | max-width: 100%; 30 | } 31 | 32 | .jumbotronLinks { 33 | grid-area: links; 34 | a { 35 | @include settings.motorway; 36 | text-decoration: none; 37 | padding: 1em; 38 | border-radius: 0.5em; 39 | display: inline-block; 40 | 41 | &::after { 42 | content: '→'; 43 | margin-left: 1em; 44 | } 45 | } 46 | } 47 | } 48 | 49 | .footer { 50 | font-size: 1.3em; 51 | margin-top: 2em; 52 | padding: 2em; 53 | @include settings.primary-road; 54 | .footerContent { 55 | font-family: settings.$main-font; 56 | max-width: settings.$page-width; 57 | width: 100%; 58 | margin-left: auto; 59 | margin-right: auto; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/redux/actions/document.js: -------------------------------------------------------------------------------- 1 | export const SAVE_DOCUMENT = 'SAVE_DOCUMENT'; 2 | export const SET_DOCUMENT_NAME = 'SET_DOCUMENT_NAME'; 3 | export const CHANGED_DOCUMENT = 'CHANGED_DOCUMENT'; 4 | export const MODIFY_MODULES = 'MODIFY_MODULES'; 5 | 6 | function saveDocument() { 7 | return { 8 | type: SAVE_DOCUMENT 9 | } 10 | } 11 | 12 | function changedDocument() { 13 | return { 14 | type: CHANGED_DOCUMENT 15 | } 16 | } 17 | 18 | function setDocumentName(name) { 19 | return { 20 | type: SET_DOCUMENT_NAME, 21 | name: name 22 | } 23 | } 24 | 25 | function modifyModules(module, enabled) { 26 | return { 27 | type: MODIFY_MODULES, 28 | module, 29 | enabled, 30 | } 31 | } 32 | 33 | export { 34 | saveDocument, 35 | changedDocument, 36 | setDocumentName, 37 | modifyModules 38 | } 39 | -------------------------------------------------------------------------------- /src/redux/actions/toasts.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 2 | export const CREATE_TOAST = 'CREATE_TOAST'; 3 | export const DELETE_TOAST = 'DELETE_TOAST'; 4 | 5 | const pushToast = ({ 6 | id, content, colour 7 | }) => ({ 8 | type: CREATE_TOAST, 9 | toast: { 10 | id, 11 | content, 12 | colour 13 | } 14 | }) 15 | 16 | const incrementToastCounter = () => { 17 | return { 18 | type: INCREMENT_COUNTER 19 | } 20 | } 21 | 22 | const createToast = ({ 23 | content = 'This is an empty toast', 24 | colour = 'red', 25 | timeout = 5000 26 | }) => { 27 | return (dispatch, getState) => { 28 | dispatch(incrementToastCounter()); 29 | const id = getState().toasts.counter; 30 | dispatch(pushToast({ 31 | id, 32 | content, 33 | colour 34 | })) 35 | 36 | if (timeout !== 0) { 37 | setTimeout(() => { 38 | dispatch(deleteToast(id)); 39 | }, timeout); 40 | } 41 | 42 | return id; 43 | } 44 | } 45 | 46 | const deleteToast = (id) => { 47 | return { 48 | type: DELETE_TOAST, 49 | id 50 | } 51 | } 52 | 53 | export { 54 | createToast, 55 | incrementToastCounter, 56 | deleteToast 57 | }; 58 | -------------------------------------------------------------------------------- /src/redux/reducers/document.js: -------------------------------------------------------------------------------- 1 | import { 2 | SAVE_DOCUMENT, 3 | SET_DOCUMENT_NAME, 4 | CHANGED_DOCUMENT, 5 | MODIFY_MODULES 6 | } from '../actions/document'; 7 | 8 | function document(state = { 9 | saved: true, 10 | name: 'Untitled Document', 11 | modules: ['discordjs'], 12 | }, action) { 13 | switch (action.type) { 14 | case SAVE_DOCUMENT: 15 | return Object.assign({}, state, { 16 | saved: true 17 | }); 18 | case CHANGED_DOCUMENT: 19 | return Object.assign({}, state, { 20 | saved: false 21 | }); 22 | case SET_DOCUMENT_NAME: 23 | return Object.assign({}, state, { 24 | name: action.name 25 | }); 26 | case MODIFY_MODULES: 27 | if (action.enabled) { 28 | // Add the module to the list of modules 29 | if (state.modules.includes(action.module)) { 30 | // If it already is in the list, do nothing 31 | return state; 32 | } else { 33 | // If it is not, add it to the end. 34 | return Object.assign({}, state, { 35 | modules: [...state.modules, action.module] 36 | }) 37 | } 38 | } else { 39 | // Remove the offending module from the list of modules 40 | return Object.assign({}, state, { 41 | modules: state.modules.filter(module => module !== action.module) 42 | }) 43 | } 44 | default: 45 | return state; 46 | } 47 | } 48 | 49 | export default document; 50 | -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import document from './document'; 4 | import toasts from './toasts'; 5 | 6 | export default combineReducers({ 7 | document, 8 | toasts 9 | }); 10 | -------------------------------------------------------------------------------- /src/redux/reducers/toasts.js: -------------------------------------------------------------------------------- 1 | import { 2 | INCREMENT_COUNTER, 3 | CREATE_TOAST, 4 | DELETE_TOAST 5 | } from '../actions/toasts'; 6 | 7 | function document(state = { 8 | counter: 0, 9 | toasts: [] 10 | }, action) { 11 | switch (action.type) { 12 | case INCREMENT_COUNTER: 13 | return Object.assign({}, state, { 14 | counter: state.counter + 1 15 | }) 16 | case CREATE_TOAST: 17 | return Object.assign({}, state, { 18 | toasts: [...state.toasts, action.toast] 19 | }) 20 | case DELETE_TOAST: 21 | return Object.assign({}, state, { 22 | toasts: state.toasts.filter(toast => toast.id !== action.id) 23 | }) 24 | default: 25 | return state; 26 | } 27 | } 28 | 29 | export default document; 30 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import reducer from './reducers'; 4 | 5 | const stuffToCompose = [ 6 | applyMiddleware(thunk) 7 | ]; 8 | 9 | if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) { 10 | stuffToCompose.push(window.__REDUX_DEVTOOLS_EXTENSION__()); 11 | } 12 | 13 | export default function configureStore() { 14 | if (typeof window !== 'undefined' && window.REDUX_STATE) { 15 | return createStore( 16 | reducer, 17 | window.REDUX_STATE, 18 | compose(...stuffToCompose) 19 | ); 20 | } 21 | return createStore( 22 | reducer, 23 | compose(...stuffToCompose) 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Transport New'; 3 | src: url('https://fonts.leondrolio.com/TransportNew-Medium.ttf'); 4 | } 5 | 6 | @font-face { 7 | font-family: 'Transport New'; 8 | src: url('https://fonts.leondrolio.com/TransportNew-Light.ttf'); 9 | font-weight: lighter; 10 | } 11 | 12 | @font-face { 13 | font-family: 'Transport New'; 14 | src: url('https://fonts.leondrolio.com/TransportNew-Heavy.ttf'); 15 | font-weight: bold; 16 | } 17 | 18 | @font-face { 19 | font-family: 'Transport New'; 20 | src: url('https://fonts.leondrolio.com/TransportNew-Medium.ttf'); 21 | font-style: italic; 22 | } 23 | 24 | @font-face { 25 | font-family: 'Transport New'; 26 | src: url('https://fonts.leondrolio.com/TransportNew-LightItalic.ttf'); 27 | font-weight: lighter; 28 | font-style: italic; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Transport New'; 33 | src: url('https://fonts.leondrolio.com/TransportNew-HeavyItalic.ttf'); 34 | font-weight: bold; 35 | font-style: italic; 36 | } 37 | 38 | html, body { 39 | padding: 0; 40 | margin: 0; 41 | } 42 | 43 | xml { 44 | display: none; 45 | } 46 | 47 | input[class*=blocklyHtmlInput] { 48 | border: none !important; 49 | border-radius: 4px !important; 50 | font-family: sans-serif !important; 51 | height: 100% !important; 52 | margin: 0 !important; 53 | outline: none !important; 54 | padding: 0 1px !important; 55 | width: 100% !important; 56 | } 57 | 58 | div[id=app] { 59 | height: 100vh; 60 | display: flex; 61 | flex-direction: column; 62 | } 63 | -------------------------------------------------------------------------------- /src/scss/settings.scss: -------------------------------------------------------------------------------- 1 | $primary: #eeeeee; 2 | $page-width: 1100px; 3 | $user-font: 'Arial', sans-serif; 4 | 5 | @mixin primary-road { 6 | background-color: rgb(0, 112, 60); 7 | color: rgb(255, 210, 0); 8 | } 9 | 10 | @mixin attraction { 11 | background-color: rgb(121, 68, 0); 12 | color: white; 13 | } 14 | 15 | @mixin non-primary-road { 16 | background-color: white; 17 | color: black; 18 | } 19 | 20 | @mixin motorway { 21 | background-color: rgb(0, 121, 193); 22 | color: white; 23 | } 24 | 25 | @mixin mobile { 26 | @media (max-width: $page-width) { 27 | @content 28 | } 29 | } 30 | 31 | // DO NOT USE `$main-font` FOR USER GENERATED CONTENT 32 | // The licence for this font does not allow for this. 33 | $main-font: 'Transport New', #{$user-font}; 34 | 35 | @function colourDepth($colour, $depth) { 36 | @return darken($colour, ($depth * 5%)); 37 | } 38 | 39 | @function primaryDepth($depth) { 40 | @return colourDepth($primary, $depth) 41 | } 42 | -------------------------------------------------------------------------------- /static/_redirects: -------------------------------------------------------------------------------- 1 | /edit /index.html 200 2 | -------------------------------------------------------------------------------- /static/example1.dbl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7coil/discord-blocks/37d6a1da643bced96dfb6fe89dee7612a134b620/static/example1.dbl --------------------------------------------------------------------------------