├── src ├── styles │ ├── options.css │ ├── help.css │ ├── main.css │ ├── appbar.css │ ├── list-field.css │ ├── output-box.css │ └── list-container.css ├── js │ ├── components │ │ ├── Transition.js │ │ ├── DragHandle.js │ │ ├── options │ │ │ ├── TagMenu.js │ │ │ ├── LocalVarPrefixField.js │ │ │ ├── OptionsButton.js │ │ │ ├── LegacyToggle.js │ │ │ ├── FuncTagSelector.js │ │ │ ├── DescTagSelector.js │ │ │ ├── ArgTagSelector.js │ │ │ └── OptionsDialog.js │ │ ├── arguments │ │ │ ├── AddArgumentButton.js │ │ │ ├── ArgumentContainer.js │ │ │ ├── ArgumentField.js │ │ │ ├── ArgumentSortable.js │ │ │ └── ArgumentDialog.js │ │ ├── localvars │ │ │ ├── AddLocalVarButton.js │ │ │ ├── LocalVarContainer.js │ │ │ ├── LocalVarField.js │ │ │ └── LocalVarSortable.js │ │ ├── ScriptNameField.js │ │ ├── DescriptionField.js │ │ ├── help │ │ │ ├── HelpButton.js │ │ │ └── HelpDialog.js │ │ ├── OutputBox.js │ │ ├── TitleBar.js │ │ ├── App.js │ │ └── CopyScriptButton.js │ ├── actions │ │ ├── id.js │ │ ├── arguments.js │ │ ├── localVars.js │ │ └── options.js │ ├── index.js │ ├── helpers │ │ ├── initialState.js │ │ ├── EventTypes.js │ │ ├── ColourTheme.js │ │ └── generateScript.js │ ├── store │ │ ├── localStorage.js │ │ └── index.js │ ├── electron-index.js │ └── reducers │ │ └── index.js └── index.html ├── docs ├── gmlsw-1.png └── gmlsw-2.png ├── electron ├── package.json └── main.js ├── .babelrc ├── GMLScriptWizard.desktop ├── .gitignore ├── webpack.prod.js ├── webpack.dev.js ├── .eslintrc ├── README.md ├── webpack.common.js ├── CHANGELOG.md ├── LICENSE └── package.json /src/styles/options.css: -------------------------------------------------------------------------------- 1 | .options-root { 2 | padding: 72px 8px 0 8px; 3 | } -------------------------------------------------------------------------------- /docs/gmlsw-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstop4/gml-script-wizard/HEAD/docs/gmlsw-1.png -------------------------------------------------------------------------------- /docs/gmlsw-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstop4/gml-script-wizard/HEAD/docs/gmlsw-2.png -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gml-script-wizard", 3 | "version": "0.9.3", 4 | "main": "main.js" 5 | } -------------------------------------------------------------------------------- /src/styles/help.css: -------------------------------------------------------------------------------- 1 | .help-link:link, .help-link:visited, .help-link:hover, .help-link:active { 2 | color: #039d5b; 3 | text-decoration: none; 4 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ], 6 | 7 | "plugins": [ 8 | "transform-object-rest-spread" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #181818; 3 | padding: 72px 8px 0 8px; 4 | } 5 | 6 | ul { 7 | list-style: none; 8 | padding-left: 0; 9 | } -------------------------------------------------------------------------------- /GMLScriptWizard.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Version=1.0 4 | Type=Application 5 | Terminal=false 6 | Exec=/path/to/executable/GMLScriptWizard 7 | Name=GML Script Wizard -------------------------------------------------------------------------------- /src/js/components/Transition.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Slide from 'material-ui/transitions/Slide' 3 | 4 | const Transition = (props) => { 5 | return 6 | } 7 | 8 | export default Transition -------------------------------------------------------------------------------- /src/js/components/DragHandle.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Icon from 'material-ui/Icon' 3 | import { SortableHandle } from 'react-sortable-hoc' 4 | 5 | const DragHandle = SortableHandle(() => reorder) 6 | 7 | export default DragHandle -------------------------------------------------------------------------------- /src/styles/appbar.css: -------------------------------------------------------------------------------- 1 | .appbar-root { 2 | display: flex; 3 | flex-grow: 1; 4 | flex-basis: 0; 5 | justify-content: space-between; 6 | align-items: center; 7 | } 8 | 9 | .appbar-branding { 10 | flex-grow: 1; 11 | flex-basis: 0; 12 | } 13 | 14 | .appbar-buttons { 15 | } 16 | -------------------------------------------------------------------------------- /src/js/actions/id.js: -------------------------------------------------------------------------------- 1 | import { NAME_CHANGE, DESC_CHANGE } from '../helpers/eventTypes' 2 | 3 | export const scriptNameChange = (value) => ({ 4 | type: NAME_CHANGE, 5 | payload: {value: value} 6 | }) 7 | 8 | export const descriptionChange = (value) => ({ 9 | type: DESC_CHANGE, 10 | payload: {value: value} 11 | }) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist/* 3 | electron/js/ 4 | electron/*.html 5 | .DS_Store 6 | yarn-error.log 7 | *.stackdump 8 | 9 | GMLScriptWizard-win32-x64/ 10 | GMLScriptWizard-darwin-x64/ 11 | GMLScriptWizard-linux-x64/ 12 | 13 | win32-x64-template/ 14 | darwin-x64-template/ 15 | linux-x64-template/ 16 | 17 | *.zip 18 | *.tar.gz -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import store from './store/index' 5 | import App from './components/App' 6 | 7 | render ( 8 | 9 | 10 | , 11 | document.getElementById('main-app') 12 | ) -------------------------------------------------------------------------------- /src/js/helpers/initialState.js: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | options: { 3 | legacyMode: false, 4 | localVarPrefix: '_', 5 | functionTag: '@function', 6 | descriptionTag: '@description', 7 | argumentTag: '@argument' 8 | }, 9 | scriptName: '', 10 | description: '', 11 | outputValue: '', 12 | args: [], 13 | localVars: [], 14 | } -------------------------------------------------------------------------------- /src/styles/list-field.css: -------------------------------------------------------------------------------- 1 | .field-root { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | } 6 | 7 | .field-button { 8 | padding: 5px; 9 | } 10 | 11 | .field-textfield { 12 | padding: 5px; 13 | flex-grow: 1; 14 | flex-basis: 0; 15 | } 16 | 17 | .field-drag-handle { 18 | color: #fff; 19 | padding: 8px 16px; 20 | } -------------------------------------------------------------------------------- /src/styles/output-box.css: -------------------------------------------------------------------------------- 1 | #generated-script { 2 | color: #fff; 3 | font-size: 14px; 4 | font-family: 'Droid Sans Mono', monospace; 5 | white-space: pre-line; 6 | } 7 | 8 | .output-root { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | } 13 | 14 | .output-title { 15 | flex-grow: 1; 16 | flex-basis: 0; 17 | } -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin') 4 | const common = require('./webpack.common.js') 5 | 6 | module.exports = merge( common, { 7 | plugins: [ 8 | new UglifyJSPlugin(), 9 | new webpack.DefinePlugin({ 10 | 'process.env': { 11 | 'NODE_ENV': JSON.stringify('production') 12 | } 13 | }) 14 | ] 15 | }) -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const common = require('./webpack.common.js') 4 | 5 | module.exports = merge( common, { 6 | devtool: 'inline-source-map', 7 | 8 | devServer: { 9 | contentBase: './dist' 10 | }, 11 | 12 | plugins: [ 13 | new webpack.DefinePlugin({ 14 | 'process.env': { 15 | 'NODE_ENV': JSON.stringify('development') 16 | } 17 | }) 18 | ] 19 | }) -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | GML Script Wizard 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/styles/list-container.css: -------------------------------------------------------------------------------- 1 | .container-root { 2 | overflow-x: hidden; 3 | } 4 | 5 | .container-header { 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | } 10 | 11 | .container-title { 12 | flex-grow: 1; 13 | flex-basis: 0; 14 | padding: 5px; 15 | } 16 | 17 | .container-add { 18 | padding: 1px; 19 | } 20 | 21 | .container-list { 22 | height: 100vh; 23 | height: -webkit-calc(100vh - 152px); 24 | height: -moz-calc(100vh - 152px); 25 | height: calc(100vh - 152px); 26 | } -------------------------------------------------------------------------------- /src/js/store/localStorage.js: -------------------------------------------------------------------------------- 1 | export const loadState = () => { 2 | try { 3 | const serialState = localStorage.getItem('state') 4 | if (serialState === null) { 5 | return undefined 6 | } 7 | return JSON.parse(serialState) 8 | } catch (err) { 9 | return undefined 10 | } 11 | } 12 | 13 | export const saveState = (state) => { 14 | try { 15 | const serializedState = JSON.stringify(state) 16 | localStorage.setItem('state', serializedState) 17 | } catch (err) { 18 | // Do nothing 19 | } 20 | } -------------------------------------------------------------------------------- /src/js/helpers/EventTypes.js: -------------------------------------------------------------------------------- 1 | export const NAME_CHANGE = 'NAME_CHANGE' 2 | export const DESC_CHANGE = 'DESC_CHANGE' 3 | 4 | export const ARG_CHANGE = 'ARG_CHANGE' 5 | export const ARG_SORT = 'ARG_SORT' 6 | export const ARG_ADD = 'ARG_ADD' 7 | export const ARG_REMOVE = 'ARG_REMOVE' 8 | 9 | export const LVAR_CHANGE = 'LVAR_CHANGE' 10 | export const LVAR_SORT = 'LVAR_SORT' 11 | export const LVAR_ADD = 'LVAR_ADD' 12 | export const LVAR_REMOVE = 'LVAR_REMOVE' 13 | 14 | export const OPT_LEGACY = 'OPT_LEGACY' 15 | export const OPT_CHANGE = 'OPT_CHANGE' -------------------------------------------------------------------------------- /src/js/actions/arguments.js: -------------------------------------------------------------------------------- 1 | import { ARG_CHANGE, ARG_SORT, ARG_ADD, ARG_REMOVE } from '../helpers/eventTypes' 2 | 3 | export const argumentChange = (id, key, value) => ({ 4 | type: ARG_CHANGE, 5 | payload: {id: id, key: key, value: value} 6 | }) 7 | 8 | export const argumentSort = (oldIndex, newIndex) => ({ 9 | type: ARG_SORT, 10 | payload: {oldIndex: oldIndex, newIndex: newIndex} 11 | }) 12 | 13 | export const argumentAdd = () => ({ 14 | type: ARG_ADD 15 | }) 16 | 17 | export const argumentRemove = (id) => ({ 18 | type: ARG_REMOVE, 19 | payload: {id: id} 20 | }) -------------------------------------------------------------------------------- /src/js/actions/localVars.js: -------------------------------------------------------------------------------- 1 | import { LVAR_CHANGE, LVAR_SORT, LVAR_ADD, LVAR_REMOVE } from '../helpers/eventTypes' 2 | 3 | export const localVarChange = (id, key, value) => ({ 4 | type: LVAR_CHANGE, 5 | payload: {id: id, key: key, value: value} 6 | }) 7 | 8 | export const localVarSort = (oldIndex, newIndex) => ({ 9 | type: LVAR_SORT, 10 | payload: {oldIndex: oldIndex, newIndex: newIndex} 11 | }) 12 | 13 | export const localVarAdd = () => ({ 14 | type: LVAR_ADD 15 | }) 16 | 17 | export const localVarRemove = (id) => ({ 18 | type: LVAR_REMOVE, 19 | payload: {id: id} 20 | }) -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "react" 4 | ], 5 | 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true, 11 | "experimentalObjectRestSpread": true 12 | } 13 | }, 14 | 15 | "env": { 16 | "es6": true, 17 | "browser": true, 18 | "node": true, 19 | "mocha": true 20 | }, 21 | 22 | "extends": [ 23 | "eslint:recommended", 24 | "plugin:react/recommended" 25 | ], 26 | 27 | "rules": { 28 | "react/prop-types": 1, 29 | "react/no-unescaped-entities": 0, 30 | "quotes": [1, "single", { "avoidEscape": true, "allowTemplateLiterals": true }] 31 | } 32 | } -------------------------------------------------------------------------------- /src/js/actions/options.js: -------------------------------------------------------------------------------- 1 | import { OPT_LEGACY, OPT_CHANGE } from '../helpers/eventTypes' 2 | 3 | export const legacyToggle = () => ({ 4 | type: OPT_LEGACY 5 | }) 6 | 7 | export const prefixChange = (id, value) => ({ 8 | type: OPT_CHANGE, 9 | payload: {id: id, value: value} 10 | }) 11 | 12 | export const funcTagChange = (value) => ({ 13 | type: OPT_CHANGE, 14 | payload: {id: 'functionTag', value: value} 15 | }) 16 | 17 | export const descTagChange = (value) => ({ 18 | type: OPT_CHANGE, 19 | payload: {id: 'descriptionTag', value: value} 20 | }) 21 | 22 | export const argTagChange = (value) => ({ 23 | type: OPT_CHANGE, 24 | payload: {id: 'argumentTag', value: value} 25 | }) -------------------------------------------------------------------------------- /src/js/components/options/TagMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Grid from 'material-ui/Grid' 3 | import LocalVarPrefixField from './LocalVarPrefixField' 4 | import FuncTagSelector from './FuncTagSelector' 5 | import DescTagSelector from './DescTagSelector' 6 | import ArgTagSelector from './ArgTagSelector' 7 | 8 | const TagMenu = () => ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default TagMenu -------------------------------------------------------------------------------- /electron/main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | 3 | const app = electron.app; 4 | const BrowserWindow = electron.BrowserWindow; 5 | 6 | const createWindow = () => { 7 | let window = new BrowserWindow({ 8 | height: 720, 9 | width: 1280 10 | }); 11 | 12 | window.loadURL(`file://${__dirname}/index.html`); 13 | window.setMenu(null); 14 | 15 | return window; 16 | }; 17 | 18 | app.on('ready', () => { 19 | let mainWindow = createWindow(); 20 | 21 | app.on('window-all-closed', () => { 22 | if (process.platform !== 'darwin') { 23 | app.quit(); 24 | } 25 | 26 | mainWindow = null; 27 | }); 28 | 29 | app.on('activate', function() { 30 | if (mainWindow === null) { 31 | createWindow(); 32 | } 33 | }); 34 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GML Script Wizard 2 | 3 | **[GML Script Wizard](https://mstop4.github.io/gml-script-wizard/)** is a tool that will help you generate and modify GML script headers just be filling in a few fields. 4 | 5 | !["Screenshot 1"](https://github.com/mstop4/gml-script-wizard/blob/master/docs/gmlsw-1.png) 6 | !["Screenshot 2"](https://github.com/mstop4/gml-script-wizard/blob/master/docs/gmlsw-2.png) 7 | 8 | * Conforms to both GM:S 1.4 and GMS 2 (JSDoc) documentation styles for documenting scripts. 9 | * Add, remove, and rearrange arguments and additional local variables with ease. 10 | * Copy the script template with a simple click of a button to paste it into the GameMaker Studio IDE or the script editor, or your choice. 11 | 12 | ## Demo 13 | 14 | https://mstop4.github.io/gml-script-wizard/ 15 | -------------------------------------------------------------------------------- /src/js/components/arguments/AddArgumentButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | 6 | import { argumentAdd } from '../../actions/arguments' 7 | import PropTypes from 'prop-types' 8 | 9 | const mapDispatchToProps = (dispatch) => ({ 10 | onClick: () => dispatch(argumentAdd()) 11 | }) 12 | 13 | const AddArgumentButton = ({ onClick }) => ( 14 |
15 | 20 | add_circle 21 | 22 |
23 | ) 24 | 25 | AddArgumentButton.propTypes = { 26 | onClick: PropTypes.func 27 | } 28 | 29 | export default connect(null, mapDispatchToProps)(AddArgumentButton) -------------------------------------------------------------------------------- /src/js/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux' 2 | import rootReducer from '../reducers/index' 3 | import { loadState, saveState } from './localStorage' 4 | import { initialState } from '../helpers/initialState' 5 | import throttle from 'lodash/throttle' 6 | 7 | // TODO: Figure out how to configure Redux devtools for production/development 8 | 9 | let initialStateCopy = initialState 10 | 11 | const loadedState = loadState(); 12 | if (loadedState) { 13 | initialStateCopy.options = loadedState.options 14 | } 15 | 16 | const store = createStore( 17 | rootReducer, 18 | initialStateCopy//, 19 | //window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 20 | ) 21 | 22 | store.subscribe(throttle(() => { 23 | saveState({ 24 | options: store.getState().options 25 | }) 26 | }, 1000)) 27 | 28 | export default store -------------------------------------------------------------------------------- /src/js/components/localvars/AddLocalVarButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | 6 | import { localVarAdd } from '../../actions/localVars' 7 | import PropTypes from 'prop-types' 8 | 9 | const mapDispatchToProps = (dispatch) => ({ 10 | onClick: () => dispatch(localVarAdd()) 11 | }) 12 | 13 | const AddLocalVarButton = ({ onClick }) => ( 14 |
15 | 20 | add_circle 21 | 22 |
23 | ) 24 | 25 | AddLocalVarButton.propTypes = { 26 | onClick: PropTypes.func 27 | } 28 | 29 | export default connect(null, mapDispatchToProps)(AddLocalVarButton) -------------------------------------------------------------------------------- /src/js/helpers/ColourTheme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from 'material-ui/styles' 2 | import red from 'material-ui/colors/red' 3 | import grey from 'material-ui/colors/grey' 4 | 5 | const ColourTheme = createMuiTheme({ 6 | typography: { 7 | fontFamily: '"Open Sans",sans-serif', 8 | headline: { 9 | fontFamily: 'Oswald' 10 | } 11 | }, 12 | 13 | palette: { 14 | type: 'dark', 15 | background: { 16 | paper: '#232323' 17 | }, 18 | 19 | primary: { 20 | light: '#039d5b', 21 | main: '#039d5b', 22 | dark: '#00664d' 23 | }, 24 | secondary: { 25 | light: grey[400], 26 | main: grey[600], 27 | dark: grey[700] 28 | }, 29 | error: { 30 | light: red[400], 31 | main: red[600], 32 | dark: red[700] 33 | } 34 | } 35 | }) 36 | 37 | export default ColourTheme -------------------------------------------------------------------------------- /src/js/components/ScriptNameField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import TextField from 'material-ui/TextField' 4 | import PropTypes from 'prop-types' 5 | 6 | import { scriptNameChange } from '../actions/id' 7 | 8 | const mapStateToProps = (state) => ({ 9 | value: state.scriptName 10 | }) 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | onChange: (event) => dispatch(scriptNameChange(event.target.value)) 14 | }) 15 | 16 | const ScriptNameField = ({ value, onChange }) => { 17 | return ( 18 | 26 | ) 27 | } 28 | 29 | ScriptNameField.propTypes = { 30 | value: PropTypes.string, 31 | onChange: PropTypes.func 32 | } 33 | 34 | export default connect(mapStateToProps, mapDispatchToProps)(ScriptNameField) -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CleanWebpackPlugin = require('clean-webpack-plugin') 3 | const HtmlWebPackPlugin = require('html-webpack-plugin') 4 | 5 | module.exports = { 6 | entry: ['babel-polyfill', './src/js/index.js'], 7 | 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'js/bundle.js' 11 | }, 12 | 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | use: 'babel-loader' 19 | }, 20 | 21 | { 22 | test: /\.html$/, 23 | use: 'html-loader' 24 | }, 25 | 26 | { 27 | test: /\.css$/, 28 | use: [ 'style-loader', 'css-loader' ] 29 | } 30 | ] 31 | }, 32 | 33 | plugins: [ 34 | new CleanWebpackPlugin(['dist']), 35 | new HtmlWebPackPlugin({ 36 | template: './src/index.html', 37 | filename: './index.html' 38 | }) 39 | ] 40 | } -------------------------------------------------------------------------------- /src/js/components/DescriptionField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import TextField from 'material-ui/TextField' 4 | import PropTypes from 'prop-types' 5 | 6 | import { descriptionChange } from '../actions/id' 7 | 8 | const mapStateToProps = (state) => ({ 9 | value: state.description 10 | }) 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | onChange: (event) => dispatch(descriptionChange(event.target.value)), 14 | }) 15 | 16 | const DescriptionField = ({ value, onChange }) => { 17 | return ( 18 | 27 | ) 28 | } 29 | 30 | DescriptionField.propTypes = { 31 | value: PropTypes.string, 32 | onChange: PropTypes.func 33 | } 34 | 35 | export default connect(mapStateToProps, mapDispatchToProps)(DescriptionField) -------------------------------------------------------------------------------- /src/js/components/localvars/LocalVarContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from 'material-ui/Typography' 3 | import { Scrollbars } from 'react-custom-scrollbars' 4 | //import PropTypes from 'prop-types' 5 | 6 | import LocalVarSortable from './LocalVarSortable' 7 | import AddLocalVarButton from './AddLocalVarButton' 8 | 9 | const LocalVarContainer = () => { 10 | return ( 11 |
12 |
13 |
14 | Local Variables 15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | ) 27 | } 28 | 29 | //LocalVarContainer.propTypes = {} 30 | 31 | export default LocalVarContainer -------------------------------------------------------------------------------- /src/js/components/arguments/ArgumentContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from 'material-ui/Typography' 3 | import { Scrollbars } from 'react-custom-scrollbars' 4 | //import PropTypes from 'prop-types' 5 | 6 | import ArgumentSortable from './ArgumentSortable' 7 | import AddArgumentButton from './AddArgumentButton' 8 | 9 | import '../../../styles/list-container.css' 10 | 11 | const ArgumentContainer = () => { 12 | return ( 13 |
14 |
15 |
16 | Arguments 17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | //ArgumentContainer.propTypes = {} 32 | 33 | export default ArgumentContainer -------------------------------------------------------------------------------- /src/js/components/help/HelpButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import HelpDialog from './HelpDialog' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | 6 | class HelpButton extends Component { 7 | constructor() { 8 | super() 9 | 10 | this.state = { 11 | dialogOpen: false 12 | } 13 | 14 | this.handleDialogOpen = this.handleDialogOpen.bind(this) 15 | this.handleDialogClose = this.handleDialogClose.bind(this) 16 | } 17 | 18 | handleDialogOpen() { 19 | this.setState({ dialogOpen: true }) 20 | } 21 | 22 | handleDialogClose() { 23 | this.setState({ dialogOpen: false }) 24 | } 25 | 26 | render() { 27 | return ( 28 |
29 | 33 | 34 | help_outline 35 | 36 |
37 | ) 38 | } 39 | } 40 | 41 | export default HelpButton -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v.0.9.3 4 | 5 | * Added basic native builds for Windows, macOS, and Linux for offline use. I know there's no Linux IDE for either GM:S 1.4 or GMS2, I just added it in for the sake of completeness. 🙂 6 | * Argument descriptions are now added beside their respective argument declarations in GameMaker: Studio 1.4 mode. 7 | * Updated old dependencies that had security issues. 8 | 9 | ## v.0.9.2 10 | 11 | * App settings now persist between sessions, until your browser's local storage is cleared. 12 | 13 | ## v.0.9.1 14 | 15 | * Switched to using Redux to maintain app state. 16 | * Added settings to change between different function, description, and argument tags. 17 | * Bug fix: Clicking the delete button on a local variable field will now remove that field instead of always the first one. 18 | 19 | ## v.0.8 20 | 21 | Added a Settings menu with the following options: 22 | 23 | * A toggle to switch between generating GameMaker Studio 2 and GameMaker: Studio 1.4-style script documentation. 24 | * Customizable prefix for local variables. 25 | 26 | ## v.0.7 27 | 28 | * First public release. 29 | -------------------------------------------------------------------------------- /src/js/components/OutputBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import Card, { CardContent } from 'material-ui/Card' 4 | import Typography from 'material-ui/Typography' 5 | import CopyScriptButton from './CopyScriptButton' 6 | import PropTypes from 'prop-types' 7 | 8 | import '../../styles/output-box.css' 9 | 10 | const mapStateToProps = (state) => ({ 11 | value: state.outputValue 12 | }) 13 | 14 | const OutputBox = ({ value }) => { 15 | return ( 16 |
17 |
18 |
19 | Script 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 |

28 | {value} 29 |

30 |
31 |
32 |
33 | ) 34 | } 35 | 36 | OutputBox.propTypes = { 37 | value: PropTypes.string 38 | } 39 | 40 | export default connect(mapStateToProps, null)(OutputBox) -------------------------------------------------------------------------------- /src/js/components/options/LocalVarPrefixField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { prefixChange } from '../../actions/options' 4 | import TextField from 'material-ui/TextField' 5 | import propTypes from 'prop-types' 6 | 7 | const mapStateToProps = (state) => ({ 8 | localVarPrefix: state.options.localVarPrefix 9 | }) 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | onPrefixChange: (event) => dispatch(prefixChange(event.target.id, event.target.value)) 13 | }) 14 | 15 | const LocalVarPrefixField = (props) => { 16 | let { localVarPrefix, onPrefixChange } = props 17 | 18 | const handleTextFieldClick = (event) => { 19 | event.stopPropagation() 20 | } 21 | 22 | return ( 23 | 32 | ) 33 | } 34 | 35 | LocalVarPrefixField.propTypes = { 36 | localVarPrefix: propTypes.string, 37 | onPrefixChange: propTypes.func 38 | } 39 | 40 | export default connect( mapStateToProps, mapDispatchToProps )(LocalVarPrefixField) -------------------------------------------------------------------------------- /src/js/components/options/OptionsButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import OptionsDialog from './OptionsDialog' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | import PropTypes from 'prop-types' 6 | 7 | class OptionsButton extends Component { 8 | constructor(props) { 9 | super(props) 10 | 11 | this.state = { 12 | dialogOpen: false 13 | } 14 | 15 | this.handleDialogOpen = this.handleDialogOpen.bind(this) 16 | this.handleDialogClose = this.handleDialogClose.bind(this) 17 | } 18 | 19 | handleDialogOpen() { 20 | this.setState({ dialogOpen: true }) 21 | } 22 | 23 | handleDialogClose() { 24 | this.setState({ dialogOpen: false }) 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 | 34 | 35 | build 36 | 37 |
38 | ) 39 | } 40 | } 41 | 42 | OptionsButton.propTypes = { 43 | options: PropTypes.object, 44 | onEvent: PropTypes.func 45 | } 46 | 47 | export default OptionsButton -------------------------------------------------------------------------------- /src/js/electron-index.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron'); 2 | const path = require('path'); 3 | const url = require('url'); 4 | 5 | // Global reference to window object to prevent it from being garbage-collected 6 | let win; 7 | 8 | function createWindow() { 9 | win = new BrowserWindow({width: 1280, height: 720}); 10 | 11 | const startUrl = process.env.ELECTRON_START_URL || url.format({ 12 | pathname: path.join(__dirname, './../../dist/index.html'), 13 | protocol: 'file:', 14 | slashes: true 15 | }); 16 | win.loadURL(startUrl); 17 | 18 | win.webContents.openDevTools(); 19 | 20 | // dereference window when it is closed 21 | win.on('closed', () => { 22 | win = null; 23 | }); 24 | } 25 | 26 | app.on('ready', createWindow); 27 | 28 | app.on('window-all-closed', () => { 29 | // If current OS is not macOS, quit the app 30 | // macOS apps are able to still run after all its windows are closed 31 | if (process.platform !== 'darwin') { 32 | app.quit(); 33 | } 34 | }); 35 | 36 | app.on('activate', () => { 37 | // On macOS, create a window in the app when the dock icon is clicked and there 38 | // are no other windows present 39 | if (win === null) { 40 | createWindow(); 41 | } 42 | }); -------------------------------------------------------------------------------- /src/js/components/options/LegacyToggle.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { FormGroup, FormControlLabel, FormLabel } from 'material-ui/Form' 4 | import Switch from 'material-ui/Switch' 5 | 6 | import { legacyToggle } from '../../actions/options' 7 | import propTypes from 'prop-types' 8 | 9 | const mapStateToProps = (state) => ({ 10 | legacyMode: state.options.legacyMode 11 | }) 12 | 13 | const mapDispatchToProps = (dispatch) => ({ 14 | onLegacyChange: () => dispatch(legacyToggle()), 15 | }) 16 | 17 | const LegacyToggle = (props) => { 18 | let { legacyMode, onLegacyChange } = props 19 | 20 | return ( 21 | 22 | Documentation Style 23 | 29 | } 30 | label={legacyMode ? 'GameMaker: Studio 1.4' : 'GameMaker Studio 2'} 31 | /> 32 | 33 | ) 34 | } 35 | 36 | LegacyToggle.propTypes = { 37 | legacyMode: propTypes.bool, 38 | onLegacyChange: propTypes.func 39 | } 40 | 41 | export default connect( mapStateToProps, mapDispatchToProps )(LegacyToggle) -------------------------------------------------------------------------------- /src/js/components/TitleBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import Toolbar from 'material-ui/Toolbar' 4 | import Typography from 'material-ui/Typography' 5 | import Icon from 'material-ui/Icon' 6 | import HelpButton from './help/HelpButton' 7 | import OptionsButton from './options/OptionsButton' 8 | import PropTypes from 'prop-types' 9 | 10 | import '../../styles/appbar.css' 11 | 12 | const TitleBar = (props) => { 13 | let { options, onEvent } = props 14 | 15 | 16 | return ( 17 |
18 | 19 | 20 |
21 | 22 | description GML Script Wizard 23 | 24 |
25 |
26 | 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | TitleBar.propTypes = { 41 | options: PropTypes.object, 42 | onEvent: PropTypes.func 43 | } 44 | 45 | export default TitleBar -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, M.S.T.O.P. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/js/components/options/FuncTagSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { MenuItem } from 'material-ui/Menu' 4 | import Input, { InputLabel } from 'material-ui/Input' 5 | import Select from 'material-ui/Select' 6 | import { FormControl } from 'material-ui/Form' 7 | 8 | import { funcTagChange } from '../../actions/options' 9 | import propTypes from 'prop-types' 10 | 11 | const style = { 12 | width: '10em' 13 | } 14 | 15 | const mapStateToProps = (state) => ({ 16 | funcTag: state.options.functionTag 17 | }) 18 | 19 | const mapDispatchToProps = (dispatch) => ({ 20 | onTagChange: (event) => dispatch(funcTagChange(event.target.value)), 21 | }) 22 | 23 | const FuncTagSelector = (props) => { 24 | let { funcTag, onTagChange } = props 25 | 26 | return ( 27 | 28 | Function Tag 29 | 39 | 40 | ) 41 | } 42 | 43 | FuncTagSelector.propTypes = { 44 | funcTag: propTypes.string, 45 | onTagChange: propTypes.func 46 | } 47 | 48 | export default connect( mapStateToProps, mapDispatchToProps )(FuncTagSelector) -------------------------------------------------------------------------------- /src/js/components/options/DescTagSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { MenuItem } from 'material-ui/Menu' 4 | import Input, { InputLabel } from 'material-ui/Input' 5 | import Select from 'material-ui/Select' 6 | import { FormControl } from 'material-ui/Form' 7 | 8 | import { descTagChange } from '../../actions/options' 9 | import propTypes from 'prop-types' 10 | 11 | const style = { 12 | width: '10em' 13 | } 14 | 15 | const mapStateToProps = (state) => ({ 16 | descTag: state.options.descriptionTag 17 | }) 18 | 19 | const mapDispatchToProps = (dispatch) => ({ 20 | onTagChange: (event) => dispatch(descTagChange(event.target.value)), 21 | }) 22 | 23 | const DescTagSelector = (props) => { 24 | let { descTag, onTagChange } = props 25 | 26 | return ( 27 | 28 | Description Tag 29 | 39 | 40 | ) 41 | } 42 | 43 | DescTagSelector.propTypes = { 44 | descTag: propTypes.string, 45 | onTagChange: propTypes.func 46 | } 47 | 48 | export default connect( mapStateToProps, mapDispatchToProps )(DescTagSelector) -------------------------------------------------------------------------------- /src/js/components/options/ArgTagSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { MenuItem } from 'material-ui/Menu' 4 | import Input, { InputLabel } from 'material-ui/Input' 5 | import Select from 'material-ui/Select' 6 | import { FormControl } from 'material-ui/Form' 7 | 8 | import { argTagChange } from '../../actions/options' 9 | import propTypes from 'prop-types' 10 | 11 | const style = { 12 | width: '10em' 13 | } 14 | 15 | const mapStateToProps = (state) => ({ 16 | argTag: state.options.argumentTag 17 | }) 18 | 19 | const mapDispatchToProps = (dispatch) => ({ 20 | onTagChange: (event) => dispatch(argTagChange(event.target.value)), 21 | }) 22 | 23 | const ArgTagSelector = (props) => { 24 | let { argTag, onTagChange } = props 25 | 26 | return ( 27 | 28 | Argument Tag 29 | 39 | 40 | ) 41 | } 42 | 43 | ArgTagSelector.propTypes = { 44 | argTag: propTypes.string, 45 | onTagChange: propTypes.func 46 | } 47 | 48 | export default connect( mapStateToProps, mapDispatchToProps )(ArgTagSelector) -------------------------------------------------------------------------------- /src/js/components/arguments/ArgumentField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SortableElement } from 'react-sortable-hoc' 3 | 4 | import IconButton from 'material-ui/IconButton' 5 | import Icon from 'material-ui/Icon' 6 | import TextField from 'material-ui/TextField' 7 | import Card from 'material-ui/Card' 8 | import DragHandle from '../DragHandle' 9 | 10 | import '../../../styles/list-field.css' 11 | 12 | const ArgumentField = SortableElement( (props) => { 13 | let { id, name, onChange, onOpen } = props 14 | 15 | const onClick = (event) => { 16 | event.stopPropagation() 17 | } 18 | 19 | const onFieldChange = (event) => { 20 | let newArg = event.target.value 21 | let key = event.target.id 22 | onChange(id, key, newArg) 23 | } 24 | 25 | const onDialogOpen = () => { 26 | onOpen(id); 27 | } 28 | 29 | return ( 30 | 31 |
32 |
33 | 34 |
35 |
36 | 44 |
45 |
46 | 47 | mode_edit 48 | 49 |
50 |
51 |
52 | ) 53 | }) 54 | 55 | export default ArgumentField -------------------------------------------------------------------------------- /src/js/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Grid from 'material-ui/Grid' 3 | 4 | import TitleBar from './TitleBar' 5 | import OutputBox from './OutputBox' 6 | import DescriptionField from './DescriptionField' 7 | import ArgumentContainer from './arguments/ArgumentContainer' 8 | import LocalVarContainer from './localvars/LocalVarContainer' 9 | import ScriptNameField from './ScriptNameField' 10 | 11 | import { MuiThemeProvider } from 'material-ui/styles' 12 | import ColourTheme from '../helpers/ColourTheme' 13 | 14 | import '../../styles/main.css' 15 | 16 | class App extends Component { 17 | 18 | constructor() { 19 | super() 20 | 21 | this.state = {} 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 |
46 |
47 |
48 | ) 49 | } 50 | } 51 | 52 | export default App -------------------------------------------------------------------------------- /src/js/components/localvars/LocalVarField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SortableElement } from 'react-sortable-hoc' 3 | 4 | import IconButton from 'material-ui/IconButton' 5 | import Icon from 'material-ui/Icon' 6 | import TextField from 'material-ui/TextField' 7 | import Card from 'material-ui/Card' 8 | 9 | import DragHandle from '../DragHandle' 10 | 11 | import '../../../styles/list-field.css' 12 | 13 | const LocalVarField = SortableElement( (props) => { 14 | let { id, name, onChange, onRemove } = props 15 | 16 | const onClick = (event) => { 17 | event.stopPropagation() 18 | } 19 | 20 | const onFieldChange = (event) => { 21 | let newArg = event.target.value 22 | let key = event.target.id 23 | onChange(id, key, newArg) 24 | } 25 | 26 | const onFieldRemove = () => { 27 | onRemove(id) 28 | } 29 | 30 | return ( 31 | 32 |
33 |
34 | 35 |
36 |
37 | 45 |
46 |
47 | 52 | delete_forever 53 | 54 |
55 |
56 |
57 | ) 58 | }) 59 | 60 | export default LocalVarField -------------------------------------------------------------------------------- /src/js/components/options/OptionsDialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import Transition from '../Transition' 5 | import LegacyToggle from './LegacyToggle' 6 | import TagMenu from './TagMenu' 7 | 8 | import Dialog from 'material-ui/Dialog/Dialog' 9 | import AppBar from 'material-ui/AppBar/AppBar' 10 | import Toolbar from 'material-ui/Toolbar/Toolbar' 11 | import Typography from 'material-ui/Typography' 12 | import IconButton from 'material-ui/IconButton' 13 | import Icon from 'material-ui/Icon' 14 | import Divider from 'material-ui/Divider/Divider' 15 | 16 | import { prefixChange } from '../../actions/options' 17 | import propTypes from 'prop-types' 18 | 19 | import '../../../styles/options.css' 20 | 21 | const mapStateToProps = (state) => ({ 22 | options: { 23 | localVarPrefix: state.options.localVarPrefix 24 | } 25 | }) 26 | 27 | const mapDispatchToProps = (dispatch) => ({ 28 | onPrefixChange: (event) => dispatch(prefixChange(event.target.id, event.target.value)) 29 | }) 30 | 31 | const OptionsDialog = (props) => { 32 | let { isOpen, onClose } = props 33 | 34 | return ( 35 | 42 | 43 | 44 | 47 | close 48 | 49 | 50 | Settings 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | OptionsDialog.propTypes = { 64 | isOpen: propTypes.bool, 65 | options: propTypes.object, 66 | onClose: propTypes.func, 67 | onLegacyChange: propTypes.func, 68 | onPrefixChange: propTypes.func 69 | } 70 | 71 | export default connect(mapStateToProps, mapDispatchToProps)(OptionsDialog) -------------------------------------------------------------------------------- /src/js/components/localvars/LocalVarSortable.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import LocalVarField from './LocalVarField' 4 | import { SortableContainer } from 'react-sortable-hoc' 5 | 6 | import { localVarChange, localVarSort, localVarRemove } from '../../actions/localVars' 7 | import PropTypes from 'prop-types' 8 | 9 | const LocalVarList = SortableContainer( (props) => { 10 | let { items, onChange, onOpen, onRemove } = props 11 | 12 | return ( 13 |
14 |
    15 | {items.map((localVar, index) => ( 16 | 24 | ))} 25 |
26 |
27 | ) 28 | }) 29 | 30 | const mapStateToProps = (state) => ({ 31 | items: state.localVars 32 | }) 33 | 34 | const mapDispatchToProps = (dispatch) => ({ 35 | onChange: (id, key, value) => dispatch(localVarChange(id, key, value)), 36 | onRemove: (id) => dispatch(localVarRemove(id)), 37 | onSortEnd: (event) => dispatch(localVarSort(event.oldIndex, event.newIndex)) 38 | }) 39 | 40 | class LocalVarSortable extends Component { 41 | 42 | constructor(props) { 43 | super(props) 44 | 45 | this.state = { 46 | dialogOpen: false, 47 | index: 0 48 | } 49 | } 50 | 51 | render() { 52 | return ( 53 |
54 | 63 |
64 | ) 65 | } 66 | } 67 | 68 | LocalVarSortable.propTypes = { 69 | onChange: PropTypes.func, 70 | onRemove: PropTypes.func, 71 | items: PropTypes.array, 72 | onSortEnd: PropTypes.func 73 | } 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(LocalVarSortable) -------------------------------------------------------------------------------- /src/js/components/CopyScriptButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import IconButton from 'material-ui/IconButton' 3 | import Icon from 'material-ui/Icon' 4 | import Snackbar from 'material-ui/Snackbar' 5 | 6 | class CopyScriptButton extends Component { 7 | 8 | constructor() { 9 | super() 10 | 11 | this.state = { 12 | snackbarOpen: false 13 | } 14 | 15 | this.copyScript = this.copyScript.bind(this) 16 | this.handleClose = this.handleClose.bind(this) 17 | } 18 | 19 | copyScript() { 20 | // Copies script text to a temporary textarea so that it can be 21 | // copied to the clipboard 22 | let scriptText = document.getElementById('generated-script').innerHTML 23 | let tempBox = document.createElement('textarea') 24 | 25 | document.body.appendChild(tempBox) 26 | tempBox.setAttribute('id', 'tempBox') 27 | scriptText = scriptText.replace(/ /g, ' ') 28 | document.getElementById('tempBox').value = scriptText 29 | tempBox.select() 30 | document.execCommand('Copy') 31 | document.body.removeChild(tempBox) 32 | this.setState({ snackbarOpen: true }) 33 | } 34 | 35 | handleClose() { 36 | this.setState({ snackbarOpen: false }) 37 | } 38 | 39 | render() { 40 | return ( 41 |
42 | 57 | close 58 | 59 | ]} 60 | /> 61 | 62 | 67 | content_copy 68 | 69 |
70 | ) 71 | } 72 | } 73 | 74 | export default CopyScriptButton -------------------------------------------------------------------------------- /src/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as event from '../helpers/eventTypes' 2 | import generateScript from '../helpers/generateScript' 3 | import { arrayMove } from 'react-sortable-hoc' 4 | import { initialState } from '../helpers/initialState' 5 | 6 | const rootReducer = (state = initialState, action) => { 7 | let newState = { 8 | ...state, 9 | options: { ...state.options }, 10 | args: [ ...state.args ], 11 | localVars: [ ...state.localVars ] 12 | } 13 | 14 | switch (action.type) { 15 | 16 | case event.NAME_CHANGE: { 17 | newState.scriptName = action.payload.value 18 | break 19 | } 20 | 21 | case event.DESC_CHANGE: { 22 | newState.description = action.payload.value 23 | break 24 | } 25 | 26 | case event.ARG_CHANGE: { 27 | newState.args[action.payload.id][action.payload.key] = action.payload.value 28 | break 29 | } 30 | 31 | case event.ARG_SORT: { 32 | newState.args = arrayMove(newState.args, action.payload.oldIndex, action.payload.newIndex) 33 | break 34 | } 35 | 36 | case event.ARG_ADD: { 37 | newState.args.push({name: '', type: '', description: ''}) 38 | break 39 | } 40 | 41 | case event.ARG_REMOVE: { 42 | if (newState.args.length > 0) { 43 | newState.args.splice(action.payload.id, 1) 44 | } 45 | break 46 | } 47 | 48 | case event.LVAR_CHANGE: { 49 | newState.localVars[action.payload.id][action.payload.key] = action.payload.value 50 | break 51 | } 52 | 53 | case event.LVAR_SORT: { 54 | newState.localVars = arrayMove(newState.localVars, action.payload.oldIndex, action.payload.newIndex) 55 | break 56 | } 57 | 58 | case event.LVAR_ADD: { 59 | newState.localVars.push({name: '', type: '', description: ''}) 60 | break 61 | } 62 | 63 | case event.LVAR_REMOVE: { 64 | if (newState.localVars.length > 0) { 65 | newState.localVars.splice(action.payload.id, 1) 66 | } 67 | break 68 | } 69 | 70 | case event.OPT_LEGACY: { 71 | newState.options.legacyMode = !newState.options.legacyMode 72 | break 73 | } 74 | 75 | case event.OPT_CHANGE: { 76 | newState.options[action.payload.id] = action.payload.value 77 | break 78 | } 79 | } 80 | 81 | newState.outputValue = generateScript(newState) 82 | return newState 83 | } 84 | 85 | export default rootReducer -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gml-script-wizard", 3 | "version": "0.9.3", 4 | "description": "GML Script Wizard", 5 | "main": "index.js", 6 | "repository": "https://github.com/mstop4/gml-script-wizard.git", 7 | "author": "mstop4 ", 8 | "license": "BSD-2-Clause", 9 | "scripts": { 10 | "start": "webpack-dev-server --port 8081 --host 0.0.0.0 --hot --open --config webpack.dev.js", 11 | "build": "./node_modules/.bin/webpack --config webpack.prod.js && cp -r dist/js electron && cp -r dist/index.html electron", 12 | "lint": "esw webpack.config.* src tools --color", 13 | "lint:watch": "yarn lint --watch", 14 | "electron": "npx electron electron/main.js", 15 | "build:electron-win": "electron-packager electron GMLScriptWizard --platform=win32 --arch=x64 --overwrite --no-tmpdir", 16 | "build:electron-mac": "electron-packager electron GMLScriptWizard --platform=darwin --arch=x64 --overwrite --no-tmpdir", 17 | "build:electron-linux": "electron-packager electron GMLScriptWizard --platform=linux --arch=x64 --overwrite --no-tmpdir", 18 | "gh-pages": "yarn build && npx push-dir --dir=dist --branch=gh-pages --cleanup" 19 | }, 20 | "devDependencies": { 21 | "babel-core": "^6.26.0", 22 | "babel-loader": "^7.1.2", 23 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 24 | "babel-preset-env": "^1.6.1", 25 | "babel-preset-react": "^6.24.1", 26 | "clean-webpack-plugin": "^0.1.17", 27 | "css-loader": "^0.28.9", 28 | "electron": "^4.0.4", 29 | "electron-packager": "^13.0.1", 30 | "eslint": "^4.18.0", 31 | "eslint-plugin-react": "^7.6.1", 32 | "eslint-watch": "^3.1.3", 33 | "html-loader": "^0.5.5", 34 | "html-webpack-plugin": "^3.0.6", 35 | "inline-source-map": "^0.6.2", 36 | "path": "^0.12.7", 37 | "style-loader": "^0.20.2", 38 | "uglifyjs-webpack-plugin": "^1.1.6", 39 | "webpack": "^4.28.3", 40 | "webpack-cli": "^3.2.0", 41 | "webpack-dev-server": "^3.1.14", 42 | "webpack-merge": "^4.1.1" 43 | }, 44 | "dependencies": { 45 | "autosuggest-highlight": "^3.1.1", 46 | "babel-polyfill": "^6.26.0", 47 | "lodash": "^4.17.13", 48 | "material-ui": "^1.0.0-beta.36", 49 | "prop-types": "^15.6.0", 50 | "react": "^16.2.0", 51 | "react-autosuggest": "^9.3.3", 52 | "react-custom-scrollbars": "^4.2.1", 53 | "react-dom": "^16.2.0", 54 | "react-redux": "^5.0.7", 55 | "react-sortable-hoc": "^0.6.8", 56 | "redux": "^3.7.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/js/components/arguments/ArgumentSortable.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import ArgumentField from './ArgumentField' 4 | import ArgumentDialog from './ArgumentDialog' 5 | import { SortableContainer } from 'react-sortable-hoc' 6 | 7 | import { argumentChange, argumentSort, argumentRemove } from '../../actions/arguments' 8 | import PropTypes from 'prop-types' 9 | 10 | const ArgumentList = SortableContainer( (props) => { 11 | let { items, onChange, onOpen, onRemove } = props 12 | 13 | return ( 14 |
15 |
    16 | {items.map((arg, index) => ( 17 | 25 | ))} 26 |
27 |
28 | ) 29 | }) 30 | 31 | const mapStateToProps = (state) => ({ 32 | items: state.args 33 | }) 34 | 35 | const mapDispatchToProps = (dispatch) => ({ 36 | onChange: (id, key, value) => dispatch(argumentChange(id, key, value)), 37 | onRemove: (id) => dispatch(argumentRemove(id)), 38 | onSortEnd: (event) => dispatch(argumentSort(event.oldIndex, event.newIndex)) 39 | }) 40 | 41 | class ArgumentSortable extends Component { 42 | 43 | constructor(props) { 44 | super(props) 45 | 46 | this.state = { 47 | dialogOpen: false, 48 | index: 0 49 | } 50 | 51 | this.handleDialogOpen = this.handleDialogOpen.bind(this) 52 | this.handleDialogClose = this.handleDialogClose.bind(this) 53 | this.onFieldRemove = this.onFieldRemove.bind(this) 54 | this.onFieldChange = this.onFieldChange.bind(this) 55 | } 56 | 57 | handleDialogOpen(id) { 58 | this.setState({ dialogOpen: true, index: id }) 59 | } 60 | 61 | handleDialogClose() { 62 | this.setState({ dialogOpen: false }) 63 | } 64 | 65 | onFieldChange(event) { 66 | let newArg = event.target.value 67 | let key = event.target.id 68 | this.props.onChange(this.state.index, key, newArg) 69 | } 70 | 71 | onFieldRemove() { 72 | this.props.onRemove(this.state.index) 73 | this.handleDialogClose() 74 | } 75 | 76 | render() { 77 | 78 | let dialogArg 79 | 80 | if (this.props.items.length > 0) { 81 | dialogArg = this.props.items[this.state.index] 82 | } else { 83 | dialogArg = {name: '', type: '', description: ''} 84 | } 85 | 86 | return ( 87 |
88 | 95 | 104 |
105 | ) 106 | } 107 | } 108 | 109 | ArgumentSortable.propTypes = { 110 | onChange: PropTypes.func, 111 | onRemove: PropTypes.func, 112 | items: PropTypes.array, 113 | onSortEnd: PropTypes.func 114 | } 115 | 116 | export default connect(mapStateToProps, mapDispatchToProps)(ArgumentSortable) -------------------------------------------------------------------------------- /src/js/components/help/HelpDialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Dialog, {DialogTitle, DialogContent, DialogActions} from 'material-ui/Dialog' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | import Transition from '../Transition' 6 | import Typography from 'material-ui/Typography/Typography' 7 | import Divider from 'material-ui/Divider' 8 | import PropTypes from 'prop-types' 9 | 10 | import '../../../styles/help.css' 11 | 12 | const HelpDialog = (props) => { 13 | let { isOpen, onClose } = props 14 | 15 | return ( 16 | 23 | Help 24 | 25 | About 26 | 27 | GML Script Wizard is a tool that automatically generates a boilerplate header you can 28 | use as your script's starting point. 29 | 30 | 31 | Usage 32 | Adding Items 33 | 34 | Click on the respective add_circle at the top of each list to add a new item, then 35 | click on the text field to give the item a name. 36 | 37 | 38 | Adding Details (Arguments only) 39 | 40 | Click on mode_edit to open up the argument details dialog. Here you can specify a type and 41 | add a description for your argument. NOTE: These will only be visible in GMS2 if you 42 | have turned this feature in the settings. 43 | 44 | 45 | Removing Items 46 | 47 | For arguments, click on mode_edit to open up the argument details dialog, then click on delete_forever to remove.
48 | For local variables, click on delete_forever to remove. 49 |
50 | 51 | Reordering Items 52 | 53 | Click and drag from an item's reorder to move and reorder it. Release to put it in its new position. 54 | 55 | 56 | Copying the Script 57 | 58 | Click on content_copy to copy the generated script to the clipboard. The script can then be pasted into 59 | the script editor in GMS2. 60 | 61 | 62 | 63 | 64 | 65 | Created by:  66 | M.S.T.O.P.  67 | (@QuadolorGames)
68 | View source code on GitHub 69 |
70 | 71 |
72 | 73 | 78 | done 79 | 80 | 81 |
82 | ) 83 | } 84 | 85 | HelpDialog.propTypes = { 86 | isOpen: PropTypes.bool, 87 | onClose: PropTypes.func 88 | } 89 | 90 | export default HelpDialog -------------------------------------------------------------------------------- /src/js/helpers/generateScript.js: -------------------------------------------------------------------------------- 1 | const generateScript = ({ scriptName, description, args, localVars, options }) => { 2 | 3 | let newOutput = '' 4 | 5 | let headFunction = '' 6 | let headDescription = '' 7 | let headArgumentTypes = [] 8 | let headArgumentNames = [] 9 | let headArgumentDescs = [] 10 | 11 | let declArguments = [] 12 | let declLocals = '' 13 | 14 | // Determine how much padding is need between tags and description based on which 15 | // tags are used 16 | let funcTagLength = scriptName !== '' ? options.functionTag.length : 0 17 | let descTagLength = description !== '' ? options.descriptionTag.length : 0 18 | let argTagLength = args.length > 0 ? options.argumentTag.length : 0 19 | 20 | let tagPadLength = Math.max(funcTagLength, descTagLength, argTagLength) + 2 21 | 22 | if (options.legacyMode) { 23 | headFunction = '/// ' 24 | headDescription = `//\xa0\xa0${description}` 25 | } else { 26 | headFunction = `/// ${options.functionTag}${'\xa0'.repeat(Math.max(2,tagPadLength-funcTagLength))}` 27 | headDescription = `/// ${options.descriptionTag}${'\xa0'.repeat(Math.max(2,tagPadLength-descTagLength))}${description}` 28 | } 29 | 30 | // Create script JSDoc header 31 | 32 | // Script name 33 | if (scriptName !== '') { 34 | let hasArgs = false 35 | headFunction += `${scriptName}(` 36 | 37 | for (let i = 0; i < args.length; i++) { 38 | if (args[i].name !== '') { 39 | headFunction += `${args[i].name}, ` 40 | hasArgs = true 41 | } 42 | } 43 | 44 | // Strip trailing comma 45 | if (hasArgs) { 46 | headFunction = headFunction.slice(0,headFunction.length-2) 47 | } 48 | 49 | headFunction += ')' 50 | } 51 | 52 | // Arguments 53 | let currentArgIndex = 0 54 | 55 | // find the length of longest type and argument names for spacing purposes 56 | let typeMaxLength = -3 57 | let nameMaxLength = 0 58 | 59 | for (let i = 0; i < args.length; i++) { 60 | if (args[i].type && args[i].type.length > typeMaxLength) { 61 | typeMaxLength = args[i].type.length 62 | } 63 | 64 | if (args[i].name && args[i].name.length > nameMaxLength) { 65 | nameMaxLength = args[i].name.length 66 | } 67 | } 68 | 69 | for (let i = 0; i < args.length; i++) { 70 | 71 | // Build JSDoc line 72 | if (args[i].name !== '') { 73 | 74 | if (!options.legacyMode) { 75 | if (args[i].type !== '') { 76 | let spaceBufferSize = Math.max(0,typeMaxLength-args[i].type.length) 77 | headArgumentTypes.push(` {${args[i].type}}${'\xa0'.repeat(spaceBufferSize)}`) 78 | } else { 79 | headArgumentTypes.push('\xa0'.repeat(typeMaxLength+3)) 80 | } 81 | 82 | if (args[i].name !== '') { 83 | let spaceBufferSize = Math.max(0,nameMaxLength-args[i].name.length) 84 | headArgumentNames.push(`${args[i].name}${'\xa0'.repeat(spaceBufferSize)}`) 85 | } else { 86 | headArgumentNames.push('\xa0'.repeat(nameMaxLength)) 87 | } 88 | 89 | if (args[i].description !== '') { 90 | headArgumentDescs.push(` ${args[i].description}`) 91 | } else { 92 | headArgumentDescs.push('') 93 | } 94 | } 95 | 96 | // Build declaration line 97 | let declArg = `var ${options.localVarPrefix}${args[i].name} = argument[${currentArgIndex}];` 98 | const declArgPad = nameMaxLength - args[i].name.length + 2 99 | 100 | if (options.legacyMode && args[i].description) { 101 | declArg += `${'\xa0'.repeat(declArgPad)}// ${args[i].description}` 102 | } 103 | 104 | declArguments.push(`${declArg}\n`) 105 | currentArgIndex++ 106 | } 107 | } 108 | 109 | // Additional local variables 110 | for (let i = 0; i < localVars.length; i++) { 111 | if (localVars[i].name !== '') { 112 | declLocals += `${options.localVarPrefix}${localVars[i].name}, ` 113 | } 114 | } 115 | 116 | // Strip trailing comma 117 | if (localVars.length > 0) { 118 | declLocals = declLocals.slice(0,declLocals.length-2) 119 | } 120 | 121 | // Build Script 122 | // ------------ 123 | 124 | let firstLine = true 125 | 126 | // @function 127 | if (scriptName !== '') { 128 | newOutput += `${headFunction}\n` 129 | firstLine = false 130 | } 131 | 132 | // @description 133 | if (description !== '') { 134 | newOutput += `${headDescription}\n` 135 | firstLine = false 136 | } 137 | 138 | // @param 139 | if (!options.legacyMode) { 140 | for (let i = 0; i < headArgumentNames.length; i++) { 141 | newOutput += `/// ${options.argumentTag}${'\xa0'.repeat(tagPadLength-argTagLength-1)}${headArgumentTypes[i]} ${headArgumentNames[i]} ${headArgumentDescs[i]}\n` 142 | } 143 | 144 | if (headArgumentNames.length > 0) { 145 | firstLine = false 146 | } 147 | } 148 | 149 | if (declArguments.length > 0 && !firstLine) { 150 | newOutput += '\n' 151 | } 152 | 153 | // argument declarations 154 | for (let i = 0; i < declArguments.length; i++) { 155 | newOutput += declArguments[i] 156 | firstLine = false 157 | } 158 | 159 | // local var declarations 160 | if (declLocals !== '') { 161 | if (!firstLine) { 162 | newOutput += '\n' 163 | } 164 | 165 | newOutput += `var ${declLocals};\n` 166 | firstLine = false 167 | } 168 | 169 | if (newOutput !== '' && !firstLine) { 170 | newOutput += '\n' 171 | } 172 | 173 | newOutput += '/* Script body goes here */' 174 | 175 | return newOutput 176 | } 177 | 178 | export default generateScript -------------------------------------------------------------------------------- /src/js/components/arguments/ArgumentDialog.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Dialog, {DialogTitle, DialogContent, DialogActions} from 'material-ui/Dialog' 3 | import IconButton from 'material-ui/IconButton' 4 | import Icon from 'material-ui/Icon' 5 | import TextField from 'material-ui/TextField' 6 | import { MenuItem } from 'material-ui/Menu' 7 | import Paper from 'material-ui/Paper' 8 | import Transition from '../Transition' 9 | import PropTypes from 'prop-types' 10 | 11 | import Autosuggest from 'react-autosuggest' 12 | import match from 'autosuggest-highlight/match' 13 | import parse from 'autosuggest-highlight/parse' 14 | 15 | const suggestions = [ 16 | { label: 'real' }, 17 | { label: 'string' }, 18 | { label: 'boolean' }, 19 | { label: 'array' }, 20 | { label: 'pointer' }, 21 | { label: 'enum' }, 22 | { label: 'matrix' }, 23 | { label: 'list' }, 24 | { label: 'queue' }, 25 | { label: 'grid' }, 26 | { label: 'priority' }, 27 | { label: 'stack' }, 28 | { label: 'map' }, 29 | { label: 'surface' }, 30 | { label: 'buffer' }, 31 | { label: 'object' } 32 | ] 33 | 34 | function renderInput(inputProps) { 35 | const { ref, ...other} = inputProps 36 | 37 | return ( 38 | 46 | ) 47 | } 48 | 49 | function renderSuggestion(suggestion, { query, isHighlighted }) { 50 | const matches = match(suggestion.label, query) 51 | const parts = parse(suggestion.label, matches) 52 | 53 | return ( 54 | 55 |
56 | {parts.map((part, index) => { 57 | return part.highlight ? ( 58 | 59 | {part.text} 60 | 61 | ) : ( 62 | 63 | {part.text} 64 | 65 | ) 66 | })} 67 |
68 |
69 | ) 70 | } 71 | 72 | function renderSuggestionsContainer(options) { 73 | const { containerProps, children } = options 74 | 75 | return ( 76 | 77 | {children} 78 | 79 | ) 80 | } 81 | 82 | function getSuggestionValue(suggestion) { 83 | return suggestion.label 84 | } 85 | 86 | function getSuggestions(value) { 87 | const inputValue = value.trim().toLowerCase() 88 | const inputLength = inputValue.length 89 | let count = 0 90 | 91 | return inputLength === 0 92 | ? [] 93 | : suggestions.filter(suggestion => { 94 | const keep = 95 | count < 5 && suggestion.label.toLowerCase().slice(0, inputLength) === inputValue 96 | 97 | if (keep) { 98 | count++ 99 | } 100 | 101 | return keep 102 | }) 103 | } 104 | 105 | class ArgumentDialog extends Component { 106 | 107 | constructor(props) { 108 | super(props) 109 | 110 | this.state = { 111 | value: '', 112 | suggestions: [] 113 | } 114 | 115 | this.handleSuggestionsFetchRequested = this.handleSuggestionsFetchRequested.bind(this) 116 | this.handleSuggestionsClearRequested = this.handleSuggestionsClearRequested.bind(this) 117 | this.handleGetSuggestion = this.handleGetSuggestion.bind(this) 118 | this.handleSumbit = this.handleSubmit.bind(this) 119 | } 120 | 121 | handleSuggestionsFetchRequested({ value }) { 122 | this.setState({ 123 | suggestions: getSuggestions(value) 124 | }) 125 | } 126 | 127 | handleSuggestionsClearRequested() { 128 | this.setState({ 129 | suggestions: [] 130 | }) 131 | } 132 | 133 | handleGetSuggestion(suggestion) { 134 | let newValue = getSuggestionValue(suggestion) 135 | this.props.onChange({ 136 | target: { 137 | value: newValue, 138 | id: 'type' 139 | }}) 140 | return newValue 141 | } 142 | 143 | handleSubmit(event) { 144 | event.preventDefault() 145 | } 146 | 147 | render() { 148 | 149 | // Check if argInfo exists before displaying its info in the dialog 150 | let displayInfo = this.props.argInfo ? this.props.argInfo : {name: '', type: '', description: ''} 151 | 152 | return ( 153 | 160 | {displayInfo.name} Details 161 | 162 |
163 | 178 | 179 | 186 |
187 | 188 | 193 | delete_forever 194 | 195 | 200 | done 201 | 202 | 203 |
204 | ) 205 | } 206 | } 207 | 208 | ArgumentDialog.propTypes = { 209 | onChange: PropTypes.func, 210 | argInfo: PropTypes.object, 211 | isOpen: PropTypes.bool, 212 | onClose: PropTypes.func, 213 | onRemove: PropTypes.func 214 | } 215 | 216 | export default ArgumentDialog --------------------------------------------------------------------------------