├── .babelrc ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── build └── icons │ ├── 256x256.png │ ├── icon.icns │ ├── icon.ico │ └── verd.png ├── package.json ├── src ├── index.ejs ├── main │ ├── index.dev.js │ └── index.js └── renderer │ ├── App.vue │ ├── assets │ ├── .gitkeep │ └── logo.png │ ├── components │ └── editor │ │ ├── CanvasMain.vue │ │ ├── CanvasMenu.vue │ │ ├── CanvasSvg.vue │ │ ├── Grid.vue │ │ ├── Modal.vue │ │ └── QuickMenu.vue │ ├── css │ └── NotoSans.css │ ├── js │ ├── JSLog.js │ ├── editor │ │ ├── Data.js │ │ ├── ERD.js │ │ ├── Event.js │ │ ├── File.js │ │ ├── IndexedDB.js │ │ ├── SQL.js │ │ ├── UndoRedo.js │ │ ├── grid │ │ │ ├── column.js │ │ │ └── domain.js │ │ ├── img │ │ │ └── relationship.js │ │ ├── sql │ │ │ ├── MSSQL.js │ │ │ ├── MariaDB.js │ │ │ ├── MySQL.js │ │ │ ├── Oracle.js │ │ │ └── PostgreSQL.js │ │ └── util.js │ └── fontawesome.js │ ├── main.js │ ├── router │ └── index.js │ ├── store │ ├── editor │ │ ├── dataType.js │ │ ├── dataType │ │ │ ├── MSSQL.js │ │ │ ├── MariaDB.js │ │ │ ├── MySQL.js │ │ │ ├── Oracle.js │ │ │ └── PostgreSQL.js │ │ ├── erd.js │ │ ├── model.js │ │ ├── mutationsColumn.js │ │ ├── mutationsDomain.js │ │ ├── mutationsLine.js │ │ ├── mutationsMemo.js │ │ ├── mutationsTable.js │ │ └── table.js │ ├── index.js │ └── modules │ │ ├── Counter.js │ │ └── index.js │ └── views │ └── ERD.vue ├── static └── .gitkeep └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | ["env", { 7 | "targets": { "node": 7 } 8 | }], 9 | "stage-0" 10 | ] 11 | }, 12 | "renderer": { 13 | "presets": [ 14 | ["env", { 15 | "modules": false 16 | }], 17 | "stage-0" 18 | ] 19 | }, 20 | "web": { 21 | "presets": [ 22 | ["env", { 23 | "modules": false 24 | }], 25 | "stage-0" 26 | ] 27 | } 28 | }, 29 | "plugins": ["transform-runtime"] 30 | } 31 | -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | const webConfig = require('./webpack.web.config') 16 | 17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 20 | const isCI = process.env.CI || false 21 | 22 | if (process.env.BUILD_TARGET === 'clean') clean() 23 | else if (process.env.BUILD_TARGET === 'web') web() 24 | else build() 25 | 26 | function clean () { 27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 28 | console.log(`\n${doneLog}\n`) 29 | process.exit() 30 | } 31 | 32 | function build () { 33 | greeting() 34 | 35 | del.sync(['dist/electron/*', '!.gitkeep']) 36 | 37 | const tasks = ['main', 'renderer'] 38 | const m = new Multispinner(tasks, { 39 | preText: 'building', 40 | postText: 'process' 41 | }) 42 | 43 | let results = '' 44 | 45 | m.on('success', () => { 46 | process.stdout.write('\x1B[2J\x1B[0f') 47 | console.log(`\n\n${results}`) 48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 49 | process.exit() 50 | }) 51 | 52 | pack(mainConfig).then(result => { 53 | results += result + '\n\n' 54 | m.success('main') 55 | }).catch(err => { 56 | m.error('main') 57 | console.log(`\n ${errorLog}failed to build main process`) 58 | console.error(`\n${err}\n`) 59 | process.exit(1) 60 | }) 61 | 62 | pack(rendererConfig).then(result => { 63 | results += result + '\n\n' 64 | m.success('renderer') 65 | }).catch(err => { 66 | m.error('renderer') 67 | console.log(`\n ${errorLog}failed to build renderer process`) 68 | console.error(`\n${err}\n`) 69 | process.exit(1) 70 | }) 71 | } 72 | 73 | function pack (config) { 74 | return new Promise((resolve, reject) => { 75 | config.mode = 'production' 76 | webpack(config, (err, stats) => { 77 | if (err) reject(err.stack || err) 78 | else if (stats.hasErrors()) { 79 | let err = '' 80 | 81 | stats.toString({ 82 | chunks: false, 83 | colors: true 84 | }) 85 | .split(/\r?\n/) 86 | .forEach(line => { 87 | err += ` ${line}\n` 88 | }) 89 | 90 | reject(err) 91 | } else { 92 | resolve(stats.toString({ 93 | chunks: false, 94 | colors: true 95 | })) 96 | } 97 | }) 98 | }) 99 | } 100 | 101 | function web () { 102 | del.sync(['dist/web/*', '!.gitkeep']) 103 | webConfig.mode = 'production' 104 | webpack(webConfig, (err, stats) => { 105 | if (err || stats.hasErrors()) console.log(err) 106 | 107 | console.log(stats.toString({ 108 | chunks: false, 109 | colors: true 110 | })) 111 | 112 | process.exit() 113 | }) 114 | } 115 | 116 | function greeting () { 117 | const cols = process.stdout.columns 118 | let text = '' 119 | 120 | if (cols > 85) text = 'lets-build' 121 | else if (cols > 60) text = 'lets-|build' 122 | else text = false 123 | 124 | if (text && !isCI) { 125 | say(text, { 126 | colors: ['yellow'], 127 | font: 'simple3d', 128 | space: false 129 | }) 130 | } else console.log(chalk.yellow.bold('\n lets-build')) 131 | console.log() 132 | } -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | * 7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats (proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer () { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | rendererConfig.mode = 'development' 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.hooks.compilation.tap('compilation', compilation => { 52 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.hooks.done.tap('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | before (app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain () { 81 | return new Promise((resolve, reject) => { 82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) 83 | mainConfig.mode = 'development' 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron () { 117 | var args = [ 118 | '--inspect=5858', 119 | path.join(__dirname, '../dist/electron/main.js') 120 | ] 121 | 122 | // detect yarn or npm and process commandline args accordingly 123 | if (process.env.npm_execpath.endsWith('yarn.js')) { 124 | args = args.concat(process.argv.slice(3)) 125 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { 126 | args = args.concat(process.argv.slice(2)) 127 | } 128 | 129 | electronProcess = spawn(electron, args) 130 | 131 | electronProcess.stdout.on('data', data => { 132 | electronLog(data, 'blue') 133 | }) 134 | electronProcess.stderr.on('data', data => { 135 | electronLog(data, 'red') 136 | }) 137 | 138 | electronProcess.on('close', () => { 139 | if (!manualRestart) process.exit() 140 | }) 141 | } 142 | 143 | function electronLog (data, color) { 144 | let log = '' 145 | data = data.toString().split(/\r?\n/) 146 | data.forEach(line => { 147 | log += ` ${line}\n` 148 | }) 149 | if (/[0-9A-z]+/.test(log)) { 150 | console.log( 151 | chalk[color].bold('┏ Electron -------------------') + 152 | '\n\n' + 153 | log + 154 | chalk[color].bold('┗ ----------------------------') + 155 | '\n' 156 | ) 157 | } 158 | } 159 | 160 | function greeting () { 161 | const cols = process.stdout.columns 162 | let text = '' 163 | 164 | if (cols > 104) text = 'electron-vue' 165 | else if (cols > 76) text = 'electron-|vue' 166 | else text = false 167 | 168 | if (text) { 169 | say(text, { 170 | colors: ['yellow'], 171 | font: 'simple3d', 172 | space: false 173 | }) 174 | } else console.log(chalk.yellow.bold('\n electron-vue')) 175 | console.log(chalk.blue(' getting ready...') + '\n') 176 | } 177 | 178 | function init () { 179 | greeting() 180 | 181 | Promise.all([startRenderer(), startMain()]) 182 | .then(() => { 183 | startElectron() 184 | }) 185 | .catch(err => { 186 | console.error(err) 187 | }) 188 | } 189 | 190 | init() 191 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js') 14 | }, 15 | externals: [ 16 | ...Object.keys(dependencies || {}) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js)$/, 22 | enforce: 'pre', 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'eslint-loader', 26 | options: { 27 | formatter: require('eslint-friendly-formatter') 28 | } 29 | } 30 | }, 31 | { 32 | test: /\.js$/, 33 | use: 'babel-loader', 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.node$/, 38 | use: 'node-loader' 39 | } 40 | ] 41 | }, 42 | node: { 43 | __dirname: process.env.NODE_ENV !== 'production', 44 | __filename: process.env.NODE_ENV !== 'production' 45 | }, 46 | output: { 47 | filename: '[name].js', 48 | libraryTarget: 'commonjs2', 49 | path: path.join(__dirname, '../dist/electron') 50 | }, 51 | plugins: [ 52 | new webpack.NoEmitOnErrorsPlugin() 53 | ], 54 | resolve: { 55 | extensions: ['.js', '.json', '.node'] 56 | }, 57 | target: 'electron-main' 58 | } 59 | 60 | /** 61 | * Adjust mainConfig for development settings 62 | */ 63 | if (process.env.NODE_ENV !== 'production') { 64 | mainConfig.plugins.push( 65 | new webpack.DefinePlugin({ 66 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 67 | }) 68 | ) 69 | } 70 | 71 | /** 72 | * Adjust mainConfig for production settings 73 | */ 74 | if (process.env.NODE_ENV === 'production') { 75 | mainConfig.plugins.push( 76 | new BabiliWebpackPlugin(), 77 | new webpack.DefinePlugin({ 78 | 'process.env.NODE_ENV': '"production"' 79 | }) 80 | ) 81 | } 82 | 83 | module.exports = mainConfig 84 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | const { VueLoaderPlugin } = require('vue-loader') 14 | 15 | /** 16 | * List of node_modules to include in webpack bundle 17 | * 18 | * Required for specific packages like Vue UI libraries 19 | * that provide pure *.vue files that need compiling 20 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 21 | */ 22 | let whiteListedModules = ['vue'] 23 | 24 | let rendererConfig = { 25 | devtool: '#cheap-module-eval-source-map', 26 | entry: { 27 | renderer: path.join(__dirname, '../src/renderer/main.js') 28 | }, 29 | externals: [ 30 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 31 | ], 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.(js|vue)$/, 36 | enforce: 'pre', 37 | exclude: /node_modules/, 38 | use: { 39 | loader: 'eslint-loader', 40 | options: { 41 | formatter: require('eslint-friendly-formatter') 42 | } 43 | } 44 | }, 45 | { 46 | test: /\.pug$/, 47 | loader: 'pug-plain-loader' 48 | }, 49 | { 50 | test: /\.scss$/, 51 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 52 | }, 53 | { 54 | test: /\.sass$/, 55 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 56 | }, 57 | { 58 | test: /\.less$/, 59 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 60 | }, 61 | { 62 | test: /\.css$/, 63 | use: ['vue-style-loader', 'css-loader'] 64 | }, 65 | { 66 | test: /\.html$/, 67 | use: 'vue-html-loader' 68 | }, 69 | { 70 | test: /\.js$/, 71 | use: 'babel-loader', 72 | exclude: /node_modules/ 73 | }, 74 | { 75 | test: /\.node$/, 76 | use: 'node-loader' 77 | }, 78 | { 79 | test: /\.vue$/, 80 | use: { 81 | loader: 'vue-loader', 82 | options: { 83 | extractCSS: process.env.NODE_ENV === 'production', 84 | loaders: { 85 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 86 | scss: 'vue-style-loader!css-loader!sass-loader', 87 | less: 'vue-style-loader!css-loader!less-loader' 88 | } 89 | } 90 | } 91 | }, 92 | { 93 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 94 | use: { 95 | loader: 'url-loader', 96 | query: { 97 | limit: 10000, 98 | name: 'imgs/[name]--[folder].[ext]' 99 | } 100 | } 101 | }, 102 | { 103 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 104 | loader: 'url-loader', 105 | options: { 106 | limit: 10000, 107 | name: 'media/[name]--[folder].[ext]' 108 | } 109 | }, 110 | { 111 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 112 | use: { 113 | loader: 'url-loader', 114 | query: { 115 | limit: 10000, 116 | name: 'fonts/[name]--[folder].[ext]' 117 | } 118 | } 119 | } 120 | ] 121 | }, 122 | node: { 123 | __dirname: process.env.NODE_ENV !== 'production', 124 | __filename: process.env.NODE_ENV !== 'production' 125 | }, 126 | plugins: [ 127 | new VueLoaderPlugin(), 128 | new MiniCssExtractPlugin({filename: 'styles.css'}), 129 | new HtmlWebpackPlugin({ 130 | filename: 'index.html', 131 | template: path.resolve(__dirname, '../src/index.ejs'), 132 | minify: { 133 | collapseWhitespace: true, 134 | removeAttributeQuotes: true, 135 | removeComments: true 136 | }, 137 | nodeModules: process.env.NODE_ENV !== 'production' 138 | ? path.resolve(__dirname, '../node_modules') 139 | : false 140 | }), 141 | new webpack.HotModuleReplacementPlugin(), 142 | new webpack.NoEmitOnErrorsPlugin() 143 | ], 144 | output: { 145 | filename: '[name].js', 146 | libraryTarget: 'commonjs2', 147 | path: path.join(__dirname, '../dist/electron') 148 | }, 149 | resolve: { 150 | alias: { 151 | '@': path.join(__dirname, '../src/renderer'), 152 | 'vue$': 'vue/dist/vue.esm.js' 153 | }, 154 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 155 | }, 156 | target: 'electron-renderer' 157 | } 158 | 159 | /** 160 | * Adjust rendererConfig for development settings 161 | */ 162 | if (process.env.NODE_ENV !== 'production') { 163 | rendererConfig.plugins.push( 164 | new webpack.DefinePlugin({ 165 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 166 | }) 167 | ) 168 | } 169 | 170 | /** 171 | * Adjust rendererConfig for production settings 172 | */ 173 | if (process.env.NODE_ENV === 'production') { 174 | rendererConfig.devtool = '' 175 | 176 | rendererConfig.plugins.push( 177 | new BabiliWebpackPlugin(), 178 | new CopyWebpackPlugin([ 179 | { 180 | from: path.join(__dirname, '../static'), 181 | to: path.join(__dirname, '../dist/electron/static'), 182 | ignore: ['.*'] 183 | } 184 | ]), 185 | new webpack.DefinePlugin({ 186 | 'process.env.NODE_ENV': '"production"' 187 | }), 188 | new webpack.LoaderOptionsPlugin({ 189 | minimize: true 190 | }) 191 | ) 192 | } 193 | 194 | module.exports = rendererConfig 195 | -------------------------------------------------------------------------------- /.electron-vue/webpack.web.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'web' 4 | 5 | const path = require('path') 6 | const webpack = require('webpack') 7 | 8 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | const { VueLoaderPlugin } = require('vue-loader') 13 | 14 | let webConfig = { 15 | devtool: '#cheap-module-eval-source-map', 16 | entry: { 17 | web: path.join(__dirname, '../src/renderer/main.js') 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(js|vue)$/, 23 | enforce: 'pre', 24 | exclude: /node_modules/, 25 | use: { 26 | loader: 'eslint-loader', 27 | options: { 28 | formatter: require('eslint-friendly-formatter') 29 | } 30 | } 31 | }, 32 | { 33 | test: /\.scss$/, 34 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 35 | }, 36 | { 37 | test: /\.sass$/, 38 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 39 | }, 40 | { 41 | test: /\.less$/, 42 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 43 | }, 44 | { 45 | test: /\.css$/, 46 | use: ['vue-style-loader', 'css-loader'] 47 | }, 48 | { 49 | test: /\.html$/, 50 | use: 'vue-html-loader' 51 | }, 52 | { 53 | test: /\.js$/, 54 | use: 'babel-loader', 55 | include: [ path.resolve(__dirname, '../src/renderer') ], 56 | exclude: /node_modules/ 57 | }, 58 | { 59 | test: /\.vue$/, 60 | use: { 61 | loader: 'vue-loader', 62 | options: { 63 | extractCSS: true, 64 | loaders: { 65 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 66 | scss: 'vue-style-loader!css-loader!sass-loader', 67 | less: 'vue-style-loader!css-loader!less-loader' 68 | } 69 | } 70 | } 71 | }, 72 | { 73 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 74 | use: { 75 | loader: 'url-loader', 76 | query: { 77 | limit: 10000, 78 | name: 'imgs/[name].[ext]' 79 | } 80 | } 81 | }, 82 | { 83 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 84 | use: { 85 | loader: 'url-loader', 86 | query: { 87 | limit: 10000, 88 | name: 'fonts/[name].[ext]' 89 | } 90 | } 91 | } 92 | ] 93 | }, 94 | plugins: [ 95 | new VueLoaderPlugin(), 96 | new MiniCssExtractPlugin({filename: 'styles.css'}), 97 | new HtmlWebpackPlugin({ 98 | filename: 'index.html', 99 | template: path.resolve(__dirname, '../src/index.ejs'), 100 | minify: { 101 | collapseWhitespace: true, 102 | removeAttributeQuotes: true, 103 | removeComments: true 104 | }, 105 | nodeModules: false 106 | }), 107 | new webpack.DefinePlugin({ 108 | 'process.env.IS_WEB': 'true' 109 | }), 110 | new webpack.HotModuleReplacementPlugin(), 111 | new webpack.NoEmitOnErrorsPlugin() 112 | ], 113 | output: { 114 | filename: '[name].js', 115 | path: path.join(__dirname, '../dist/web') 116 | }, 117 | resolve: { 118 | alias: { 119 | '@': path.join(__dirname, '../src/renderer'), 120 | 'vue$': 'vue/dist/vue.esm.js' 121 | }, 122 | extensions: ['.js', '.vue', '.json', '.css'] 123 | }, 124 | target: 'web' 125 | } 126 | 127 | /** 128 | * Adjust webConfig for production settings 129 | */ 130 | if (process.env.NODE_ENV === 'production') { 131 | webConfig.devtool = '' 132 | 133 | webConfig.plugins.push( 134 | new BabiliWebpackPlugin(), 135 | new CopyWebpackPlugin([ 136 | { 137 | from: path.join(__dirname, '../static'), 138 | to: path.join(__dirname, '../dist/web/static'), 139 | ignore: ['.*'] 140 | } 141 | ]), 142 | new webpack.DefinePlugin({ 143 | 'process.env.NODE_ENV': '"production"' 144 | }), 145 | new webpack.LoaderOptionsPlugin({ 146 | minimize: true 147 | }) 148 | ) 149 | } 150 | 151 | module.exports = webConfig 152 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/.eslintignore -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: 'standard', 12 | globals: { 13 | __static: true 14 | }, 15 | plugins: [ 16 | 'html' 17 | ], 18 | 'rules': { 19 | // allow paren-less arrow functions 20 | 'arrow-parens': 0, 21 | // allow async-await 22 | 'generator-star-spacing': 0, 23 | // allow debugger during development 24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | node_modules/ 7 | npm-debug.log 8 | npm-debug.log.* 9 | thumbs.db 10 | !.gitkeep 11 | .idea 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8.3 2 | sudo: required 3 | dist: trusty 4 | language: c 5 | matrix: 6 | include: 7 | - os: osx 8 | - os: linux 9 | env: CC=clang CXX=clang++ npm_config_clang=1 10 | compiler: clang 11 | cache: 12 | directories: 13 | - node_modules 14 | - "$HOME/.electron" 15 | - "$HOME/.cache" 16 | addons: 17 | apt: 18 | packages: 19 | - libgnome-keyring-dev 20 | - icnsutils 21 | before_install: 22 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 23 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 24 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 25 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 26 | install: 27 | - nvm install 7 28 | - curl -o- -L https://yarnpkg.com/install.sh | bash 29 | - source ~/.bashrc 30 | - npm install -g xvfb-maybe 31 | - yarn 32 | script: 33 | - yarn run build 34 | branches: 35 | only: 36 | - master 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 | # Warning 2 | > This project is no longer managed 3 | 4 | completely restructured 5 | Please use the [vscode extension ERD Editor](https://marketplace.visualstudio.com/items?itemName=dineug.vuerd-vscode) 6 | 7 | ## Migration Data 8 | ```javascript 9 | const oldData = require("./[oldData].json"); 10 | const fs = require("fs"); 11 | 12 | function createBaseData() { 13 | return { 14 | canvas: { 15 | width: 2000, 16 | height: 2000, 17 | scrollTop: 0, 18 | scrollLeft: 0, 19 | show: { 20 | tableComment: true, 21 | columnComment: true, 22 | columnDataType: true, 23 | columnDefault: true, 24 | columnAutoIncrement: true, 25 | columnPrimaryKey: true, 26 | columnUnique: true, 27 | columnNotNull: true, 28 | relationship: true 29 | }, 30 | database: "MySQL", 31 | databaseName: "", 32 | canvasType: "ERD", 33 | language: "graphql", 34 | tableCase: "pascalCase", 35 | columnCase: "camelCase" 36 | }, 37 | memo: { 38 | memos: [] 39 | }, 40 | table: { 41 | tables: [], 42 | edit: null, 43 | copyColumns: [], 44 | columnDraggable: null 45 | }, 46 | relationship: { 47 | relationships: [], 48 | draw: null 49 | } 50 | }; 51 | } 52 | 53 | oldData.tabs.forEach(tab => { 54 | const baseData = createBaseData(); 55 | baseData.canvas.databaseName = tab.name; 56 | baseData.canvas.width = tab.store.CANVAS_WIDTH; 57 | baseData.canvas.height = tab.store.CANVAS_WIDTH; 58 | tab.store.tables.forEach(table => { 59 | const resultTable = { 60 | id: table.id, 61 | name: table.name, 62 | comment: table.comment, 63 | ui: { 64 | active: table.ui.selected, 65 | top: table.ui.top, 66 | left: table.ui.left, 67 | widthName: table.ui.width / 2, 68 | widthComment: table.ui.width / 2, 69 | zIndex: table.ui.zIndex 70 | }, 71 | columns: [] 72 | }; 73 | table.columns.forEach(column => { 74 | resultTable.columns.push({ 75 | id: column.id, 76 | name: column.name, 77 | comment: column.comment, 78 | dataType: column.dataType, 79 | default: column.default, 80 | option: { 81 | autoIncrement: column.options.autoIncrement, 82 | primaryKey: column.options.primaryKey, 83 | unique: column.options.unique, 84 | notNull: column.options.notNull 85 | }, 86 | ui: { 87 | active: column.ui.selected, 88 | pk: column.ui.pk, 89 | fk: column.ui.fk, 90 | pfk: column.ui.pfk, 91 | widthName: column.ui.widthName, 92 | widthComment: column.ui.widthComment, 93 | widthDataType: column.ui.widthDataType, 94 | widthDefault: 60 95 | } 96 | }); 97 | }); 98 | baseData.table.tables.push(resultTable); 99 | }); 100 | tab.store.lines.forEach(line => { 101 | baseData.relationship.relationships.push({ 102 | identification: line.isIdentification, 103 | id: line.id, 104 | relationshipType: line.type === "erd-0-1-N" ? "ZeroOneN" : "ZeroOne", 105 | start: { 106 | tableId: line.points[0].id, 107 | columnIds: line.points[0].columnIds, 108 | x: line.points[0].x, 109 | y: line.points[0].y, 110 | direction: "top" 111 | }, 112 | end: { 113 | tableId: line.points[1].id, 114 | columnIds: line.points[1].columnIds, 115 | x: line.points[1].x, 116 | y: line.points[1].y, 117 | direction: "top" 118 | } 119 | }); 120 | }); 121 | tab.store.memos.forEach(memo => { 122 | baseData.memo.memos.push({ 123 | value: memo.content, 124 | id: memo.id, 125 | ui: { 126 | active: memo.ui.selected, 127 | top: memo.ui.top, 128 | left: memo.ui.left, 129 | width: memo.ui.width, 130 | height: memo.ui.height, 131 | zIndex: memo.ui.zIndex 132 | } 133 | }); 134 | }); 135 | 136 | fs.writeFile( 137 | `./convert-${new Date().getTime()}.vuerd.json`, 138 | JSON.stringify( 139 | baseData, 140 | (key, value) => { 141 | return value; 142 | }, 143 | 2 144 | ), 145 | err => { 146 | if (err) { 147 | console.log(err); 148 | } 149 | } 150 | ); 151 | }); 152 | 153 | console.log("END"); 154 | ``` 155 | 156 | ## vuerd 157 | ![logo](https://vuerd.github.io/vuerd-front/verd.png) 158 | --- 159 | ![use](https://user-images.githubusercontent.com/45829489/54869528-2ecfeb00-4ddd-11e9-8f7b-40df329646fa.png) 160 | ![use](https://user-images.githubusercontent.com/45829489/54869529-2f688180-4ddd-11e9-810c-60c78a776bf5.png) 161 | --- 162 | # vuerd-electron 163 | ## ERD 164 | * ERD editor [demo](https://vuerd.github.io/vuerd-front/). 165 | * ERD chrome extensions [app](https://chrome.google.com/webstore/detail/vuerd/jnjbnkehgfngjhlcaefjfdamioapajfg) 166 | * ERD desktop app [download](https://github.com/vuerd/vuerd-electron/releases) 167 | 168 | ## start 169 | 170 | ``` bash 171 | $ yarn 172 | $ yarn dev 173 | ``` 174 | 175 | ## License 176 | [MIT](https://github.com/vuerd/vuerd-electron/blob/master/LICENSE) 177 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | image: Visual Studio 2017 8 | platform: 9 | - x64 10 | 11 | cache: 12 | - node_modules 13 | - '%APPDATA%\npm-cache' 14 | - '%USERPROFILE%\.electron' 15 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 16 | 17 | init: 18 | - git config --global core.autocrlf input 19 | 20 | install: 21 | - ps: Install-Product node 8 x64 22 | - git reset --hard HEAD 23 | - yarn 24 | - node --version 25 | 26 | build_script: 27 | - yarn build 28 | 29 | test: off 30 | -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/build/icons/icon.ico -------------------------------------------------------------------------------- /build/icons/verd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/build/icons/verd.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuerd-electron", 3 | "version": "1.0.2", 4 | "author": "dineug ", 5 | "description": "ERD app", 6 | "license": "MIT", 7 | "repository": "https://github.com/vuerd/vuerd-electron.git", 8 | "main": "./dist/electron/main.js", 9 | "scripts": { 10 | "build": "node .electron-vue/build.js && electron-builder", 11 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 12 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 13 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 14 | "dev": "node .electron-vue/dev-runner.js", 15 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src", 16 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src", 17 | "pack": "npm run pack:main && npm run pack:renderer", 18 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 19 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 20 | "postinstall": "npm run lint:fix" 21 | }, 22 | "build": { 23 | "productName": "vuerd", 24 | "appId": "com.dineug.vuerd", 25 | "directories": { 26 | "output": "build" 27 | }, 28 | "files": [ 29 | "dist/electron/**/*" 30 | ], 31 | "dmg": { 32 | "contents": [ 33 | { 34 | "x": 410, 35 | "y": 150, 36 | "type": "link", 37 | "path": "/Applications" 38 | }, 39 | { 40 | "x": 130, 41 | "y": 150, 42 | "type": "file" 43 | } 44 | ] 45 | }, 46 | "mac": { 47 | "icon": "build/icons/icon.icns" 48 | }, 49 | "win": { 50 | "icon": "build/icons/icon.ico" 51 | }, 52 | "linux": { 53 | "icon": "build/icons", 54 | "target": [ 55 | "AppImage", 56 | "deb", 57 | "snap" 58 | ] 59 | } 60 | }, 61 | "dependencies": { 62 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 63 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 64 | "@fortawesome/vue-fontawesome": "^0.1.6", 65 | "axios": "^0.19.0", 66 | "dom-to-image": "^2.6.0", 67 | "filename-reserved-regex": "^2.0.0", 68 | "moo": "^0.5.0", 69 | "undo-manager": "^1.0.5", 70 | "velocity-animate": "^1.5.2", 71 | "vue": "^2.6.10", 72 | "vue-electron": "^1.0.6", 73 | "vue-router": "^3.0.6", 74 | "vuedraggable": "^2.21.0", 75 | "vuex": "^3.1.1", 76 | "vuex-electron": "^1.0.3" 77 | }, 78 | "devDependencies": { 79 | "ajv": "^6.5.0", 80 | "babel-core": "^6.26.3", 81 | "babel-eslint": "^8.2.6", 82 | "babel-loader": "^7.1.4", 83 | "babel-plugin-transform-runtime": "^6.23.0", 84 | "babel-preset-env": "^1.7.0", 85 | "babel-preset-stage-0": "^6.24.1", 86 | "babel-register": "^6.26.0", 87 | "babili-webpack-plugin": "^0.1.2", 88 | "cfonts": "^2.1.2", 89 | "chalk": "^2.4.1", 90 | "copy-webpack-plugin": "^4.5.1", 91 | "cross-env": "^5.1.6", 92 | "css-loader": "^0.28.11", 93 | "del": "^3.0.0", 94 | "devtron": "^1.4.0", 95 | "electron": "^2.0.4", 96 | "electron-builder": "^20.39.0", 97 | "electron-debug": "^1.5.0", 98 | "electron-devtools-installer": "^2.2.4", 99 | "eslint": "^4.19.1", 100 | "eslint-config-standard": "^11.0.0", 101 | "eslint-friendly-formatter": "^4.0.1", 102 | "eslint-loader": "^2.0.0", 103 | "eslint-plugin-html": "^4.0.3", 104 | "eslint-plugin-import": "^2.12.0", 105 | "eslint-plugin-node": "^6.0.1", 106 | "eslint-plugin-promise": "^3.8.0", 107 | "eslint-plugin-standard": "^3.1.0", 108 | "file-loader": "^1.1.11", 109 | "html-webpack-plugin": "^3.2.0", 110 | "mini-css-extract-plugin": "0.4.0", 111 | "multispinner": "^0.2.1", 112 | "node-loader": "^0.6.0", 113 | "node-sass": "^4.9.2", 114 | "pug": "^2.0.3", 115 | "pug-plain-loader": "^1.0.0", 116 | "sass-loader": "^7.0.3", 117 | "style-loader": "^0.21.0", 118 | "url-loader": "^1.0.1", 119 | "vue-html-loader": "^1.2.4", 120 | "vue-loader": "^15.2.4", 121 | "vue-style-loader": "^4.1.0", 122 | "vue-template-compiler": "^2.5.16", 123 | "webpack": "^4.15.1", 124 | "webpack-cli": "^3.0.8", 125 | "webpack-dev-server": "^3.1.4", 126 | "webpack-hot-middleware": "^2.22.2", 127 | "webpack-merge": "^4.1.3" 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vuerd 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | <% if (!process.browser) { %> 17 | 20 | <% } %> 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Install `electron-debug` with `devtron` 11 | require('electron-debug')({ showDevTools: true }) 12 | 13 | // Install `vue-devtools` 14 | require('electron').app.on('ready', () => { 15 | let installExtension = require('electron-devtools-installer') 16 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 17 | .then(() => {}) 18 | .catch(err => { 19 | console.log('Unable to install `vue-devtools`: \n', err) 20 | }) 21 | }) 22 | 23 | // Require `main` process to boot app 24 | require('./index') -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app, BrowserWindow } from 'electron' 4 | 5 | /** 6 | * Set `__static` path to static files in production 7 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 8 | */ 9 | if (process.env.NODE_ENV !== 'development') { 10 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') 11 | } 12 | 13 | let mainWindow 14 | const winURL = process.env.NODE_ENV === 'development' 15 | ? `http://localhost:9080` 16 | : `file://${__dirname}/index.html` 17 | 18 | function createWindow () { 19 | /** 20 | * Initial window options 21 | */ 22 | mainWindow = new BrowserWindow({ 23 | height: 800, 24 | useContentSize: true, 25 | width: 1280 26 | }) 27 | 28 | mainWindow.loadURL(winURL) 29 | 30 | mainWindow.on('closed', () => { 31 | mainWindow = null 32 | }) 33 | } 34 | 35 | app.on('ready', createWindow) 36 | 37 | app.on('window-all-closed', () => { 38 | if (process.platform !== 'darwin') { 39 | app.quit() 40 | } 41 | }) 42 | 43 | app.on('activate', () => { 44 | if (mainWindow === null) { 45 | createWindow() 46 | } 47 | }) 48 | 49 | /** 50 | * Auto Updater 51 | * 52 | * Uncomment the following code below and install `electron-updater` to 53 | * support auto updating. Code Signing with a valid certificate is required. 54 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 55 | */ 56 | 57 | /* 58 | import { autoUpdater } from 'electron-updater' 59 | 60 | autoUpdater.on('update-downloaded', () => { 61 | autoUpdater.quitAndInstall() 62 | }) 63 | 64 | app.on('ready', () => { 65 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 66 | }) 67 | */ 68 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/components/editor/CanvasSvg.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 133 | 134 | 143 | -------------------------------------------------------------------------------- /src/renderer/components/editor/Grid.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 250 | 251 | 369 | -------------------------------------------------------------------------------- /src/renderer/components/editor/Modal.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 214 | 215 | 402 | -------------------------------------------------------------------------------- /src/renderer/components/editor/QuickMenu.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 124 | 125 | 181 | -------------------------------------------------------------------------------- /src/renderer/js/JSLog.js: -------------------------------------------------------------------------------- 1 | JSLog('module loaded', 'JSLog') 2 | 3 | /** 4 | * 전역 로그 5 | * @param option 옵션 'to' -> toString 6 | * @param list 7 | * @constructor 8 | */ 9 | export default function JSLog (option, ...list) { 10 | if (process.env.NODE_ENV === 'development') { 11 | if (option === 'to') { 12 | list.forEach(obj => { 13 | let log = '' 14 | const objs = [] 15 | let isFirst = false 16 | Object.keys(obj).forEach(prop => { 17 | if (isFirst) { 18 | log += 'JSLog: ' 19 | } 20 | isFirst = true 21 | log += `${prop} : ${obj[prop]} \n` 22 | if (typeof (obj[prop]) === 'object') { 23 | objs.push({ 24 | name: prop, 25 | o: obj[prop] 26 | }) 27 | } 28 | }) 29 | JSLog(log) 30 | objs.forEach(function (v) { 31 | JSLog(`-> ${v.name}`) 32 | JSLog('to', v.o) 33 | }) 34 | }) 35 | } else { 36 | let args = Array.prototype.slice.call(arguments) 37 | if (window.console) console.log(`JSLog: ${args.join(' | ')}`) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/renderer/js/editor/Data.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import * as util from './util' 3 | import dataType from '@/store/editor/dataType' 4 | 5 | const TABLE_WIDTH = 350 6 | const TABLE_HEIGHT = 84 7 | const COLUMN_WIDTH = 50 8 | const MEMO_WIDTH = 150 9 | const MEMO_HEIGHT = 100 10 | 11 | // 데이터 구조 초기값 12 | const tab = { 13 | id: util.guid(), 14 | name: 'untitled', 15 | active: true, 16 | ui: { 17 | isReadName: true 18 | } 19 | } 20 | const erd = { 21 | TABLE_WIDTH: 350, 22 | TABLE_HEIGHT: 84, 23 | COLUMN_WIDTH: 50, 24 | COLUMN_HEIGHT: 25, 25 | PREVIEW_WIDTH: 150, 26 | CANVAS_WIDTH: 5000, 27 | CANVAS_HEIGHT: 5000, 28 | MEMO_WIDTH: 150, 29 | MEMO_HEIGHT: 100, 30 | DBType: 'MySQL', 31 | dataTypes: dataType['MySQL'], 32 | searchDomains: [] 33 | } 34 | const table = { 35 | id: util.guid(), 36 | name: '', 37 | comment: '', 38 | ui: { 39 | selected: false, 40 | top: document.documentElement.scrollTop + 100, 41 | left: document.documentElement.scrollLeft + 200, 42 | width: TABLE_WIDTH, 43 | height: TABLE_HEIGHT, 44 | zIndex: 0, 45 | isReadName: true, 46 | isReadComment: true 47 | } 48 | } 49 | const column = { 50 | id: util.guid(), 51 | name: '', 52 | comment: '', 53 | dataType: '', 54 | domain: '', 55 | domainId: '', 56 | default: '', 57 | options: { 58 | autoIncrement: false, 59 | primaryKey: false, 60 | unique: false, 61 | notNull: false 62 | }, 63 | ui: { 64 | selected: false, 65 | pk: false, 66 | fk: false, 67 | pfk: false, 68 | isDataTypeHint: false, 69 | isDomainHint: false, 70 | isHover: false, 71 | widthName: COLUMN_WIDTH, 72 | widthDataType: COLUMN_WIDTH, 73 | widthComment: COLUMN_WIDTH, 74 | widthDomain: COLUMN_WIDTH, 75 | isReadName: true, 76 | isReadDataType: true, 77 | isReadComment: true, 78 | isReadDomain: true 79 | } 80 | } 81 | const line = { 82 | id: util.guid(), 83 | type: 'QuickMenu.vue', 84 | isIdentification: false, 85 | points: [ 86 | { 87 | id: null, 88 | x: 0, 89 | y: 0, 90 | columnIds: [] 91 | }, 92 | { 93 | id: null, 94 | x: 0, 95 | y: 0, 96 | columnIds: [] 97 | } 98 | ] 99 | } 100 | const memo = { 101 | id: util.guid(), 102 | content: '', 103 | ui: { 104 | selected: false, 105 | top: document.documentElement.scrollTop + 100, 106 | left: document.documentElement.scrollLeft + 200, 107 | width: MEMO_WIDTH, 108 | height: MEMO_HEIGHT, 109 | zIndex: 0 110 | } 111 | } 112 | const domain = { 113 | id: util.guid(), 114 | name: '', 115 | dataType: '', 116 | default: '', 117 | ui: { 118 | isReadname: true, 119 | isReaddataType: true, 120 | isReaddefault: true 121 | } 122 | } 123 | 124 | /** 125 | * 데이터 구조 호환성 126 | */ 127 | class Data { 128 | constructor () { 129 | JSLog('module loaded', 'Data') 130 | this.core = null 131 | } 132 | 133 | // 종속성 초기화 134 | init (core) { 135 | JSLog('module dependency init', 'Data') 136 | this.core = core 137 | } 138 | 139 | set (old) { 140 | if (old.id === undefined) { 141 | old.id = util.guid() 142 | } 143 | this.setTab(old.tabs) 144 | } 145 | setTab (list) { 146 | list.forEach(old => { 147 | this.setData(old, tab) 148 | this.setErd(old.store) 149 | }) 150 | } 151 | setErd (old) { 152 | this.setData(old, erd) 153 | this.setTable(old.tables) 154 | this.setList(line, old.lines) 155 | this.setList(memo, old.memos) 156 | this.setList(domain, old.domains) 157 | } 158 | setTable (list) { 159 | list.forEach(old => { 160 | this.setData(old, table) 161 | this.setList(column, old.columns) 162 | }) 163 | } 164 | setList (type, list) { 165 | list.forEach(old => { 166 | this.setData(old, type) 167 | }) 168 | } 169 | 170 | setData (oldData, newData) { 171 | if (newData !== null) { 172 | Object.keys(newData).forEach(key => { 173 | if (typeof newData[key] === 'object') { 174 | if (oldData[key] === undefined) { 175 | oldData[key] = newData[key] 176 | } else { 177 | this.setData(oldData[key], newData[key]) 178 | } 179 | } else { 180 | if (oldData[key] === undefined) { 181 | oldData[key] = newData[key] 182 | } 183 | } 184 | }) 185 | } 186 | } 187 | 188 | // 객체 정리 189 | destroy () {} 190 | } 191 | 192 | export default new Data() 193 | -------------------------------------------------------------------------------- /src/renderer/js/editor/ERD.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import event from './Event' 3 | import file from './File' 4 | import sql from './SQL' 5 | import undoRedo from './UndoRedo' 6 | import indexedDB from './IndexedDB' 7 | import data from './Data' 8 | import model from '@/store/editor/model' 9 | 10 | /** 11 | * core 클래스 12 | */ 13 | class ERD { 14 | constructor () { 15 | JSLog('module loaded', 'ERD') 16 | 17 | // 모듈 객체 18 | this.core = { 19 | erd: this, 20 | event: event, 21 | file: file, 22 | sql: sql, 23 | undoRedo: undoRedo, 24 | indexedDB: indexedDB, 25 | data: data 26 | } 27 | 28 | this.setInit(this.core) 29 | } 30 | 31 | // 종속성 초기화 32 | setInit (core) { 33 | JSLog('module dependency init', 'ERD') 34 | Object.keys(core).forEach(v => { 35 | if (typeof core[v].init === 'function') core[v].init(core) 36 | }) 37 | } 38 | 39 | // 할성화 된 탭 모델 데이터 40 | store () { 41 | for (let tab of model.state.tabs) { 42 | if (tab.active) { 43 | return tab.store 44 | } 45 | } 46 | } 47 | 48 | // 활성화 된 탭 state 49 | active () { 50 | for (let tab of model.state.tabs) { 51 | if (tab.active) { 52 | return tab 53 | } 54 | } 55 | } 56 | 57 | // 객체 정리 58 | destroy () { 59 | Object.keys(this.core).forEach(key => { 60 | if (key !== 'erd' && typeof this.core[key].destroy === 'function') { 61 | this.core[key].destroy() 62 | } 63 | }) 64 | } 65 | } 66 | 67 | export default new ERD() 68 | -------------------------------------------------------------------------------- /src/renderer/js/editor/File.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import storeERD from '@/store/editor/erd' 3 | import model from '@/store/editor/model' 4 | import domToImage from 'dom-to-image' 5 | import * as util from './util' 6 | import fs from 'fs' 7 | import electron from 'electron' 8 | 9 | const dialog = electron.remote.dialog 10 | 11 | /** 12 | * 파일 클래스 13 | */ 14 | class File { 15 | constructor () { 16 | JSLog('module loaded', 'File') 17 | 18 | this.core = null 19 | this.a = document.createElement('a') 20 | } 21 | 22 | // 종속성 초기화 23 | init (core) { 24 | JSLog('module dependency init', 'File') 25 | this.core = core 26 | } 27 | 28 | // file import click event 29 | click (type) { 30 | switch (type) { 31 | case 'verd': 32 | dialog.showOpenDialog({ 33 | properties: ['openFile'], 34 | filters: [ 35 | { name: 'erd', extensions: ['verd'] } 36 | ] 37 | }, (fileNames) => { 38 | if (fileNames === undefined) return false 39 | 40 | // fileNames[0] file path 41 | if (/\.(verd)$/i.test(fileNames[0])) { 42 | fs.readFile(fileNames[0], 'utf-8', (err, data) => { 43 | if (err) { 44 | alert('An error ocurred reading the file :' + err.message) 45 | } else { 46 | this.loaded('verd', data, true) 47 | let path = fileNames[0] 48 | this.core.indexedDB.setImport(util.getPathToFileName(path), path) 49 | } 50 | }) 51 | } else { 52 | alert('Just upload the verd file') 53 | } 54 | }) 55 | break 56 | } 57 | } 58 | 59 | // loaded 60 | loaded (type, data, isImport, id) { 61 | switch (type) { 62 | case 'verd': 63 | try { 64 | const json = JSON.parse(data) 65 | this.core.data.set(json) 66 | if (isImport) { 67 | json.id = util.guid() 68 | } else { 69 | json.id = id 70 | } 71 | const tabs = [] 72 | for (let tab of json.tabs) { 73 | const newTab = { 74 | id: util.guid(), 75 | name: tab.name, 76 | active: tab.active, 77 | store: storeERD(), 78 | ui: { 79 | isReadName: true 80 | } 81 | } 82 | newTab.store.commit({ 83 | type: 'importData', 84 | state: tab.store 85 | }) 86 | tabs.push(newTab) 87 | } 88 | model.commit({ 89 | type: 'importData', 90 | state: { 91 | id: json.id, 92 | tabs: tabs 93 | } 94 | }) 95 | this.core.event.components.CanvasMenu.isSave = true 96 | } catch (e) { 97 | alert('verd parsing error') 98 | } 99 | break 100 | } 101 | } 102 | 103 | // export 104 | exportData (type) { 105 | this.core.indexedDB.one(model.state.id, v => { 106 | const fileName = `${v.name}.${type}` 107 | switch (type) { 108 | case 'verd': 109 | dialog.showSaveDialog({ 110 | defaultPath: fileName, 111 | filters: [ 112 | { name: 'erd', extensions: ['verd'] } 113 | ] 114 | }, (fileName) => { 115 | if (fileName === undefined) return false 116 | const json = this.toJSON() 117 | fs.writeFile(fileName, json, (err) => { 118 | if (err) { 119 | alert('An error ocurred creating the file ' + err.message) 120 | } else { 121 | this.core.indexedDB.update({ 122 | id: model.state.id, 123 | path: fileName, 124 | name: util.getPathToFileName(fileName) 125 | }) 126 | } 127 | }) 128 | }) 129 | break 130 | case 'sql': 131 | const sql = this.core.sql.toDDL() 132 | const blobSQL = new Blob([sql], { type: 'text' }) 133 | this.execute(blobSQL, fileName) 134 | break 135 | case 'png': 136 | domToImage.toBlob(document.querySelector('.canvas')).then(blob => { 137 | this.execute(blob, fileName) 138 | }) 139 | break 140 | } 141 | }) 142 | } 143 | 144 | // download 145 | execute (blob, fileName) { 146 | this.a.href = window.URL.createObjectURL(blob) 147 | this.a.download = fileName 148 | this.a.click() 149 | } 150 | 151 | // json 데이터 정제 152 | toJSON (data) { 153 | let state = model.state 154 | if (data) { 155 | state = data 156 | } 157 | const models = { 158 | id: state.id, 159 | tabs: [] 160 | } 161 | for (let tab of state.tabs) { 162 | models.tabs.push({ 163 | id: tab.id, 164 | name: tab.name, 165 | active: tab.active, 166 | store: tab.store.state, 167 | ui: { 168 | isReadName: true 169 | } 170 | }) 171 | } 172 | return JSON.stringify(models) 173 | } 174 | 175 | // 현재 텝 복사 생성 176 | clone () { 177 | const tab = this.core.erd.active() 178 | const json = JSON.stringify(this.core.erd.store().state) 179 | const state = JSON.parse(json) 180 | const newTab = { 181 | name: tab.name, 182 | store: storeERD() 183 | } 184 | newTab.store.commit({ 185 | type: 'importData', 186 | state: state 187 | }) 188 | model.commit({ 189 | type: 'modelAdd', 190 | isInit: true, 191 | name: newTab.name, 192 | store: newTab.store 193 | }) 194 | } 195 | 196 | // 객체 정리 197 | destroy () {} 198 | } 199 | 200 | export default new File() 201 | -------------------------------------------------------------------------------- /src/renderer/js/editor/IndexedDB.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import model from '@/store/editor/model' 3 | import storeERD from '@/store/editor/erd' 4 | import * as util from './util' 5 | import fs from 'fs' 6 | 7 | const DB_NAME = 'verd' 8 | const DB_VERSION = 2 9 | const DB_STORE_NAME = 'project' 10 | const DB_STORE_NAME_MODEL = 'model' 11 | const MODE = { 12 | RW: 'readwrite', 13 | R: 'readonly' 14 | } 15 | 16 | /** 17 | * indexDB 18 | */ 19 | class IndexedDB { 20 | constructor () { 21 | JSLog('module loaded', 'IndexedDB') 22 | this.core = null 23 | } 24 | 25 | init (core) { 26 | JSLog('module dependency init', 'IndexedDB') 27 | this.core = core 28 | this.lastLoaded([], v => { 29 | if (v.path === null) { 30 | this.core.file.loaded('verd', v.json, false, v.id) 31 | } else { 32 | fs.readFile(v.path, 'utf-8', (err, data) => { 33 | if (err) { 34 | if (err.code === 'ENOENT') { 35 | this.update({ 36 | id: v.id, 37 | path: null 38 | }) 39 | this.core.file.loaded('verd', v.json, false, v.id) 40 | } else { 41 | alert('An error ocurred reading the file :' + err.message) 42 | } 43 | } else { 44 | this.core.file.loaded('verd', data, false, v.id) 45 | } 46 | }) 47 | } 48 | }) 49 | } 50 | 51 | openIndexedDB () { 52 | const openDB = indexedDB.open(DB_NAME, DB_VERSION) 53 | openDB.onerror = e => { 54 | alert('IndexedDB onerror') 55 | } 56 | openDB.onupgradeneeded = e => { 57 | JSLog('IndexedDB onupgradeneeded') 58 | e.currentTarget.result.createObjectStore(DB_STORE_NAME, { keyPath: 'id' }) 59 | e.currentTarget.result.createObjectStore(DB_STORE_NAME_MODEL, { keyPath: 'id', autoIncrement: true }) 60 | } 61 | return openDB 62 | } 63 | 64 | getObjectStore (openDB, storeName, mode) { 65 | const db = {} 66 | db.result = openDB.result 67 | db.tx = db.result.transaction(storeName, mode) 68 | db.store = db.tx.objectStore(storeName) 69 | db.tx.oncomplete = e => { 70 | db.result.close() 71 | } 72 | return db 73 | } 74 | 75 | // 추가 76 | add (type, data) { 77 | switch (type) { 78 | case 'project': 79 | const openDB = this.openIndexedDB() 80 | openDB.onsuccess = e => { 81 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.RW) 82 | const project = { 83 | id: util.guid(), 84 | tabs: [{ 85 | id: util.guid(), 86 | name: 'untitled', 87 | active: true, 88 | store: storeERD(), 89 | ui: { 90 | isReadName: true 91 | } 92 | }] 93 | } 94 | const json = this.core.file.toJSON(project) 95 | this.core.file.loaded('verd', json, false, project.id) 96 | db.store.add({ 97 | id: project.id, 98 | name: this.getProjectName(), 99 | json: json, 100 | update_date: util.formatDate('yyyy-MM-dd hh:mm:ss', new Date()), 101 | path: null 102 | }) 103 | } 104 | break 105 | case 'model': 106 | const openDB2 = this.openIndexedDB() 107 | openDB2.onsuccess = e => { 108 | const db = this.getObjectStore(openDB2, DB_STORE_NAME_MODEL, MODE.RW) 109 | db.store.add({ 110 | name: data.name, 111 | json: JSON.stringify({ 112 | id: data.id, 113 | name: data.name, 114 | active: data.active, 115 | store: data.store.state, 116 | ui: { 117 | isReadName: true 118 | } 119 | }), 120 | update_date: util.formatDate('yyyy-MM-dd hh:mm:ss', new Date()) 121 | }) 122 | this.list('model', [], list => { 123 | if (list.length > 100) { 124 | let last = list[0] 125 | list.forEach(v => { 126 | const old = new Date(last.update_date) 127 | const date = new Date(v.update_date) 128 | if (old.getTime() > date.getTime()) { 129 | last = v 130 | } 131 | }) 132 | this.delete('model', last.id, () => {}) 133 | } 134 | }) 135 | } 136 | break 137 | } 138 | } 139 | 140 | // import 파일 추가 141 | setImport (name, path) { 142 | name = util.validFileName(name) 143 | const openDB = this.openIndexedDB() 144 | openDB.onsuccess = e => { 145 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.RW) 146 | db.store.add({ 147 | id: model.state.id, 148 | name: name, 149 | json: this.core.file.toJSON(), 150 | update_date: util.formatDate('yyyy-MM-dd hh:mm:ss', new Date()), 151 | path: path 152 | }) 153 | } 154 | } 155 | 156 | // 리스트 157 | list (type, list, callback) { 158 | const openDB = this.openIndexedDB() 159 | openDB.onsuccess = e => { 160 | let db = null 161 | switch (type) { 162 | case 'project': 163 | db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.R) 164 | break 165 | case 'model': 166 | db = this.getObjectStore(openDB, DB_STORE_NAME_MODEL, MODE.R) 167 | break 168 | } 169 | const req = db.store.openCursor() 170 | req.onsuccess = e => { 171 | const cursor = e.target.result 172 | if (cursor) { 173 | const req = db.store.get(cursor.key) 174 | req.onsuccess = e => { 175 | list.push(e.target.result) 176 | } 177 | cursor.continue() 178 | } else { 179 | list.sort((a, b) => { 180 | return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 181 | }) 182 | callback(list) 183 | } 184 | } 185 | } 186 | } 187 | 188 | // 선택 파일 load 189 | loaded (type, id) { 190 | switch (type) { 191 | case 'project': 192 | const openDB = this.openIndexedDB() 193 | openDB.onsuccess = e => { 194 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.R) 195 | const req = db.store.get(id) 196 | req.onsuccess = e => { 197 | if (e.target.result.path === null) { 198 | this.core.file.loaded('verd', e.target.result.json, false, e.target.result.id) 199 | } else { 200 | fs.readFile(e.target.result.path, 'utf-8', (err, data) => { 201 | if (err) { 202 | if (err.code === 'ENOENT') { 203 | this.update({ 204 | id: e.target.result.id, 205 | path: null 206 | }) 207 | this.core.file.loaded('verd', e.target.result.json, false, e.target.result.id) 208 | } else { 209 | alert('An error ocurred reading the file :' + err.message) 210 | } 211 | } else { 212 | this.core.file.loaded('verd', data, false, e.target.result.id) 213 | } 214 | }) 215 | } 216 | } 217 | } 218 | break 219 | case 'model': 220 | const openDB2 = this.openIndexedDB() 221 | openDB2.onsuccess = e => { 222 | const db = this.getObjectStore(openDB2, DB_STORE_NAME_MODEL, MODE.R) 223 | const req = db.store.get(id) 224 | req.onsuccess = e => { 225 | const json = JSON.parse(e.target.result.json) 226 | const newTab = { 227 | name: json.name, 228 | store: storeERD() 229 | } 230 | newTab.store.commit({ 231 | type: 'importData', 232 | state: json.store 233 | }) 234 | model.commit({ 235 | type: 'modelAdd', 236 | isInit: true, 237 | name: newTab.name, 238 | store: newTab.store 239 | }) 240 | } 241 | } 242 | break 243 | } 244 | } 245 | 246 | // 단일 조회 247 | one (id, callback) { 248 | const openDB = this.openIndexedDB() 249 | openDB.onsuccess = e => { 250 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.R) 251 | const req = db.store.get(id) 252 | req.onsuccess = e => { 253 | callback(e.target.result) 254 | } 255 | } 256 | } 257 | 258 | // 수정 259 | update (data, isRename) { 260 | if (data) { 261 | const openDB = this.openIndexedDB() 262 | openDB.onsuccess = e => { 263 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.RW) 264 | const req = db.store.get(data.id) 265 | req.onsuccess = e => { 266 | const oldData = req.result 267 | const oldPath = oldData.path 268 | const oldName = oldData.name 269 | oldData.update_date = util.formatDate('yyyy-MM-dd hh:mm:ss', new Date()) 270 | util.setData(oldData, data) 271 | 272 | if (oldData.path === null) { 273 | this.core.file.exportData('verd') 274 | } else if (!isRename) { 275 | fs.writeFile(oldData.path, oldData.json, (err) => { 276 | if (err) { 277 | alert('An error ocurred updating the file' + err.message) 278 | } 279 | }) 280 | } else if (isRename) { 281 | const newPath = `${util.getPathFile(oldData.path)}/${data.name}.verd` 282 | fs.rename(oldData.path, newPath, (err) => { 283 | if (err) { 284 | alert('An error ocurred updating the file' + err.message) 285 | this.update({ 286 | id: data.id, 287 | path: oldPath, 288 | name: oldName 289 | }) 290 | } 291 | }) 292 | 293 | oldData.path = newPath 294 | } 295 | 296 | db.store.put(oldData) 297 | this.core.event.components.CanvasMenu.isSave = true 298 | } 299 | } 300 | } else { 301 | const openDB = this.openIndexedDB() 302 | openDB.onsuccess = e => { 303 | this.core.event.onCursor('stop') 304 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.RW) 305 | const req = db.store.get(model.state.id) 306 | req.onsuccess = e => { 307 | const oldData = req.result 308 | oldData.update_date = util.formatDate('yyyy-MM-dd hh:mm:ss', new Date()) 309 | oldData.json = this.core.file.toJSON() 310 | db.store.put(oldData) 311 | this.core.event.components.CanvasMenu.isSave = true 312 | if (oldData.path === null) { 313 | this.core.file.exportData('verd') 314 | } else { 315 | fs.writeFile(oldData.path, oldData.json, (err) => { 316 | if (err) { 317 | alert('An error ocurred updating the file' + err.message) 318 | } 319 | }) 320 | } 321 | } 322 | } 323 | } 324 | } 325 | 326 | // 마지막 작업 내용 로드 327 | lastLoaded (list, callback) { 328 | const openDB = this.openIndexedDB() 329 | openDB.onsuccess = e => { 330 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.R) 331 | const req = db.store.openCursor() 332 | req.onsuccess = e => { 333 | const cursor = e.target.result 334 | if (cursor) { 335 | const req = db.store.get(cursor.key) 336 | req.onsuccess = e => { 337 | list.push(e.target.result) 338 | } 339 | cursor.continue() 340 | } else { 341 | if (list.length !== 0) { 342 | let last = list[0] 343 | list.forEach(v => { 344 | const old = new Date(last.update_date) 345 | const date = new Date(v.update_date) 346 | if (old.getTime() < date.getTime()) { 347 | last = v 348 | } 349 | }) 350 | callback(last) 351 | } else { 352 | this.add('project') 353 | } 354 | } 355 | } 356 | } 357 | } 358 | 359 | // 삭제 360 | delete (type, id, callback) { 361 | switch (type) { 362 | case 'project': 363 | const openDB = this.openIndexedDB() 364 | openDB.onsuccess = e => { 365 | const db = this.getObjectStore(openDB, DB_STORE_NAME, MODE.RW) 366 | const req = db.store.get(id) 367 | req.onsuccess = e => { 368 | db.store.delete(id) 369 | if (model.state.id === id) { 370 | this.lastLoaded([], v => { 371 | if (v.path === null) { 372 | this.core.file.loaded('verd', v.json, false, v.id) 373 | } else { 374 | fs.readFile(v.path, 'utf-8', (err, data) => { 375 | if (err) { 376 | if (err.code === 'ENOENT') { 377 | this.update({ 378 | id: v.id, 379 | path: null 380 | }) 381 | this.core.file.loaded('verd', v.json, false, v.id) 382 | } else { 383 | alert('An error ocurred reading the file :' + err.message) 384 | } 385 | } else { 386 | this.core.file.loaded('verd', data, false, v.id) 387 | } 388 | }) 389 | } 390 | }) 391 | } else { 392 | callback() 393 | } 394 | } 395 | } 396 | break 397 | case 'model': 398 | const openDB2 = this.openIndexedDB() 399 | openDB2.onsuccess = e => { 400 | const db = this.getObjectStore(openDB2, DB_STORE_NAME_MODEL, MODE.RW) 401 | const req = db.store.get(id) 402 | req.onsuccess = e => { 403 | db.store.delete(id) 404 | callback() 405 | } 406 | } 407 | break 408 | } 409 | } 410 | 411 | // get name 412 | getProjectName () { 413 | return `unnamed-${util.formatDate('yyyy-MM-dd_hhmmss', new Date())}` 414 | } 415 | 416 | // 객체 정리 417 | destroy () {} 418 | } 419 | 420 | export default new IndexedDB() 421 | -------------------------------------------------------------------------------- /src/renderer/js/editor/SQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import mysql from './sql/MySQL' 3 | import oracle from './sql/Oracle' 4 | import mariadb from './sql/MariaDB' 5 | import mssql from './sql/MSSQL' 6 | import postgresql from './sql/PostgreSQL' 7 | 8 | /** 9 | * SQL 클래스 10 | */ 11 | class SQL { 12 | constructor () { 13 | JSLog('module loaded', 'SQL') 14 | 15 | this.core = null 16 | this.DB = { 17 | mysql: mysql.init(this), 18 | oracle: oracle.init(this), 19 | mariadb: mariadb.init(this), 20 | mssql: mssql.init(this), 21 | postgresql: postgresql.init(this) 22 | } 23 | } 24 | 25 | // 종속성 초기화 26 | init (core) { 27 | JSLog('module dependency init', 'SQL') 28 | this.core = core 29 | } 30 | 31 | // SQL DDL 32 | toDDL () { 33 | const database = this.core.erd.active() 34 | return this.ddl(database) 35 | } 36 | 37 | // DDL SQL 생성 38 | ddl (database) { 39 | switch (database.store.state.DBType) { 40 | case 'MySQL': 41 | return this.DB.mysql.ddl(database) 42 | case 'Oracle': 43 | return this.DB.oracle.ddl(database) 44 | case 'MariaDB': 45 | return this.DB.mariadb.ddl(database) 46 | case 'MSSQL': 47 | return this.DB.mssql.ddl(database) 48 | case 'PostgreSQL': 49 | return this.DB.postgresql.ddl(database) 50 | } 51 | } 52 | 53 | // 이름 foramtter 54 | formatNames (list, backtick, backtick2) { 55 | let str = '' 56 | list.forEach((v, i) => { 57 | if (backtick) { 58 | if (backtick2) { 59 | str += `${backtick}${v.name}${backtick2}` 60 | } else { 61 | str += `${backtick}${v.name}${backtick}` 62 | } 63 | } else { 64 | str += v.name 65 | } 66 | if (list.length !== i + 1) str += ', ' 67 | }) 68 | return str 69 | } 70 | 71 | // 컬럼 이름, 데이터 타입 정렬 최대길이 72 | formatSize (columns) { 73 | let nameMax = 0 74 | let dataTypeMax = 0 75 | columns.forEach(column => { 76 | if (nameMax < column.name.length) nameMax = column.name.length 77 | if (dataTypeMax < column.dataType.length) dataTypeMax = column.dataType.length 78 | }) 79 | return { 80 | nameMax: nameMax, 81 | dataTypeMax: dataTypeMax 82 | } 83 | } 84 | 85 | // 숫자만큼 공백생성 86 | formatSpace (size) { 87 | let space = '' 88 | for (let i = 0; i < size; i++) { 89 | space += ' ' 90 | } 91 | return space 92 | } 93 | 94 | // 객체 정리 95 | destroy () { 96 | Object.keys(this.DB).forEach(key => { 97 | if (typeof this.DB[key].destroy === 'function') { 98 | this.DB[key].destroy() 99 | } 100 | }) 101 | } 102 | } 103 | 104 | export default new SQL() 105 | -------------------------------------------------------------------------------- /src/renderer/js/editor/UndoRedo.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../JSLog' 2 | import UndoManager from 'undo-manager' 3 | 4 | /** 5 | * undo, redo 6 | */ 7 | class UndoRedo { 8 | constructor () { 9 | JSLog('module loaded', 'UndoRedo') 10 | 11 | this.core = null 12 | this.historys = {} 13 | this.callback = null 14 | 15 | this.undoJson = { 16 | draggable: null, 17 | draw: null 18 | } 19 | } 20 | 21 | // 종속성 초기화 22 | init (core) { 23 | JSLog('module dependency init', 'UndoRedo') 24 | this.core = core 25 | } 26 | 27 | // 각 모델별 undo, redo 매니저 28 | getManager () { 29 | const id = this.core.erd.active().id 30 | if (this.historys[id] === undefined) { 31 | this.historys[id] = new UndoManager() 32 | this.historys[id].setCallback(this.callback) 33 | return this.getManager() 34 | } else { 35 | return this.historys[id] 36 | } 37 | } 38 | 39 | // undo, redo 추가 40 | add ({ undo, redo }) { 41 | this.getManager().add({ 42 | undo: () => { 43 | this.core.erd.store().commit({ 44 | type: 'importData', 45 | state: JSON.parse(undo) 46 | }) 47 | this.core.event.components.CanvasMenu.isSave = false 48 | }, 49 | redo: () => { 50 | this.core.erd.store().commit({ 51 | type: 'importData', 52 | state: JSON.parse(redo) 53 | }) 54 | this.core.event.components.CanvasMenu.isSave = false 55 | } 56 | }) 57 | } 58 | 59 | // 이전 60 | undo () { 61 | // this.core.event.stop() 62 | this.getManager().undo() 63 | } 64 | // 앞전 65 | redo () { 66 | // this.core.event.stop() 67 | this.getManager().redo() 68 | } 69 | 70 | // undo 셋팅 71 | setUndo (type) { 72 | switch (type) { 73 | case 'draggable': 74 | this.undoJson.draggable = JSON.stringify(this.core.erd.store().state) 75 | break 76 | case 'draw': 77 | this.undoJson.draw = JSON.stringify(this.core.erd.store().state) 78 | break 79 | } 80 | } 81 | 82 | // undo, redo 전 유효성 83 | set () { 84 | this.core.event.onCursor('stop') 85 | this.core.event.onDraggable('stop') 86 | this.core.event.onMemoResize('stop') 87 | } 88 | 89 | // 객체 정리 90 | destroy () {} 91 | } 92 | 93 | export default new UndoRedo() 94 | -------------------------------------------------------------------------------- /src/renderer/js/editor/grid/column.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'Name', 4 | key: 'name', 5 | width: 23 6 | }, 7 | { 8 | name: 'DataType', 9 | key: 'dataType', 10 | width: 23 11 | }, 12 | { 13 | name: 'PK', 14 | key: 'primaryKey', 15 | width: 2, 16 | type: 'checkbox' 17 | }, 18 | { 19 | name: 'NN', 20 | key: 'notNull', 21 | width: 2, 22 | type: 'checkbox' 23 | }, 24 | { 25 | name: 'UQ', 26 | key: 'unique', 27 | width: 2, 28 | type: 'checkbox' 29 | }, 30 | { 31 | name: 'AI', 32 | key: 'autoIncrement', 33 | width: 2, 34 | type: 'checkbox' 35 | }, 36 | { 37 | name: 'Default', 38 | key: 'default', 39 | width: 23 40 | }, 41 | { 42 | name: 'Comment', 43 | key: 'comment', 44 | width: 23 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /src/renderer/js/editor/grid/domain.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'Name', 4 | key: 'name', 5 | width: 32 6 | }, 7 | { 8 | name: 'DataType', 9 | key: 'dataType', 10 | width: 32 11 | }, 12 | { 13 | name: 'Default', 14 | key: 'default', 15 | width: 32 16 | }, 17 | { 18 | name: '', 19 | key: '', 20 | width: 4, 21 | type: 'button', 22 | icon: 'times' 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /src/renderer/js/editor/img/relationship.js: -------------------------------------------------------------------------------- 1 | const base64 = { 2 | 'erd-0-1': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QkExNkM0M0I0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QkExNkM0M0M0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCQTE2QzQzOTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCQTE2QzQzQTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PomEhhsAAAKSSURBVHja7FexbhpBED0ipFRI7ixBQwlV0nCUWKJIydEkFSRpHCGQ0iXgBqfBbQrAShXzA4BLJJCdEip3UDoSUFvwAZv30B5a1ncHBiSk2CONdrU3O/N2dt6w+IQQxiHllXFgeQFwcAD+aDT6zDPwBNsjaAoaVtbuoNcee46hOSi5/hd6pRv41vUBXJFJJ4FA4FMymTRCodDy22g0Mnq93gOml8Ph8Mxhb71QKOQYYzqdGq1W6zPsVkHwo5tGIpFvsVhMNJtN4Saz2UwUi0UB299QU9u/tBsMBrS51WN4BrcsS4zHY7GJEKQOYmsAMIzz5GrwyWQiSqWSwDXQkchms6LRaCwyoIPYB4Ar3NdyM+cEhPXv0LC0OYH+ZJZUEASG9fdbA2AAnlI9uQwedwF7waC29Pt92t5sCoCN6FzSZCmsdluq1aoxn8+LqN6+E0uwXoKPKNiQ4j7TNA0w5gRLZRdWraz7QZOyTkU6saXb7XL4taZH3IGSKRs4asVA5s5Vg2AwaOTz+QSmCZ2GniLTuI6uFpyLbcRfq9UeHYcZwL0v5mw8SFsYqb73yMBb9Tel3W4zA4s5gC1G2Yget2Lc8Q99MZ1Ol20AmLMOTjE9c7nT1xgstW4qlQrrhn7LNgACgp8/mN56dkKk84hVb1OLo+T+qUv66wi4QlnSeNc+cAG0y81IvyDfZaf7CE1Av5JubE6qSLDxXQEc05ib9HbLvp/JZARPTc6rQjDMyL5+Cz7wKkDDjapZD74zAOngHbRN57wGJyFAmfa6w/71ndCruyBoB0MHlf4FxZUDJd+o7wF8Z7Vfy/dAx8HFJWies1ng+CABqqe8oMIOL6KHDV5ElPt9APj/HqW+l/+Gzx7APwEGACfjkeXK58xsAAAAAElFTkSuQmCC', 3 | 'erd-0-1-N': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0RFRDI1RjI0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0RFRDI1RjM0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCQTE2QzQzRDQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCQTE2QzQzRTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnTT5ywAAAJMSURBVHja7Fc9T2JBFB3MZq1IqLSgsRM6G6TcAmK7VNoRK4yBxE6XP/CsNxBohc6G5w+AoCU0aEWpBfAD4AeM55AZMjvvg2dClsRwkxOeb+7ce+6dmTPPmJRSbNP2xJZtR2DrBH6k0+ndEkSxBHAD9AGpMAJc4Lfh1zDGn4GLgHglNb8kqANhSKVSTiaTkY7jyMFgILWNx2PZ7XZluVyW8BkBJWA1PhwO+f7ZinUO9DmH8xl3XfIGnefzuQwzBsvlcjKIAH4PWAh9zCLq9Xp48mq16knGwASTmkaShULBQ0B1ZlSr1TyFtFqtwOS3xWLxH2eyZctUUOKVFTGRtslk4iHAODZZ/k2yLDKIwLsZjJ2gM3Bo+V0wieu6vsvCCu0ucS+RPHC1vId8kp+YrWRwvHsIWaosO7Nun3DDqn1yD+zr+RQiz3WYz+dXz6iCP82g84lKB4jxF0Rv0G7P+Gw2E6ha9Ho9/eoPsRJAucbUzl53Wi79NmwU25QSJuLx+ObugkqlInD+l8+oTsAny1aHxDlKJpOBgzhBot1ui8Vi4R3cwCY8jbIJeap4JOHvAmd6flDQj+l06ncMD/yktdPp+AoW59nEeBqUnjjAzyACd7YQUcnUxL7CyJZWk7QWIs6xdcLQg6cvSzETEl+Q4iyXkAWZBHU3/stlpLvKpaWkm0sb5Tq+19exqfvWdbyU1gjX8SGLIlnKNJ9jPGYRP0gugQLwS717Az6AB+DJ+CC5Vs8vSkEffeIdK79mVALf95swtvvXbEdg2wQ+BRgAvLABcxKvek4AAAAASUVORK5CYII=', 4 | 'erd-0-N': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDc5MkNFMEM0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDc5MkNFMEQ0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0NzkyQ0UwQTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0NzkyQ0UwQjQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpR0ImIAAALlSURBVHja7Fe/jxJBGF0MyVUk2HgJFJ6NQqUNWEoCkfKWxusozxBI7Eiw8ardKy/hxyVWQqcF8AdA9Eqg8DqwuwJI7Aj8AeN7OEuGhd09tCBRvuTLzA4z37z53rxvF58QQtunPdD2bAcA/mg0+p9nYMf5Qfhz5fnmbwH47iND0PQaTS4QCOiRSGQ1PhgMZmja8OvhcNi/z4aIdc5Ycs1HjQCcHJu9hH/KZrOi0+kIu83nc9FsNkUymRSYZ3jEegqv6bousLHI5/NcU3RbkIa3W62W8DICKZVKDFhziFWMxWKiXq+v1ozHY8Exv0OajpmmSqVyitOtxrvdrjYajZZ90KGlUiktFAot+4ZhcDiHtTOc8L1KHWLoAKiFw+FVrOl0+rvjgNjA5msnJA3MCPxC+hX8DhuvzZN0kDqDfTt1nCPT/w1+tm3zIFPDidYC8oZxc8vcI6ZdBUHKMPadY1YMy0gBY6uxfHhIMBEKAw+BvIUMLB+q1aqG/iXSWnKg6wjND9DzmHQsFgttMploqlpIm2maWr/fv8bj5zUZMrV2KWYymaXTyDMCPgGAOxdpXRQKhQ9I7cZv2FSzDrPVvG44TiLc5CWp0Mnrn5gfxWQDFG8r02nddpwwiAzMXOrLC6d3CtNPWhxLMSjYKKcYe0XZ0OLxOOVHOX1xA6Bybj8MKWg0GjdOFNjTeUL5WNbr9UjDV3jcIf3nVIllrHK8V2xVYxypphr8kbXeidO2ql+WWwnijQrU0rq6mdT4W/gta4ldiuVy2ZJi0Q1AmoHVxTyBDL50/m7XOkETvIxxTL1zHpSwBoJlWBa2mtvNNpky+wmcjJvIk6Vtcc5Y9QjeHovxveRlbiun9tLKVFul1S2W+kJiywx4fg9AXmm+UDD5lIqg3HizwftSYgBHmV1izhXGfnrEeobmHeLkuB7rIj4n+WyTGjwh2xP4rXR+kMx2/BBKyLWzXQD8mx+lvsNfswOAfQP4JcAAK/At0HQvwB8AAAAASUVORK5CYII=', 5 | 'erd-1': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUJCNTBCQ0M0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUJCNTBCQ0Q0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQkI1MEJDQTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQkI1MEJDQjQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnOhfCQAAADcSURBVHjaYvz//z/DQAImhgEGow4YcAewaGpqjvAQoJI5CUAsD2VPB+JXxGpkpLQcAEZhQmBg4HwpKSkwf+rUqdOvX7+eRbQBIAdQgjU0NA6cOnXqPwwA+f9J0T+aDUcdACqIGkBpkZqGAs2sJzobTpky5T+lWRGYDRmkpaXBbKB5JOll/D/ADQIWYMFBsSGgEEAqiEgLAWDB0UCh/Q6LFi2yNzU1hcU/iGocLQlHC6IR1SBZsH79entgQmRAapCQlA2p1SJSQHLAS3o3yRYM2TTAONo3HPEOAAgwAJh583IRUllDAAAAAElFTkSuQmCC', 6 | 'erd-1-N': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QkExNkM0Mzc0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QkExNkM0Mzg0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCQTE2QzQzNTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCQTE2QzQzNjQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pt+BFx8AAAIISURBVHja7Fc9TwJBEF0MLYkdBgot4QdwLSaQSAmNdkCrR61CI9Ud1oCUIB0NYElBtJU/QIkmQKIVgcpqfXPekQNBYu7WM4FJJvs1x87Ovnk7uDjnzEnZYw7LzgHHHXAFAoHtjoDbpt9JQw/1fgX6vsH+CDohdVnlgWAwmE4kElWfz6eNy+Vypd/vX6yx9aK58fv959PplM1mswNGDlhRYOip1+txQzDma+wuoYNSqaTZUYvxnXAHMJag1WQyyYfDITdLJBLhbpEAQ8gVj8eThTBc03weoWeqqrLRaFQREgHoKfQRG3Pc9cKpW60WD4VCZKPS90IiAJA1FEVhkiTN58bjMaNIwNl7yhQA9VlLQ4Qpj9a2J1GWZZbJZBbmkBkMoHtFt6pPxbBvTGNCHZWWNqX7xalXriHkdNfrqZg7XBC4KTxWhSJgENGy0Onb7faPj1He4v7H9Xo9DGRrg1QqpaWY2SEAj+Vyua+0W6ZpQWn4YjCeWYrFIq0NiBWN70U54CWajcfj3LxGQmxIrEjsSCwplIrRP6N1cMI3Qmo2mxohCa0HQDYNNCfASAG8z7rd7nwtGo3+TUECJz6gWfB/DAT1QCAlViSgYq5gBxXXQDZhQrqpIFnlSAdNBwx4BWwUsPk15m7tqgnTepVjOPC2wX5fr4hse4xqv7Sf/J+yfPffcOsd+BRgAOvawAWTC+PMAAAAAElFTkSuQmCC', 7 | 'erd-1-only': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUJCNTBCRDA0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QkExNkM0MzQ0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQkI1MEJDRTQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQkI1MEJDRjQ4NjQxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pur5d4QAAAE1SURBVHjaYvz//z/DQAImhgEGow4YcAewaGpqjvAQoJI5CUAsD2VPB+JXULY4EGcCMSivPwTiBRgOuH79OkU2A6MwITAwcL6UlBSYP3XqVHGgmVlQufqcnJxMUFnz7NkzhvXr1zMA5VAdAZKkBGtoaBw4derUfxgA8v8jycHFQWpAatH1j2bDUQeACqIGaDahGgClfmLlWIDZpJ7SKhmWBUEgOzsbRDVgUwOUswcy7ZHFGf8PcIOABVhwUGwIsCBiQCqIUOSgIQIviNABI7BwaKDQfodFixbZm5qawuIYRDVC5ephJe3p06cZ4uLiDgKZB0ZLwtGCaNg1SBYAs5c9MJExIDVI4GxgtgQ1SBiePn3KgK1BAsqG1GoRKSA54CVaiwgEHtDSAUM3DTCO9g1HvAMAAgwAvvwYPnW1JuQAAAAASUVORK5CYII=', 8 | 'erd-N': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDc5MkNFMDg0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDc5MkNFMDk0ODY0MTFFN0JEQTA4NTA5QjY4NEI4MzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozNDQwRjc3NTQ4M0MxMUU3QkRBMDg1MDlCNjg0QjgzMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozNDQwRjc3NjQ4M0MxMUU3QkRBMDg1MDlCNjg0QjgzMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkbQDKwAAAGjSURBVHja7Jcxb4JQEMefLl272cjSUbYu0g+gSVemdpPZwKzRRSf4AGg70k8AfACTdqx+ibYJLp2MfoDr/wg0vJp2Al6TeskFeQ+4e/fufvdsEJFQKU2hWE4OKHeg0el0TlugQi6h5+kW1MkBXddbuMw0TRvu93txOBwumjUaH+Hy4jjOcLVaCcuyeHgmOAJVKpLcgAaDwYCSJKGi9Hq9yo273W6XwjCUDCP8NJlMCPP3VRm+hT6xETZWlCiKiJ3CvJfmX8mGW7wqDu16vZYMb7db4m3A/CP0On+HQTRHMpRVCiaS7Mq2bWlwuVyKxWLxjp/BEQkxQWWVYr/fF9/JipALrP5nFJPiA0EagbI+BsAI0zSlMV59HMe/NqN5mYg1DMPyPE+02+2vwc1mI6bTKTvzgNsP6Y0KSnAMfctySxLf97kKXqGj/PmqOJCWI7aDsHrJCaZhVo4BU7JqEt5Bn13XPQIS05GBVEcvOGPqsTE0IQnHtThQcOQGGnP4mYpZL/Bqc6CYpFkvGOcoVsEfPg3tVB7Jdn/nWH76b/jvHfgUYADeZNmzPgOYQQAAAABJRU5ErkJggg==' 9 | } 10 | 11 | export default type => { 12 | return base64[type] 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/js/editor/sql/MSSQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../../JSLog' 2 | import * as util from '../util' 3 | 4 | /** 5 | * MSSQL 6 | */ 7 | class MSSQL { 8 | constructor () { 9 | JSLog('module loaded', 'MSSQL') 10 | this.sql = null 11 | this.fkNames = [] 12 | } 13 | 14 | init (sql) { 15 | this.sql = sql 16 | return this 17 | } 18 | 19 | ddl (database) { 20 | this.fkNames = [] 21 | const stringBuffer = [] 22 | const tables = database.store.state.tables 23 | const lines = database.store.state.lines 24 | 25 | stringBuffer.push(`DROP DATABASE [${database.name}]\nGO`) 26 | stringBuffer.push('') 27 | stringBuffer.push(`CREATE DATABASE [${database.name}]\nGO`) 28 | stringBuffer.push('') 29 | stringBuffer.push(`USE [${database.name}]\nGO`) 30 | stringBuffer.push('') 31 | 32 | tables.forEach(table => { 33 | this.formatTable({ 34 | name: database.name, 35 | table: table, 36 | buffer: stringBuffer 37 | }) 38 | stringBuffer.push('') 39 | // 유니크 40 | if (util.isColumnOption('unique', table.columns)) { 41 | const uqColumns = util.getColumnOptions('unique', table.columns) 42 | uqColumns.forEach(column => { 43 | stringBuffer.push(`ALTER TABLE [${database.name}].[${table.name}]`) 44 | stringBuffer.push(`\tADD CONSTRAINT [UQ_${column.name}] UNIQUE ([${column.name}])\nGO`) 45 | stringBuffer.push('') 46 | }) 47 | } 48 | // 코멘트 추가 49 | this.formatComment({ 50 | name: database.name, 51 | table: table, 52 | buffer: stringBuffer 53 | }) 54 | }) 55 | lines.forEach(line => { 56 | this.formatRelation({ 57 | name: database.name, 58 | tables: tables, 59 | line: line, 60 | buffer: stringBuffer 61 | }) 62 | stringBuffer.push('') 63 | }) 64 | 65 | return stringBuffer.join('\n') 66 | } 67 | 68 | // 테이블 formatter 69 | formatTable ({ name, table, buffer }) { 70 | buffer.push(`CREATE TABLE [${name}].[${table.name}]`) 71 | buffer.push(`(`) 72 | const spaceSize = this.sql.formatSize(table.columns) 73 | const isPK = util.isColumnOption('primaryKey', table.columns) 74 | 75 | table.columns.forEach((column, i) => { 76 | if (isPK) { 77 | this.formatColumn({ 78 | column: column, 79 | isComma: true, 80 | spaceSize: spaceSize, 81 | buffer: buffer 82 | }) 83 | } else { 84 | this.formatColumn({ 85 | column: column, 86 | isComma: table.columns.length !== i + 1, 87 | spaceSize: spaceSize, 88 | buffer: buffer 89 | }) 90 | } 91 | }) 92 | // PK 93 | if (isPK) { 94 | const pkColumns = util.getColumnOptions('primaryKey', table.columns) 95 | buffer.push(`\tCONSTRAINT [PK_${table.name}] PRIMARY KEY (${this.sql.formatNames(pkColumns, '[', ']')})`) 96 | } 97 | buffer.push(`)\nGO`) 98 | } 99 | 100 | // 컬럼 formatter 101 | formatColumn ({ column, isComma, spaceSize, buffer }) { 102 | const stringBuffer = [] 103 | stringBuffer.push(`\t[${column.name}]` + this.sql.formatSpace(spaceSize.nameMax - column.name.length)) 104 | stringBuffer.push(`[${column.dataType}]` + this.sql.formatSpace(spaceSize.dataTypeMax - column.dataType.length)) 105 | // 옵션 Not NUll 106 | if (column.options.notNull) { 107 | stringBuffer.push(`NOT NULL`) 108 | } 109 | // 옵션 AUTO_INCREMENT 110 | if (column.options.autoIncrement) { 111 | stringBuffer.push(`IDENTITY`) 112 | } else { 113 | // 컬럼 DEFAULT 114 | if (column.default.trim() !== '') { 115 | stringBuffer.push(`DEFAULT ${column.default}`) 116 | } 117 | } 118 | buffer.push(stringBuffer.join(' ') + `${isComma ? ',' : ''}`) 119 | } 120 | 121 | // 코멘트 122 | formatComment ({ name, table, buffer }) { 123 | if (table.comment.trim() !== '') { 124 | buffer.push(`EXECUTE sys.sp_addextendedproperty 'MS_Description',`) 125 | buffer.push(`\t'${table.comment}', 'user', dbo, 'table', '${name}.${table.name}'\nGO`) 126 | buffer.push('') 127 | } 128 | table.columns.forEach(column => { 129 | if (column.comment.trim() !== '') { 130 | buffer.push(`EXECUTE sp_addextendedproperty 'MS_Description',`) 131 | buffer.push(`\t'${column.comment}', 'user', dbo, 'table', '${name}.${table.name}', 'column', '${table.name}'\nGO`) 132 | buffer.push('') 133 | } 134 | }) 135 | } 136 | 137 | // 관계 formatter 138 | formatRelation ({ name, tables, line, buffer }) { 139 | const startTable = util.getData(tables, line.points[0].id) 140 | const endTable = util.getData(tables, line.points[1].id) 141 | buffer.push(`ALTER TABLE [${name}].[${endTable.name}]`) 142 | 143 | // FK 중복 처리 144 | let fkName = `FK_${startTable.name}_TO_${endTable.name}` 145 | fkName = util.autoName(this.fkNames, fkName) 146 | this.fkNames.push({ name: fkName }) 147 | 148 | buffer.push(`\tADD CONSTRAINT [${fkName}]`) 149 | 150 | // key 컬럼 정제 151 | const columns = { 152 | start: [], 153 | end: [] 154 | } 155 | line.points[1].columnIds.forEach(id => { 156 | columns.end.push(util.getData(endTable.columns, id)) 157 | }) 158 | line.points[0].columnIds.forEach(id => { 159 | columns.start.push(util.getData(startTable.columns, id)) 160 | }) 161 | 162 | buffer.push(`\t\tFOREIGN KEY (${this.sql.formatNames(columns.end, '[', ']')})`) 163 | buffer.push(`\t\tREFERENCES [${name}].[${startTable.name}] (${this.sql.formatNames(columns.start, '[', ']')})\nGO`) 164 | } 165 | 166 | // 객체 정리 167 | destroy () {} 168 | } 169 | 170 | export default new MSSQL() 171 | -------------------------------------------------------------------------------- /src/renderer/js/editor/sql/MariaDB.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../../JSLog' 2 | import * as util from '../util' 3 | 4 | /** 5 | * MariaDB 6 | */ 7 | class MariaDB { 8 | constructor () { 9 | JSLog('module loaded', 'MariaDB') 10 | this.sql = null 11 | this.fkNames = [] 12 | } 13 | 14 | init (sql) { 15 | this.sql = sql 16 | return this 17 | } 18 | 19 | ddl (database) { 20 | this.fkNames = [] 21 | const stringBuffer = [] 22 | const tables = database.store.state.tables 23 | const lines = database.store.state.lines 24 | 25 | stringBuffer.push(`DROP DATABASE IF EXISTS ${database.name};`) 26 | stringBuffer.push('') 27 | stringBuffer.push(`CREATE DATABASE ${database.name};`) 28 | stringBuffer.push(`USE ${database.name}`) 29 | stringBuffer.push('') 30 | 31 | tables.forEach(table => { 32 | this.formatTable({ 33 | name: database.name, 34 | table: table, 35 | buffer: stringBuffer 36 | }) 37 | stringBuffer.push('') 38 | // 유니크 39 | if (util.isColumnOption('unique', table.columns)) { 40 | const uqColumns = util.getColumnOptions('unique', table.columns) 41 | uqColumns.forEach(column => { 42 | stringBuffer.push(`ALTER TABLE ${database.name}.${table.name}`) 43 | stringBuffer.push(`\tADD CONSTRAINT UQ_${column.name} UNIQUE (\`${column.name}\`);`) 44 | stringBuffer.push('') 45 | }) 46 | } 47 | }) 48 | lines.forEach(line => { 49 | this.formatRelation({ 50 | name: database.name, 51 | tables: tables, 52 | line: line, 53 | buffer: stringBuffer 54 | }) 55 | stringBuffer.push('') 56 | }) 57 | 58 | return stringBuffer.join('\n') 59 | } 60 | 61 | // 테이블 formatter 62 | formatTable ({ name, table, buffer }) { 63 | buffer.push(`CREATE TABLE ${name}.${table.name}`) 64 | buffer.push(`(`) 65 | const isPK = util.isColumnOption('primaryKey', table.columns) 66 | const spaceSize = this.sql.formatSize(table.columns) 67 | 68 | table.columns.forEach((column, i) => { 69 | if (isPK) { 70 | this.formatColumn({ 71 | column: column, 72 | isComma: true, 73 | spaceSize: spaceSize, 74 | buffer: buffer 75 | }) 76 | } else { 77 | this.formatColumn({ 78 | column: column, 79 | isComma: table.columns.length !== i + 1, 80 | spaceSize: spaceSize, 81 | buffer: buffer 82 | }) 83 | } 84 | }) 85 | // PK 86 | if (isPK) { 87 | const pkColumns = util.getColumnOptions('primaryKey', table.columns) 88 | buffer.push(`\tPRIMARY KEY (${this.sql.formatNames(pkColumns, '`')})`) 89 | } 90 | // 코멘트 처리 91 | if (table.comment.trim() === '') { 92 | buffer.push(`);`) 93 | } else { 94 | buffer.push(`) COMMENT '${table.comment}';`) 95 | } 96 | } 97 | 98 | // 컬럼 formatter 99 | formatColumn ({ column, isComma, spaceSize, buffer }) { 100 | const stringBuffer = [] 101 | stringBuffer.push(`\t\`${column.name}\`` + this.sql.formatSpace(spaceSize.nameMax - column.name.length)) 102 | stringBuffer.push(`${column.dataType}` + this.sql.formatSpace(spaceSize.dataTypeMax - column.dataType.length)) 103 | stringBuffer.push(`${column.options.notNull ? 'NOT NULL' : 'NULL '}`) 104 | // 옵션 AUTO_INCREMENT 105 | if (column.options.autoIncrement) { 106 | stringBuffer.push(`AUTO_INCREMENT`) 107 | } else { 108 | // 컬럼 DEFAULT 109 | if (column.default.trim() !== '') { 110 | stringBuffer.push(`DEFAULT ${column.default}`) 111 | } 112 | } 113 | // 코멘트 처리 114 | if (column.comment.trim() !== '') { 115 | stringBuffer.push(`COMMENT '${column.comment}'`) 116 | } 117 | buffer.push(stringBuffer.join(' ') + `${isComma ? ',' : ''}`) 118 | } 119 | 120 | // 관계 formatter 121 | formatRelation ({ name, tables, line, buffer }) { 122 | const startTable = util.getData(tables, line.points[0].id) 123 | const endTable = util.getData(tables, line.points[1].id) 124 | buffer.push(`ALTER TABLE ${name}.${endTable.name}`) 125 | 126 | // FK 중복 처리 127 | let fkName = `FK_${startTable.name}_TO_${endTable.name}` 128 | fkName = util.autoName(this.fkNames, fkName) 129 | this.fkNames.push({ name: fkName }) 130 | 131 | buffer.push(`\tADD CONSTRAINT ${fkName}`) 132 | 133 | // key 컬럼 정제 134 | const columns = { 135 | start: [], 136 | end: [] 137 | } 138 | line.points[1].columnIds.forEach(id => { 139 | columns.end.push(util.getData(endTable.columns, id)) 140 | }) 141 | line.points[0].columnIds.forEach(id => { 142 | columns.start.push(util.getData(startTable.columns, id)) 143 | }) 144 | 145 | buffer.push(`\t\tFOREIGN KEY (${this.sql.formatNames(columns.end, '`')})`) 146 | buffer.push(`\t\tREFERENCES ${name}.${startTable.name} (${this.sql.formatNames(columns.start, '`')});`) 147 | } 148 | 149 | // 객체 정리 150 | destroy () {} 151 | } 152 | 153 | export default new MariaDB() 154 | -------------------------------------------------------------------------------- /src/renderer/js/editor/sql/MySQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../../JSLog' 2 | import * as util from '../util' 3 | 4 | /** 5 | * MySQL 6 | */ 7 | class MySQL { 8 | constructor () { 9 | JSLog('module loaded', 'MySQL') 10 | this.sql = null 11 | this.fkNames = [] 12 | } 13 | 14 | init (sql) { 15 | this.sql = sql 16 | return this 17 | } 18 | 19 | ddl (database) { 20 | this.fkNames = [] 21 | const stringBuffer = [] 22 | const tables = database.store.state.tables 23 | const lines = database.store.state.lines 24 | 25 | stringBuffer.push(`DROP SCHEMA IF EXISTS \`${database.name}\`;`) 26 | stringBuffer.push('') 27 | stringBuffer.push(`CREATE SCHEMA \`${database.name}\`;`) 28 | stringBuffer.push(`USE \`${database.name}\`;`) 29 | stringBuffer.push('') 30 | 31 | tables.forEach(table => { 32 | this.formatTable({ 33 | name: database.name, 34 | table: table, 35 | buffer: stringBuffer 36 | }) 37 | stringBuffer.push('') 38 | // 유니크 39 | if (util.isColumnOption('unique', table.columns)) { 40 | const uqColumns = util.getColumnOptions('unique', table.columns) 41 | uqColumns.forEach(column => { 42 | stringBuffer.push(`ALTER TABLE \`${database.name}\`.\`${table.name}\``) 43 | stringBuffer.push(`\tADD CONSTRAINT \`UQ_${column.name}\` UNIQUE (\`${column.name}\`);`) 44 | stringBuffer.push('') 45 | }) 46 | } 47 | }) 48 | lines.forEach(line => { 49 | this.formatRelation({ 50 | name: database.name, 51 | tables: tables, 52 | line: line, 53 | buffer: stringBuffer 54 | }) 55 | stringBuffer.push('') 56 | }) 57 | 58 | return stringBuffer.join('\n') 59 | } 60 | 61 | // 테이블 formatter 62 | formatTable ({ name, table, buffer }) { 63 | buffer.push(`CREATE TABLE \`${name}\`.\`${table.name}\``) 64 | buffer.push(`(`) 65 | const spaceSize = this.sql.formatSize(table.columns) 66 | const isPK = util.isColumnOption('primaryKey', table.columns) 67 | 68 | table.columns.forEach((column, i) => { 69 | if (isPK) { 70 | this.formatColumn({ 71 | column: column, 72 | isComma: true, 73 | spaceSize: spaceSize, 74 | buffer: buffer 75 | }) 76 | } else { 77 | this.formatColumn({ 78 | column: column, 79 | isComma: table.columns.length !== i + 1, 80 | spaceSize: spaceSize, 81 | buffer: buffer 82 | }) 83 | } 84 | }) 85 | // PK 86 | if (isPK) { 87 | const pkColumns = util.getColumnOptions('primaryKey', table.columns) 88 | buffer.push(`\tPRIMARY KEY (${this.sql.formatNames(pkColumns, '`')})`) 89 | } 90 | // 코멘트 처리 91 | if (table.comment.trim() === '') { 92 | buffer.push(`);`) 93 | } else { 94 | buffer.push(`) COMMENT '${table.comment}';`) 95 | } 96 | } 97 | 98 | // 컬럼 formatter 99 | formatColumn ({ column, isComma, spaceSize, buffer }) { 100 | const stringBuffer = [] 101 | stringBuffer.push(`\t\`${column.name}\`` + this.sql.formatSpace(spaceSize.nameMax - column.name.length)) 102 | stringBuffer.push(`${column.dataType}` + this.sql.formatSpace(spaceSize.dataTypeMax - column.dataType.length)) 103 | stringBuffer.push(`${column.options.notNull ? 'NOT NULL' : 'NULL '}`) 104 | // 옵션 AUTO_INCREMENT 105 | if (column.options.autoIncrement) { 106 | stringBuffer.push(`AUTO_INCREMENT`) 107 | } else { 108 | // 컬럼 DEFAULT 109 | if (column.default.trim() !== '') { 110 | stringBuffer.push(`DEFAULT ${column.default}`) 111 | } 112 | } 113 | // 코멘트 처리 114 | if (column.comment.trim() !== '') { 115 | stringBuffer.push(`COMMENT '${column.comment}'`) 116 | } 117 | buffer.push(stringBuffer.join(' ') + `${isComma ? ',' : ''}`) 118 | } 119 | 120 | // 관계 formatter 121 | formatRelation ({ name, tables, line, buffer }) { 122 | const startTable = util.getData(tables, line.points[0].id) 123 | const endTable = util.getData(tables, line.points[1].id) 124 | buffer.push(`ALTER TABLE \`${name}\`.\`${endTable.name}\``) 125 | 126 | // FK 중복 처리 127 | let fkName = `FK_${startTable.name}_TO_${endTable.name}` 128 | fkName = util.autoName(this.fkNames, fkName) 129 | this.fkNames.push({ name: fkName }) 130 | 131 | buffer.push(`\tADD CONSTRAINT \`${fkName}\``) 132 | 133 | // key 컬럼 정제 134 | const columns = { 135 | start: [], 136 | end: [] 137 | } 138 | line.points[1].columnIds.forEach(id => { 139 | columns.end.push(util.getData(endTable.columns, id)) 140 | }) 141 | line.points[0].columnIds.forEach(id => { 142 | columns.start.push(util.getData(startTable.columns, id)) 143 | }) 144 | 145 | buffer.push(`\t\tFOREIGN KEY (${this.sql.formatNames(columns.end, '`')})`) 146 | buffer.push(`\t\tREFERENCES \`${name}\`.\`${startTable.name}\` (${this.sql.formatNames(columns.start, '`')});`) 147 | } 148 | 149 | // 객체 정리 150 | destroy () {} 151 | } 152 | 153 | export default new MySQL() 154 | -------------------------------------------------------------------------------- /src/renderer/js/editor/sql/Oracle.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../../JSLog' 2 | import * as util from '../util' 3 | 4 | /** 5 | * Oracle 6 | */ 7 | class Oracle { 8 | constructor () { 9 | JSLog('module loaded', 'Oracle') 10 | this.sql = null 11 | this.fkNames = [] 12 | this.aiNames = [] 13 | this.trgNames = [] 14 | } 15 | 16 | init (sql) { 17 | this.sql = sql 18 | return this 19 | } 20 | 21 | ddl (database) { 22 | this.fkNames = [] 23 | this.aiNames = [] 24 | this.trgNames = [] 25 | const stringBuffer = [] 26 | const tables = database.store.state.tables 27 | const lines = database.store.state.lines 28 | 29 | tables.forEach(table => { 30 | this.formatTable({ 31 | name: database.name, 32 | table: table, 33 | buffer: stringBuffer 34 | }) 35 | stringBuffer.push('') 36 | // 유니크 37 | if (util.isColumnOption('unique', table.columns)) { 38 | const uqColumns = util.getColumnOptions('unique', table.columns) 39 | uqColumns.forEach(column => { 40 | stringBuffer.push(`ALTER TABLE ${database.name}.${table.name}`) 41 | stringBuffer.push(`\tADD CONSTRAINT UQ_${column.name} UNIQUE (${column.name});`) 42 | stringBuffer.push('') 43 | }) 44 | } 45 | // 시퀀스 추가 46 | table.columns.forEach(column => { 47 | if (column.options.autoIncrement) { 48 | let aiName = `SEQ_${table.name}` 49 | aiName = util.autoName(this.aiNames, aiName) 50 | this.aiNames.push({ name: aiName }) 51 | 52 | stringBuffer.push(`CREATE SEQUENCE ${database.name}.${aiName}`) 53 | stringBuffer.push(`START WITH 1`) 54 | stringBuffer.push(`INCREMENT BY 1;`) 55 | stringBuffer.push('') 56 | 57 | let trgName = `SEQ_TRG_${table.name}` 58 | trgName = util.autoName(this.aiNames, trgName) 59 | this.trgNames.push({ name: trgName }) 60 | stringBuffer.push(`CREATE OR REPLACE TRIGGER ${database.name}.${trgName}`) 61 | stringBuffer.push(`BEFORE INSERT ON ${database.name}.${table.name}`) 62 | stringBuffer.push(`REFERENCING NEW AS NEW FOR EACH ROW`) 63 | stringBuffer.push(`BEGIN`) 64 | stringBuffer.push(`\tSELECT ${database.name}.${aiName}.NEXTVAL`) 65 | stringBuffer.push(`\tINTO: NEW.${column.name}`) 66 | stringBuffer.push(`\tFROM DUAL;`) 67 | stringBuffer.push(`END;`) 68 | stringBuffer.push('') 69 | } 70 | }) 71 | // 코멘트 추가 72 | this.formatComment({ 73 | name: database.name, 74 | table: table, 75 | buffer: stringBuffer 76 | }) 77 | }) 78 | lines.forEach(line => { 79 | this.formatRelation({ 80 | name: database.name, 81 | tables: tables, 82 | line: line, 83 | buffer: stringBuffer 84 | }) 85 | stringBuffer.push('') 86 | }) 87 | 88 | return stringBuffer.join('\n') 89 | } 90 | 91 | // 테이블 92 | formatTable ({ name, table, buffer }) { 93 | buffer.push(`CREATE TABLE ${name}.${table.name}`) 94 | buffer.push(`(`) 95 | const isPK = util.isColumnOption('primaryKey', table.columns) 96 | const spaceSize = this.sql.formatSize(table.columns) 97 | 98 | table.columns.forEach((column, i) => { 99 | if (isPK) { 100 | this.formatColumn({ 101 | column: column, 102 | isComma: true, 103 | spaceSize: spaceSize, 104 | buffer: buffer 105 | }) 106 | } else { 107 | this.formatColumn({ 108 | column: column, 109 | isComma: table.columns.length !== i + 1, 110 | spaceSize: spaceSize, 111 | buffer: buffer 112 | }) 113 | } 114 | }) 115 | // PK 116 | if (isPK) { 117 | const pkColumns = util.getColumnOptions('primaryKey', table.columns) 118 | buffer.push(`\tCONSTRAINT PK_${table.name} PRIMARY KEY (${this.sql.formatNames(pkColumns)})`) 119 | } 120 | buffer.push(`);`) 121 | } 122 | 123 | // 컬럼 124 | formatColumn ({ column, isComma, spaceSize, buffer }) { 125 | const stringBuffer = [] 126 | stringBuffer.push(`\t${column.name}` + this.sql.formatSpace(spaceSize.nameMax - column.name.length)) 127 | stringBuffer.push(`${column.dataType}` + this.sql.formatSpace(spaceSize.dataTypeMax - column.dataType.length)) 128 | // 옵션 Not NUll 129 | if (column.options.notNull) { 130 | stringBuffer.push(`NOT NULL`) 131 | } 132 | // 컬럼 DEFAULT 133 | if (column.default.trim() !== '') { 134 | stringBuffer.push(`DEFAULT ${column.default}`) 135 | } 136 | buffer.push(stringBuffer.join(' ') + `${isComma ? ',' : ''}`) 137 | } 138 | 139 | // 코멘트 140 | formatComment ({ name, table, buffer }) { 141 | if (table.comment.trim() !== '') { 142 | buffer.push(`COMMENT ON TABLE ${name}.${table.name} IS '${table.comment}';`) 143 | buffer.push('') 144 | } 145 | table.columns.forEach(column => { 146 | if (column.comment.trim() !== '') { 147 | buffer.push(`COMMENT ON COLUMN ${name}.${table.name}.${column.name} IS '${column.comment}';`) 148 | buffer.push('') 149 | } 150 | }) 151 | } 152 | 153 | // 관계 154 | formatRelation ({ name, tables, line, buffer }) { 155 | const startTable = util.getData(tables, line.points[0].id) 156 | const endTable = util.getData(tables, line.points[1].id) 157 | buffer.push(`ALTER TABLE ${name}.${endTable.name}`) 158 | 159 | // FK 중복 처리 160 | let fkName = `FK_${startTable.name}_TO_${endTable.name}` 161 | fkName = util.autoName(this.fkNames, fkName) 162 | this.fkNames.push({ name: fkName }) 163 | 164 | buffer.push(`\tADD CONSTRAINT ${fkName}`) 165 | 166 | // key 컬럼 정제 167 | const columns = { 168 | start: [], 169 | end: [] 170 | } 171 | line.points[1].columnIds.forEach(id => { 172 | columns.end.push(util.getData(endTable.columns, id)) 173 | }) 174 | line.points[0].columnIds.forEach(id => { 175 | columns.start.push(util.getData(startTable.columns, id)) 176 | }) 177 | 178 | buffer.push(`\t\tFOREIGN KEY (${this.sql.formatNames(columns.end)})`) 179 | buffer.push(`\t\tREFERENCES ${name}.${startTable.name} (${this.sql.formatNames(columns.start)});`) 180 | } 181 | 182 | // 객체 정리 183 | destroy () {} 184 | } 185 | 186 | export default new Oracle() 187 | -------------------------------------------------------------------------------- /src/renderer/js/editor/sql/PostgreSQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '../../JSLog' 2 | import * as util from '../util' 3 | 4 | /** 5 | * PostgreSQL 6 | */ 7 | class PostgreSQL { 8 | constructor () { 9 | JSLog('module loaded', 'MSSQL') 10 | this.sql = null 11 | this.fkNames = [] 12 | } 13 | 14 | init (sql) { 15 | this.sql = sql 16 | return this 17 | } 18 | 19 | ddl (database) { 20 | this.fkNames = [] 21 | const stringBuffer = [] 22 | const tables = database.store.state.tables 23 | const lines = database.store.state.lines 24 | 25 | stringBuffer.push(`DROP SCHEMA IF EXISTS "${database.name}" RESTRICT;`) 26 | stringBuffer.push('') 27 | stringBuffer.push(`CREATE SCHEMA "${database.name}";`) 28 | stringBuffer.push('') 29 | 30 | tables.forEach(table => { 31 | this.formatTable({ 32 | name: database.name, 33 | table: table, 34 | buffer: stringBuffer 35 | }) 36 | stringBuffer.push('') 37 | // 코멘트 추가 38 | this.formatComment({ 39 | name: database.name, 40 | table: table, 41 | buffer: stringBuffer 42 | }) 43 | }) 44 | lines.forEach(line => { 45 | this.formatRelation({ 46 | name: database.name, 47 | tables: tables, 48 | line: line, 49 | buffer: stringBuffer 50 | }) 51 | stringBuffer.push('') 52 | }) 53 | 54 | return stringBuffer.join('\n') 55 | } 56 | 57 | // 테이블 formatter 58 | formatTable ({ name, table, buffer }) { 59 | buffer.push(`CREATE TABLE "${name}"."${table.name}"`) 60 | buffer.push(`(`) 61 | const spaceSize = this.sql.formatSize(table.columns) 62 | const isPK = util.isColumnOption('primaryKey', table.columns) 63 | 64 | table.columns.forEach((column, i) => { 65 | if (isPK) { 66 | this.formatColumn({ 67 | column: column, 68 | isComma: true, 69 | spaceSize: spaceSize, 70 | buffer: buffer 71 | }) 72 | } else { 73 | this.formatColumn({ 74 | column: column, 75 | isComma: table.columns.length !== i + 1, 76 | spaceSize: spaceSize, 77 | buffer: buffer 78 | }) 79 | } 80 | }) 81 | // PK 82 | if (isPK) { 83 | const pkColumns = util.getColumnOptions('primaryKey', table.columns) 84 | buffer.push(`\tPRIMARY KEY (${this.sql.formatNames(pkColumns, '"')})`) 85 | } 86 | buffer.push(`);`) 87 | } 88 | 89 | // 컬럼 formatter 90 | formatColumn ({ column, isComma, spaceSize, buffer }) { 91 | const stringBuffer = [] 92 | stringBuffer.push(`\t"${column.name}"` + this.sql.formatSpace(spaceSize.nameMax - column.name.length)) 93 | stringBuffer.push(`${column.dataType}` + this.sql.formatSpace(spaceSize.dataTypeMax - column.dataType.length)) 94 | // 옵션 Not NUll 95 | if (column.options.notNull) { 96 | stringBuffer.push(`NOT NULL`) 97 | } 98 | // 유니크 99 | if (column.options.unique) { 100 | stringBuffer.push(`UNIQUE`) 101 | } else { 102 | // 컬럼 DEFAULT 103 | if (column.default.trim() !== '') { 104 | stringBuffer.push(`DEFAULT ${column.default}`) 105 | } 106 | } 107 | buffer.push(stringBuffer.join(' ') + `${isComma ? ',' : ''}`) 108 | } 109 | 110 | // 코멘트 111 | formatComment ({ name, table, buffer }) { 112 | if (table.comment.trim() !== '') { 113 | buffer.push(`COMMENT ON TABLE "${name}"."${table.name}" IS '${table.comment}';`) 114 | buffer.push('') 115 | } 116 | table.columns.forEach(column => { 117 | if (column.comment.trim() !== '') { 118 | buffer.push(`COMMENT ON COLUMN "${name}"."${table.name}"."${column.name}" IS '${column.comment}';`) 119 | buffer.push('') 120 | } 121 | }) 122 | } 123 | 124 | // 관계 formatter 125 | formatRelation ({ name, tables, line, buffer }) { 126 | const startTable = util.getData(tables, line.points[0].id) 127 | const endTable = util.getData(tables, line.points[1].id) 128 | buffer.push(`ALTER TABLE "${name}"."${endTable.name}"`) 129 | 130 | // FK 중복 처리 131 | let fkName = `FK_${startTable.name}_TO_${endTable.name}` 132 | fkName = util.autoName(this.fkNames, fkName) 133 | this.fkNames.push(fkName) 134 | 135 | buffer.push(`\tADD CONSTRAINT "${fkName}"`) 136 | 137 | // key 컬럼 정제 138 | const columns = { 139 | start: [], 140 | end: [] 141 | } 142 | line.points[1].columnIds.forEach(id => { 143 | columns.end.push(util.getData(endTable.columns, id)) 144 | }) 145 | line.points[0].columnIds.forEach(id => { 146 | columns.start.push(util.getData(startTable.columns, id)) 147 | }) 148 | 149 | buffer.push(`\t\tFOREIGN KEY (${this.sql.formatNames(columns.end, '"')})`) 150 | buffer.push(`\t\tREFERENCES "${name}"."${startTable.name}" (${this.sql.formatNames(columns.start, '"')});`) 151 | } 152 | 153 | // 객체 정리 154 | destroy () {} 155 | } 156 | 157 | export default new PostgreSQL() 158 | -------------------------------------------------------------------------------- /src/renderer/js/fontawesome.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { library } from '@fortawesome/fontawesome-svg-core' 3 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 4 | import { 5 | faKey, 6 | faEye, 7 | faPlus, 8 | faList, 9 | faUndo, 10 | faRedo, 11 | faFile, 12 | faCopy, 13 | faSave, 14 | faBook, 15 | faTable, 16 | faTimes, 17 | faFolder, 18 | faHistory, 19 | faTrashAlt, 20 | faDatabase, 21 | faQuestion, 22 | faFolderOpen, 23 | faFileImport, 24 | faFileExport, 25 | faStickyNote, 26 | faExpandArrowsAlt 27 | } from '@fortawesome/free-solid-svg-icons' 28 | 29 | library.add( 30 | faKey, 31 | faEye, 32 | faPlus, 33 | faList, 34 | faUndo, 35 | faRedo, 36 | faFile, 37 | faCopy, 38 | faSave, 39 | faBook, 40 | faTable, 41 | faTimes, 42 | faFolder, 43 | faHistory, 44 | faTrashAlt, 45 | faDatabase, 46 | faQuestion, 47 | faFolderOpen, 48 | faFileImport, 49 | faFileExport, 50 | faStickyNote, 51 | faExpandArrowsAlt 52 | ) 53 | 54 | Vue.component('font-awesome-icon', FontAwesomeIcon) 55 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import './js/fontawesome' 8 | 9 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 10 | Vue.http = Vue.prototype.$http = axios 11 | Vue.prototype.$event = new Vue() 12 | Vue.config.productionTip = false 13 | 14 | /* eslint-disable no-new */ 15 | new Vue({ 16 | components: { App }, 17 | router, 18 | store, 19 | template: '' 20 | }).$mount('#app') 21 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import ERD from '../views/ERD' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'ERD', 12 | component: ERD 13 | }, 14 | { 15 | path: '*', 16 | redirect: '/' 17 | } 18 | ] 19 | }) 20 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import MySQL from './dataType/MySQL' 3 | import Oracle from './dataType/Oracle' 4 | import MariaDB from './dataType/MariaDB' 5 | import MSSQL from './dataType/MSSQL' 6 | import PostgreSQL from './dataType/PostgreSQL' 7 | 8 | JSLog('store loaded', 'dataType') 9 | 10 | export default { 11 | MySQL: MySQL, 12 | Oracle: Oracle, 13 | MariaDB: MariaDB, 14 | MSSQL: MSSQL, 15 | PostgreSQL: PostgreSQL 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType/MSSQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | 3 | JSLog('store loaded', 'MSSQL') 4 | 5 | const MSSQL = [ 6 | { name: 'int' }, 7 | { name: 'tinyint' }, 8 | { name: 'smallint' }, 9 | { name: 'bigint' }, 10 | { name: 'varchar' }, 11 | { name: 'nvarchar' }, 12 | { name: 'char' }, 13 | { name: 'nchar' }, 14 | { name: 'text' }, 15 | { name: 'ntext' }, 16 | { name: 'date' }, 17 | { name: 'datetime' }, 18 | { name: 'decimal' }, 19 | { name: 'numeric' }, 20 | { name: 'bit' }, 21 | { name: 'datetime2' }, 22 | { name: 'datetimeoffset' }, 23 | { name: 'float' }, 24 | { name: 'binary' }, 25 | { name: 'money' }, 26 | { name: 'real' }, 27 | { name: 'smallmoney' }, 28 | { name: 'variant' }, 29 | { name: 'time' }, 30 | { name: 'timestamp' }, 31 | { name: 'uniqueidentifier' }, 32 | { name: 'varbinary' }, 33 | { name: 'xml' } 34 | ] 35 | 36 | export default MSSQL 37 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType/MariaDB.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | 3 | JSLog('store loaded', 'MariaDB') 4 | 5 | const MariaDB = [ 6 | { name: 'INT' }, 7 | { name: 'VARCHAR' }, 8 | { name: 'DECIMAL' }, 9 | { name: 'DATETIME' }, 10 | { name: 'BLOB' }, 11 | { name: 'CHAR' }, 12 | { name: 'TINYTEXT' }, 13 | { name: 'TEXT' }, 14 | { name: 'MEDIUMTEXT' }, 15 | { name: 'LONGTEXT' }, 16 | { name: 'TINYBLOB' }, 17 | { name: 'MEDIUMBLOB' }, 18 | { name: 'LONGBLOB' }, 19 | { name: 'BIT' }, 20 | { name: 'TINYINT' }, 21 | { name: 'SMALLINT' }, 22 | { name: 'MEDIUMINT' }, 23 | { name: 'INTEGER' }, 24 | { name: 'BIGINT' }, 25 | { name: 'FLOAT' }, 26 | { name: 'DOUBLE' }, 27 | { name: 'DOUBLE PRECISION' }, 28 | { name: 'NUMERIC' }, 29 | { name: 'DATE' }, 30 | { name: 'TIMESTAMP' }, 31 | { name: 'TIME' }, 32 | { name: 'YEAR' }, 33 | { name: 'ENUM' }, 34 | { name: 'REAL' }, 35 | { name: 'SET' } 36 | ] 37 | 38 | export default MariaDB 39 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType/MySQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | 3 | JSLog('store loaded', 'MySQL') 4 | 5 | const MySQL = [ 6 | { name: 'BLOB' }, 7 | { name: 'BINARY' }, 8 | { name: 'LONGBLOB' }, 9 | { name: 'MEDIUMBLOB' }, 10 | { name: 'TINYBLOB' }, 11 | { name: 'VARBINARY' }, 12 | 13 | { name: 'DATE' }, 14 | { name: 'DATETIME' }, 15 | { name: 'TIME' }, 16 | { name: 'TIMESTAMP' }, 17 | { name: 'YEAR' }, 18 | 19 | { name: 'GEOMETRY' }, 20 | { name: 'GEOMETRYCOLLECTION' }, 21 | { name: 'LINESTRING' }, 22 | { name: 'MULTILINESTRING' }, 23 | { name: 'MULTIPOINT' }, 24 | { name: 'MULTIPOLYGON' }, 25 | { name: 'POINT' }, 26 | { name: 'POLYGON' }, 27 | 28 | { name: 'BIGINT' }, 29 | { name: 'DECIMAL' }, 30 | { name: 'DOUBLE' }, 31 | { name: 'FLOAT' }, 32 | { name: 'INT' }, 33 | { name: 'MEDIUMINT' }, 34 | { name: 'REAL' }, 35 | { name: 'SMALLINT' }, 36 | { name: 'TINYINT' }, 37 | 38 | { name: 'CHAR' }, 39 | { name: 'JSON' }, 40 | { name: 'NCHAR' }, 41 | { name: 'NVARCHAR' }, 42 | { name: 'VARCHAR' }, 43 | 44 | { name: 'LONGTEXT' }, 45 | { name: 'MEDIUMTEXT' }, 46 | { name: 'TEXT' }, 47 | { name: 'TINYTEXT' }, 48 | 49 | { name: 'BIT' }, 50 | { name: 'BOOLEAN' }, 51 | { name: 'ENUM' }, 52 | { name: 'SET' } 53 | ] 54 | 55 | export default MySQL 56 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType/Oracle.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | 3 | JSLog('store loaded', 'Oracle') 4 | 5 | const Oracle = [ 6 | { name: 'VARCHAR2' }, 7 | { name: 'NVARCHAR2' }, 8 | { name: 'CHAR' }, 9 | { name: 'NCHAR' }, 10 | { name: 'INT' }, 11 | { name: 'NUMBER' }, 12 | { name: 'DATE' }, 13 | { name: 'CLOB' }, 14 | { name: 'BLOB' }, 15 | { name: 'BFILE' }, 16 | { name: 'FLOAT' }, 17 | { name: 'INTERVAL' }, 18 | { name: 'LONG' }, 19 | { name: 'LONG RAW' }, 20 | { name: 'NCLOB' }, 21 | { name: 'RAW' }, 22 | { name: 'REAL' }, 23 | { name: 'ROWID' }, 24 | { name: 'TIMESTAMP' }, 25 | { name: 'UROWID' }, 26 | { name: 'VARCHAR' }, 27 | { name: 'BINARY_DOUBLE' }, 28 | { name: 'BINARY_FLOAT' } 29 | ] 30 | 31 | export default Oracle 32 | -------------------------------------------------------------------------------- /src/renderer/store/editor/dataType/PostgreSQL.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | 3 | JSLog('store loaded', 'PostgreSQL') 4 | 5 | const PostgreSQL = [ 6 | { name: 'bigint' }, 7 | { name: 'int8' }, 8 | { name: 'bigserial' }, 9 | { name: 'serial8' }, 10 | { name: 'bit' }, 11 | { name: 'bit varying' }, 12 | { name: 'varbit' }, 13 | { name: 'boolean' }, 14 | { name: 'bool' }, 15 | { name: 'box' }, 16 | { name: 'bytea' }, 17 | { name: 'character varying' }, 18 | { name: 'varchar' }, 19 | { name: 'character' }, 20 | { name: 'char' }, 21 | { name: 'cidr' }, 22 | { name: 'circle' }, 23 | { name: 'date' }, 24 | { name: 'double precision' }, 25 | { name: 'float8' }, 26 | { name: 'inet' }, 27 | { name: 'integer' }, 28 | { name: 'int' }, 29 | { name: 'int4' }, 30 | { name: 'interval' }, 31 | { name: 'line' }, 32 | { name: 'lseg' }, 33 | { name: 'macaddr' }, 34 | { name: 'money' }, 35 | { name: 'numeric' }, 36 | { name: 'decimal' }, 37 | { name: 'path' }, 38 | { name: 'point' }, 39 | { name: 'polygon' }, 40 | { name: 'real' }, 41 | { name: 'float4' }, 42 | { name: 'smallint' }, 43 | { name: 'int2' }, 44 | { name: 'smallserial' }, 45 | { name: 'serial' }, 46 | { name: 'text' }, 47 | { name: 'time' }, 48 | { name: 'timetz' }, 49 | { name: 'timestamp' }, 50 | { name: 'timestamptz' }, 51 | { name: 'tsquery' }, 52 | { name: 'tsvector' }, 53 | { name: 'txid_snapshot' }, 54 | { name: 'uuid' }, 55 | { name: 'xml' }, 56 | { name: 'json' } 57 | ] 58 | 59 | export default PostgreSQL 60 | -------------------------------------------------------------------------------- /src/renderer/store/editor/erd.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import dataType from './dataType' 5 | import table from './mutationsTable' 6 | import column from './mutationsColumn' 7 | import line from './mutationsLine' 8 | import memo from './mutationsMemo' 9 | import domain from './mutationsDomain' 10 | import * as util from '@/js/editor/util' 11 | import ERD from '@/js/editor/ERD' 12 | 13 | JSLog('store loaded', 'erd') 14 | Vue.use(Vuex) 15 | 16 | // ERD 데이터 17 | export default () => { 18 | return new Vuex.Store({ 19 | state: { 20 | TABLE_WIDTH: 350, 21 | TABLE_HEIGHT: 84, 22 | COLUMN_WIDTH: 50, 23 | COLUMN_HEIGHT: 25, 24 | PREVIEW_WIDTH: 150, 25 | CANVAS_WIDTH: 5000, 26 | CANVAS_HEIGHT: 5000, 27 | MEMO_WIDTH: 150, 28 | MEMO_HEIGHT: 100, 29 | DBType: 'MySQL', 30 | dataTypes: dataType['MySQL'], 31 | tables: [], 32 | lines: [], 33 | memos: [], 34 | domains: [], 35 | searchDomains: [] 36 | }, 37 | mutations: { 38 | // DB 변경 39 | changeDB (state, data) { 40 | JSLog('mutations', 'erd', 'changeDB') 41 | state.DBType = data.DBType 42 | state.dataTypes = dataType[data.DBType] 43 | }, 44 | // 데이터타입 검색 45 | changeDataTypeHint (state, data) { 46 | JSLog('mutations', 'erd', 'changeDataTypeHint') 47 | state.dataTypes = dataType[state.DBType].filter(v => { 48 | return v.name.toLowerCase().indexOf(data.key.toLowerCase()) !== -1 49 | }) 50 | }, 51 | // 도메인 검색 52 | changeDomainHint (state, data) { 53 | JSLog('mutations', 'erd', 'changeDomainHint') 54 | state.searchDomains = state.domains.filter(v => { 55 | return v.name.toLowerCase().indexOf(data.key.toLowerCase()) !== -1 56 | }) 57 | }, 58 | // 전체 import 59 | importData (state, data) { 60 | JSLog('mutations', 'erd', 'importData') 61 | Object.keys(state).forEach(key => { 62 | state[key] = data.state[key] 63 | }) 64 | }, 65 | // 환경설정 66 | setConfig (state, data) { 67 | ERD.core.event.components.CanvasMenu.isSave = false 68 | util.setData(state, data.config) 69 | }, 70 | // 테이블 추가 71 | tableAdd: table.add, 72 | // 테이블 삭제 73 | tableDelete: table.delete, 74 | // 테이블 높이 리셋 75 | tableHeightReset: table.heightReset, 76 | // 테이블 선택 77 | tableSelected: table.selected, 78 | // 테이블 top, left 변경 79 | tableDraggable: table.draggable, 80 | // 테이블 및 컬럼 selected All 해제 81 | tableSelectedAllNone: table.selectedAllNone, 82 | // 테이블 드래그 multi selected 83 | tableMultiSelected: table.multiSelected, 84 | // 테이블 전체 선택 85 | tableSelectedAll: table.selectedAll, 86 | // 테이블 편집모드 87 | tableEdit: table.edit, 88 | // 테이블 및 컬럼 edit all 해제 89 | tableEditAllNone: table.editAllNone, 90 | // 컬럼 추가 91 | columnAdd: column.add, 92 | // 컬럼 삭제 93 | columnDelete: column.delete, 94 | // 컬럼 NULL 조건 변경 95 | columnChangeNull: column.changeNull, 96 | // 컬럼 선택 97 | columnSelected: column.selected, 98 | // 컬럼 key active 99 | columnKey: column.key, 100 | // 컬럼 데이터변경 101 | columnChangeDataType: column.changeDataType, 102 | // 컬럼 데이터타입 힌트 show/hide 103 | columnDataTypeHintVisible: column.dataTypeHintVisible, 104 | // 컬럼 데이터타입 힌트 show/hide ALL 105 | columnDataTypeHintVisibleAll: column.dataTypeHintVisibleAll, 106 | // 컬럼 데이터타입 관계 동기화 107 | columnRelationSync: column.relationSync, 108 | // 컬럼 너비 리셋 109 | columnWidthReset: column.widthReset, 110 | // 컬럼 편집모드 111 | columnEdit: column.edit, 112 | // 컬럼 도메인 힌트 show/hide 113 | columnDomainHintVisible: column.domainHintVisible, 114 | // 컬럼 도메인 힌트 show/hide ALL 115 | columnDomainHintVisibleAll: column.domainHintVisibleAll, 116 | // 컬럼 도메인 변경 117 | columnChangeDomain: column.changeDomain, 118 | // 컬럼 도메인 유효성 119 | columnValidDomain: column.validDomain, 120 | // 컬럼 도메인 동기화 121 | columnDomainSync: column.domainSync, 122 | // 관계 생성 123 | lineAdd: line.add, 124 | // 관계 drawing 125 | lineDraw: line.draw, 126 | // 관계 삭제 127 | lineDelete: line.delete, 128 | // 관계 식별, 비식별 변경 129 | lineChangeIdentification: line.changeIdentification, 130 | // 관계 컬럼 이동 유효성 131 | lineValidColumn: line.validColumn, 132 | // 관계 컬럼 hover 처리 133 | lineHover: line.hover, 134 | // 메모 추가 135 | memoAdd: memo.add, 136 | // 메모 삭제 137 | memoDelete: memo.delete, 138 | // 메모 크기 수정 139 | memoSetWidthHeight: memo.setWidthHeight, 140 | // 메모 선택 141 | memoSelected: memo.selected, 142 | // 메모 top, left 변경 143 | memoDraggable: memo.draggable, 144 | // 메모 선택 전체 해제 145 | memoSelectedAllNone: memo.selectedAllNone, 146 | // 메모 드래그 selected 147 | memoMultiSelected: memo.multiSelected, 148 | // 메모 전체 선택 149 | memoSelectedAll: memo.selectedAll, 150 | // 메모 리사이징 151 | memoResize: memo.resize, 152 | // 도메인 추가 153 | domainAdd: domain.add, 154 | // 도메인 삭제 155 | domainDelete: domain.delete, 156 | // 도메인값 변경 157 | domainChange: domain.change, 158 | // 도메인 수정모드 159 | domainEdit: domain.edit, 160 | // 도메인 edit 해제 161 | domainEditAllNone: domain.editAllNone 162 | } 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /src/renderer/store/editor/model.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import * as util from '@/js/editor/util' 5 | import storeERD from '@/store/editor/erd' 6 | import ERD from '@/js/editor/ERD' 7 | import storeTable from './table' 8 | 9 | JSLog('store loaded', 'model') 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | state: { 14 | id: util.guid(), 15 | tabs: [ 16 | { 17 | id: util.guid(), 18 | name: 'untitled', 19 | active: true, 20 | store: storeERD(), 21 | ui: { 22 | isReadName: true 23 | } 24 | } 25 | ] 26 | }, 27 | mutations: { 28 | // 전체 import 29 | importData (state, data) { 30 | JSLog('mutations', 'model importData') 31 | Object.keys(state).forEach(key => { 32 | state[key] = data.state[key] 33 | }) 34 | }, 35 | // 모델 추가 36 | modelAdd (state, data) { 37 | JSLog('mutations', 'modelAdd') 38 | ERD.core.event.components.CanvasMenu.isSave = false 39 | 40 | const tab = { 41 | id: util.guid(), 42 | name: util.autoName(state.tabs, 'untitled'), 43 | active: false, 44 | store: storeERD(), 45 | ui: { 46 | isReadName: true 47 | } 48 | } 49 | if (data.isInit) { 50 | tab.name = data.name 51 | tab.store = data.store 52 | } 53 | state.tabs.push(tab) 54 | this.commit({ 55 | type: 'modelActive', 56 | id: tab.id 57 | }) 58 | }, 59 | // 모델 변경 60 | modelActive (state, data) { 61 | JSLog('mutations', 'modelActive') 62 | 63 | const isTab = util.getData(state.tabs, data.id) 64 | if (isTab) { 65 | state.tabs.forEach(tab => { 66 | tab.active = tab.id === data.id 67 | }) 68 | } 69 | 70 | // 모든 이벤트 중지 71 | ERD.core.event.stop() 72 | // 테이블 상세 그리드 해제 73 | storeTable.commit({ type: 'delete' }) 74 | }, 75 | // 모델 변경 단축키 76 | modelActiveKeyMap (state, data) { 77 | JSLog('mutations', 'modelActiveKeyMap') 78 | 79 | let isActive = false 80 | for (let i = 0; i < state.tabs.length; i++) { 81 | if (data.index === i + 1) { 82 | isActive = true 83 | break 84 | } 85 | } 86 | if (isActive) { 87 | state.tabs.forEach((tab, i) => { 88 | tab.active = data.index === i + 1 89 | }) 90 | } 91 | 92 | // 모든 이벤트 중지 93 | ERD.core.event.stop() 94 | // 테이블 상세 그리드 해제 95 | storeTable.commit({ type: 'delete' }) 96 | }, 97 | // 모델 삭제 98 | modelDelete (state, data) { 99 | JSLog('mutations', 'modelDelete') 100 | ERD.core.event.components.CanvasMenu.isSave = false 101 | 102 | const tab = util.getData(state.tabs, data.id) 103 | if (tab) { 104 | ERD.core.indexedDB.add('model', tab) 105 | } 106 | for (let i in state.tabs) { 107 | if (data.id === state.tabs[i].id) { 108 | state.tabs.splice(i, 1) 109 | if (state.tabs.length === 0) { 110 | this.commit({ type: 'modelAdd' }) 111 | } else if (tab && tab.active) { 112 | state.tabs[state.tabs.length - 1].active = true 113 | document.getElementById(`tab_${state.tabs[state.tabs.length - 1].id}`).focus() 114 | } 115 | break 116 | } 117 | } 118 | 119 | // 모든 이벤트 중지 120 | ERD.core.event.stop() 121 | }, 122 | // edit on/off 123 | modelEdit (state, data) { 124 | JSLog('mutations', 'modelEdit') 125 | 126 | const tab = util.getData(state.tabs, data.id) 127 | tab.ui.isReadName = data.isRead 128 | }, 129 | // edit 전체 해제 130 | modelEditAllNone (state) { 131 | JSLog('mutations', 'modelEditAllNone') 132 | state.tabs.forEach(tab => { 133 | tab.ui.isReadName = true 134 | }) 135 | } 136 | } 137 | }) 138 | -------------------------------------------------------------------------------- /src/renderer/store/editor/mutationsColumn.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import * as util from '@/js/editor/util' 3 | import storeTable from './table' 4 | import ERD from '@/js/editor/ERD' 5 | 6 | JSLog('store loaded', 'mutationsColumn') 7 | 8 | export default { 9 | // 컬럼 추가 10 | add (state, data) { 11 | JSLog('mutations', 'column', 'add') 12 | ERD.core.event.components.CanvasMenu.isSave = false 13 | ERD.core.undoRedo.set() 14 | const undo = JSON.stringify(state) 15 | 16 | ERD.core.event.isEdit = true 17 | 18 | for (let table of state.tables) { 19 | if (data.id === table.id) { 20 | table.ui.height += state.COLUMN_HEIGHT 21 | const column = { 22 | id: util.guid(), 23 | name: '', 24 | comment: '', 25 | dataType: '', 26 | domain: '', 27 | domainId: '', 28 | default: '', 29 | options: { 30 | autoIncrement: false, 31 | primaryKey: false, 32 | unique: false, 33 | notNull: false 34 | }, 35 | ui: { 36 | selected: false, 37 | pk: false, 38 | fk: false, 39 | pfk: false, 40 | isDataTypeHint: false, 41 | isDomainHint: false, 42 | isHover: false, 43 | widthName: state.COLUMN_WIDTH, 44 | widthDataType: state.COLUMN_WIDTH, 45 | widthComment: state.COLUMN_WIDTH, 46 | widthDomain: state.COLUMN_WIDTH, 47 | isReadName: true, 48 | isReadDataType: true, 49 | isReadComment: true, 50 | isReadDomain: true 51 | } 52 | } 53 | if (data.isInit) { 54 | util.setData(column, data.column) 55 | } 56 | if (table.columns.length !== 0) { 57 | column.ui.widthName = table.columns[0].ui.widthName 58 | column.ui.widthDataType = table.columns[0].ui.widthDataType 59 | column.ui.widthComment = table.columns[0].ui.widthComment 60 | column.ui.widthDomain = table.columns[0].ui.widthDomain 61 | } 62 | table.columns.push(column) 63 | break 64 | } 65 | } 66 | 67 | if (!data.isInit) { 68 | // undo, redo 등록 69 | ERD.core.undoRedo.add({ 70 | undo: undo, 71 | redo: JSON.stringify(state) 72 | }) 73 | } 74 | }, 75 | // 컬럼 삭제 76 | delete (state, data) { 77 | JSLog('mutations', 'column', 'delete') 78 | ERD.core.event.components.CanvasMenu.isSave = false 79 | ERD.core.undoRedo.set() 80 | const undo = JSON.stringify(state) 81 | 82 | const table = util.getData(state.tables, data.tableId) 83 | for (let i in table.columns) { 84 | if (data.columnId === table.columns[i].id) { 85 | table.columns.splice(i, 1) 86 | table.ui.height -= state.COLUMN_HEIGHT 87 | break 88 | } 89 | } 90 | 91 | // 관계처리 92 | for (let i = 0; i < state.lines.length; i++) { 93 | if (state.lines[i].points[0].id === data.tableId || state.lines[i].points[1].id === data.tableId) { 94 | let endColumnId = null 95 | for (let j in state.lines[i].points[0].columnIds) { 96 | if (data.columnId === state.lines[i].points[0].columnIds[j] || data.columnId === state.lines[i].points[1].columnIds[j]) { 97 | endColumnId = state.lines[i].points[1].columnIds[j] 98 | state.lines[i].points[0].columnIds.splice(j, 1) 99 | state.lines[i].points[1].columnIds.splice(j, 1) 100 | util.changeIdentification(state, util.getData(state.tables, state.lines[i].points[1].id)) 101 | break 102 | } 103 | } 104 | // fk시 해제처리 105 | if (state.lines[i].points[0].id === data.tableId) { 106 | const endTable = util.getData(state.tables, state.lines[i].points[1].id) 107 | for (let column of endTable.columns) { 108 | if (column.id === endColumnId) { 109 | if (column.ui.pfk) { 110 | column.ui.pk = true 111 | column.ui.pfk = false 112 | } else if (column.ui.fk) { 113 | column.ui.fk = false 114 | } 115 | break 116 | } 117 | } 118 | } 119 | // 관계 컬럼이 0개시 삭제 120 | if (state.lines[i].points[0].columnIds.length === 0 || state.lines[i].points[1].columnIds.length === 0) { 121 | this.commit({ 122 | type: 'lineDelete', 123 | id: state.lines[i].id 124 | }) 125 | i-- 126 | } 127 | } 128 | } 129 | 130 | // 테이블 상세 활성화 131 | storeTable.commit({ 132 | type: 'active', 133 | id: data.tableId 134 | }) 135 | 136 | // 마지막 컬럼 포커스 137 | const isColumns = table.columns.length 138 | if (isColumns !== 0) { 139 | document.getElementById(`columnName_${table.columns[isColumns - 1].id}`).focus() 140 | } 141 | 142 | this.commit({ 143 | type: 'columnWidthReset', 144 | id: data.tableId 145 | }) 146 | // undo, redo 등록 147 | ERD.core.undoRedo.add({ 148 | undo: undo, 149 | redo: JSON.stringify(state) 150 | }) 151 | }, 152 | // 컬럼 NULL 조건 변경 153 | changeNull (state, data) { 154 | JSLog('mutations', 'column', 'changeNull') 155 | ERD.core.event.components.CanvasMenu.isSave = false 156 | ERD.core.undoRedo.set() 157 | const undo = JSON.stringify(state) 158 | 159 | const table = util.getData(state.tables, data.tableId) 160 | const column = util.getData(table.columns, data.columnId) 161 | column.options.notNull = !column.options.notNull 162 | 163 | // undo, redo 등록 164 | ERD.core.undoRedo.add({ 165 | undo: undo, 166 | redo: JSON.stringify(state) 167 | }) 168 | }, 169 | // 컬럼 선택 170 | selected (state, data) { 171 | JSLog('mutations', 'column', 'selected') 172 | this.commit({ 173 | type: 'tableSelectedAllNone', 174 | isTable: false, 175 | isColumn: true 176 | }) 177 | const table = util.getData(state.tables, data.tableId) 178 | if (table) { 179 | const column = util.getData(table.columns, data.columnId) 180 | if (column) column.ui.selected = true 181 | } 182 | 183 | // 테이블 상세 활성화 184 | storeTable.commit({ 185 | type: 'active', 186 | id: data.tableId 187 | }) 188 | }, 189 | // 컬럼 key active 190 | key (state, data) { 191 | JSLog('mutations', 'column', 'key') 192 | ERD.core.event.components.CanvasMenu.isSave = false 193 | ERD.core.undoRedo.set() 194 | const undo = JSON.stringify(state) 195 | 196 | for (let table of state.tables) { 197 | let check = false 198 | for (let column of table.columns) { 199 | if (column.ui.selected) { 200 | if (data.key === 'pk') { 201 | column.options.primaryKey = !column.options.primaryKey 202 | if (column.options.primaryKey) { 203 | column.options.notNull = true 204 | } 205 | } 206 | if (column.ui.fk) { 207 | column.ui.fk = false 208 | column.ui.pfk = true 209 | util.changeIdentification(state, table) 210 | } else if (column.ui.pfk) { 211 | column.ui.fk = true 212 | column.ui.pfk = false 213 | util.changeIdentification(state, table) 214 | } else { 215 | column.ui[data.key] = !column.ui[data.key] 216 | } 217 | check = true 218 | 219 | // 테이블 상세 활성화 220 | storeTable.commit({ 221 | type: 'active', 222 | id: table.id 223 | }) 224 | } 225 | } 226 | if (check) { 227 | break 228 | } 229 | } 230 | 231 | // undo, redo 등록 232 | ERD.core.undoRedo.add({ 233 | undo: undo, 234 | redo: JSON.stringify(state) 235 | }) 236 | }, 237 | // 컬럼 데이터변경 238 | changeDataType (state, data) { 239 | JSLog('mutations', 'column', 'changeDataType') 240 | ERD.core.event.components.CanvasMenu.isSave = false 241 | ERD.core.undoRedo.set() 242 | const undo = JSON.stringify(state) 243 | 244 | const table = util.getData(state.tables, data.tableId) 245 | const column = util.getData(table.columns, data.columnId) 246 | column.dataType = data.dataType 247 | 248 | // 테이블 상세 활성화 249 | storeTable.commit({ 250 | type: 'active', 251 | id: data.tableId 252 | }) 253 | 254 | if (column.domainId.trim() !== '') { 255 | this.commit({ 256 | type: 'domainChange', 257 | id: column.domainId, 258 | domain: { 259 | name: column.domain, 260 | dataType: column.dataType, 261 | default: column.default 262 | } 263 | }) 264 | } 265 | 266 | this.commit({ 267 | type: 'columnWidthReset', 268 | id: data.tableId 269 | }) 270 | // undo, redo 등록 271 | ERD.core.undoRedo.add({ 272 | undo: undo, 273 | redo: JSON.stringify(state) 274 | }) 275 | }, 276 | // 컬럼 데이터타입 힌트 show/hide 277 | dataTypeHintVisible (state, data) { 278 | JSLog('mutations', 'column', 'dataTypeHintVisible') 279 | const table = util.getData(state.tables, data.tableId) 280 | const column = util.getData(table.columns, data.columnId) 281 | column.ui.isDataTypeHint = data.isDataTypeHint 282 | }, 283 | // 컬럼 데이터타입 힌트 show/hide ALL 284 | dataTypeHintVisibleAll (state, data) { 285 | JSLog('mutations', 'column', 'dataTypeHintVisibleAll') 286 | for (let table of state.tables) { 287 | for (let column of table.columns) { 288 | column.ui.isDataTypeHint = data.isDataTypeHint 289 | } 290 | } 291 | }, 292 | // 컬럼 데이터타입 관계 동기화 293 | relationSync (state, data) { 294 | JSLog('mutations', 'column', 'relationSync') 295 | ERD.core.event.components.CanvasMenu.isSave = false 296 | const table = util.getData(state.tables, data.tableId) 297 | const column = util.getData(table.columns, data.columnId) 298 | if (util.isRelationSync(state, data.tableId, column)) { 299 | // 동기화 컬럼 탐색 300 | const columns = [] 301 | const lines = state.lines.slice() 302 | util.getColumnsSync(columns, lines, state, data.tableId, column) 303 | // 컬럼 데이터 동기화 304 | columns.forEach(v => { 305 | v.dataType = column.dataType 306 | v.domain = column.domain 307 | v.domainId = column.domainId 308 | v.default = column.default 309 | }) 310 | } 311 | }, 312 | // 컬럼 너비 리셋 313 | widthReset (state, data) { 314 | JSLog('mutations', 'column', 'widthReset') 315 | if (data.id) { 316 | const table = util.getData(state.tables, data.id) 317 | const max = util.columnMaxWidth(state, table.columns) 318 | table.columns.forEach(column => { 319 | column.ui.widthName = max.name 320 | column.ui.widthDataType = max.dataType 321 | column.ui.widthComment = max.comment 322 | column.ui.widthDomain = max.domain 323 | }) 324 | if (table.columns.length !== 0) { 325 | let width = table.columns[0].ui.widthName + 326 | table.columns[0].ui.widthDataType + 327 | table.columns[0].ui.widthComment + 328 | table.columns[0].ui.widthDomain 329 | if (width > state.COLUMN_WIDTH * 4) { 330 | table.ui.width = state.TABLE_WIDTH + width - state.COLUMN_WIDTH * 4 331 | } else { 332 | table.ui.width = state.TABLE_WIDTH 333 | } 334 | } else { 335 | table.ui.width = state.TABLE_WIDTH 336 | } 337 | } else { 338 | state.tables.forEach(table => { 339 | const max = util.columnMaxWidth(state, table.columns) 340 | table.columns.forEach(column => { 341 | column.ui.widthName = max.name 342 | column.ui.widthDataType = max.dataType 343 | column.ui.widthComment = max.comment 344 | column.ui.widthDomain = max.domain 345 | }) 346 | if (table.columns.length !== 0) { 347 | let width = table.columns[0].ui.widthName + 348 | table.columns[0].ui.widthDataType + 349 | table.columns[0].ui.widthComment + 350 | table.columns[0].ui.widthDomain 351 | if (width > state.COLUMN_WIDTH * 4) { 352 | table.ui.width = state.TABLE_WIDTH + width - state.COLUMN_WIDTH * 4 353 | } else { 354 | table.ui.width = state.TABLE_WIDTH 355 | } 356 | } else { 357 | table.ui.width = state.TABLE_WIDTH 358 | } 359 | }) 360 | } 361 | }, 362 | // 컬럼 편집모드 363 | edit (state, data) { 364 | JSLog('mutations', 'column', 'edit') 365 | const table = util.getData(state.tables, data.tableId) 366 | const column = util.getData(table.columns, data.columnId) 367 | column.ui[data.current] = data.isRead 368 | this.commit({ type: 'columnValidDomain' }) 369 | }, 370 | // 컬럼 도메인 힌트 show/hide 371 | domainHintVisible (state, data) { 372 | JSLog('mutations', 'column', 'domainHintVisible') 373 | const table = util.getData(state.tables, data.tableId) 374 | const column = util.getData(table.columns, data.columnId) 375 | column.ui.isDomainHint = data.isDomainHint 376 | }, 377 | // 컬럼 도메인 힌트 show/hide ALL 378 | domainHintVisibleAll (state, data) { 379 | JSLog('mutations', 'column', 'domainHintVisibleAll') 380 | for (let table of state.tables) { 381 | for (let column of table.columns) { 382 | column.ui.isDomainHint = data.isDomainHint 383 | } 384 | } 385 | }, 386 | // 컬럼 도메인 변경 387 | changeDomain (state, data) { 388 | JSLog('mutations', 'column', 'changeDomain') 389 | ERD.core.event.components.CanvasMenu.isSave = false 390 | ERD.core.undoRedo.set() 391 | const undo = JSON.stringify(state) 392 | 393 | const table = util.getData(state.tables, data.tableId) 394 | const column = util.getData(table.columns, data.columnId) 395 | const domain = util.getData(state.domains, data.domainId) 396 | column.domain = domain.name 397 | column.domainId = data.domainId 398 | column.dataType = domain.dataType 399 | column.default = domain.default 400 | 401 | // 컬럼 데이터타입 관계 동기화 402 | this.commit({ 403 | type: 'columnRelationSync', 404 | tableId: data.tableId, 405 | columnId: data.columnId 406 | }) 407 | 408 | this.commit({ 409 | type: 'columnWidthReset', 410 | id: data.tableId 411 | }) 412 | // undo, redo 등록 413 | ERD.core.undoRedo.add({ 414 | undo: undo, 415 | redo: JSON.stringify(state) 416 | }) 417 | }, 418 | // 컬럼 도메인 유효성 419 | validDomain (state) { 420 | JSLog('mutations', 'column', 'validDomain') 421 | 422 | state.tables.forEach(table => { 423 | table.columns.forEach(column => { 424 | if (column.domainId.trim() === '') { 425 | column.domain = '' 426 | } 427 | }) 428 | }) 429 | this.commit({ type: 'columnWidthReset' }) 430 | }, 431 | // 컬럼 도메인 동기화 432 | domainSync (state, data) { 433 | JSLog('mutations', 'column', 'domainSync') 434 | ERD.core.event.components.CanvasMenu.isSave = false 435 | const table = util.getData(state.tables, data.tableId) 436 | const column = util.getData(table.columns, data.columnId) 437 | if (column.domain === '') { 438 | ERD.core.undoRedo.set() 439 | const undo = JSON.stringify(state) 440 | 441 | column.domainId = '' 442 | // 동기화 컬럼 탐색 443 | const columns = [] 444 | const lines = state.lines.slice() 445 | util.getColumnsSync(columns, lines, state, data.tableId, column) 446 | // 컬럼 동기화 447 | columns.forEach(v => { 448 | v.domain = column.domain 449 | v.domainId = column.domainId 450 | }) 451 | 452 | // undo, redo 등록 453 | ERD.core.undoRedo.add({ 454 | undo: undo, 455 | redo: JSON.stringify(state) 456 | }) 457 | } else if (column.domainId.trim() !== '') { 458 | this.commit({ 459 | type: 'domainChange', 460 | isUpdated: true, 461 | id: column.domainId, 462 | domain: { 463 | name: column.domain, 464 | dataType: column.dataType, 465 | default: column.default 466 | } 467 | }) 468 | } 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/renderer/store/editor/mutationsDomain.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import * as util from '@/js/editor/util' 3 | import ERD from '@/js/editor/ERD' 4 | 5 | JSLog('store loaded', 'mutationsDomain') 6 | 7 | export default { 8 | // 도메인 추가 9 | add (state) { 10 | JSLog('mutations', 'domain', 'add') 11 | ERD.core.event.components.CanvasMenu.isSave = false 12 | ERD.core.undoRedo.set() 13 | const undo = JSON.stringify(ERD.core.erd.store().state) 14 | 15 | const domain = { 16 | id: util.guid(), 17 | name: '', 18 | dataType: '', 19 | default: '', 20 | ui: { 21 | isReadname: true, 22 | isReaddataType: true, 23 | isReaddefault: true 24 | } 25 | } 26 | state.domains.push(domain) 27 | 28 | // undo, redo 등록 29 | ERD.core.undoRedo.add({ 30 | undo: undo, 31 | redo: JSON.stringify(ERD.core.erd.store().state) 32 | }) 33 | }, 34 | // 도메인 삭제 35 | delete (state, data) { 36 | JSLog('mutations', 'domain', 'delete') 37 | ERD.core.event.components.CanvasMenu.isSave = false 38 | ERD.core.undoRedo.set() 39 | const undo = JSON.stringify(ERD.core.erd.store().state) 40 | 41 | for (let i in state.domains) { 42 | if (data.id === state.domains[i].id) { 43 | state.domains.splice(i, 1) 44 | break 45 | } 46 | } 47 | 48 | // 도메인 연동 초기화 49 | state.tables.forEach(table => { 50 | table.columns.forEach(column => { 51 | if (data.id === column.domainId) { 52 | column.domain = '' 53 | column.domainId = '' 54 | } 55 | }) 56 | }) 57 | 58 | // undo, redo 등록 59 | ERD.core.undoRedo.add({ 60 | undo: undo, 61 | redo: JSON.stringify(ERD.core.erd.store().state) 62 | }) 63 | }, 64 | // 도메인값 변경 65 | change (state, data) { 66 | JSLog('mutations', 'domain', 'change') 67 | ERD.core.event.components.CanvasMenu.isSave = false 68 | ERD.core.undoRedo.set() 69 | const undo = JSON.stringify(ERD.core.erd.store().state) 70 | 71 | const domain = util.getData(state.domains, data.id) 72 | util.setData(domain, data.domain) 73 | 74 | state.tables.forEach(table => { 75 | table.columns.forEach(column => { 76 | if (domain.id === column.domainId) { 77 | column.domain = domain.name 78 | column.dataType = domain.dataType 79 | column.default = domain.default 80 | } 81 | }) 82 | }) 83 | 84 | // updated 강제 85 | if (data.isUpdated) { 86 | state.domains = state.domains.slice() 87 | } 88 | 89 | this.commit({ type: 'columnWidthReset' }) 90 | 91 | // undo, redo 등록 92 | ERD.core.undoRedo.add({ 93 | undo: undo, 94 | redo: JSON.stringify(ERD.core.erd.store().state) 95 | }) 96 | }, 97 | // 도메인 수정모드 98 | edit (state, data) { 99 | JSLog('mutations', 'domain', 'edit') 100 | const domain = util.getData(state.domains, data.id) 101 | domain.ui[data.current] = data.isRead 102 | }, 103 | // 도메인 edit 해제 104 | editAllNone (state) { 105 | JSLog('mutations', 'domain', 'editAllNone') 106 | state.domains.forEach(domain => { 107 | domain.ui.isReadname = true 108 | domain.ui.isReaddataType = true 109 | domain.ui.isReaddefault = true 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/renderer/store/editor/mutationsLine.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import * as util from '@/js/editor/util' 3 | import ERD from '@/js/editor/ERD' 4 | 5 | JSLog('store loaded', 'mutationsLine') 6 | 7 | export default { 8 | // 관계 생성 9 | add (state, data) { 10 | ERD.core.undoRedo.setUndo('draw') 11 | 12 | const line = { 13 | id: util.guid(), 14 | type: ERD.core.event.cursor, 15 | isIdentification: false, 16 | points: [ 17 | { 18 | id: data.tableId, 19 | x: data.x, 20 | y: data.y, 21 | columnIds: [] 22 | }, 23 | { 24 | id: null, 25 | x: data.x, 26 | y: data.y, 27 | columnIds: [] 28 | } 29 | ] 30 | } 31 | state.lines.push(line) 32 | ERD.core.event.onDraw('start', line.id) 33 | }, 34 | // 관계 drawing 35 | draw (state, data) { 36 | JSLog('mutations', 'line', 'draw') 37 | const line = util.getData(state.lines, data.id) 38 | line.points[1].x = data.x 39 | line.points[1].y = data.y 40 | line.points[0].columnIds = data.startColumnIds 41 | line.points[1].columnIds = data.endColumnIds 42 | if (data.tableId) line.points[1].id = data.tableId 43 | this.commit({ 44 | type: 'columnWidthReset', 45 | id: data.tableId 46 | }) 47 | }, 48 | // 관계 삭제 49 | delete (state, data) { 50 | JSLog('mutations', 'line', 'delete') 51 | for (let i in state.lines) { 52 | if (data.id === state.lines[i].id) { 53 | state.lines.splice(i, 1) 54 | break 55 | } 56 | } 57 | }, 58 | // 관계 식별, 비식별 변경 59 | changeIdentification (state, data) { 60 | JSLog('mutations', 'line', 'changeIdentification') 61 | const line = util.getData(state.lines, data.id) 62 | line.isIdentification = data.isIdentification 63 | }, 64 | // 관계 컬럼 이동 유효성 65 | validColumn (state, data) { 66 | JSLog('mutations', 'line', 'validColumn') 67 | // 테이블 id 탐색 68 | let tableId = null 69 | for (let table of state.tables) { 70 | for (let column of table.columns) { 71 | if (data.id === column.id) { 72 | tableId = table.id 73 | ERD.store().commit({ 74 | type: 'tableSelected', 75 | id: table.id, 76 | isColumnSelected: true 77 | }) 78 | break 79 | } 80 | } 81 | if (tableId) break 82 | } 83 | 84 | // 관계처리 85 | for (let i = 0; i < state.lines.length; i++) { 86 | let isFk = false 87 | let endColumnId = null 88 | for (let j in state.lines[i].points[0].columnIds) { 89 | if (data.id === state.lines[i].points[0].columnIds[j] && tableId !== state.lines[i].points[0].id) { 90 | endColumnId = state.lines[i].points[1].columnIds[j] 91 | state.lines[i].points[0].columnIds.splice(j, 1) 92 | state.lines[i].points[1].columnIds.splice(j, 1) 93 | util.changeIdentification(state, util.getData(state.tables, state.lines[i].points[1].id)) 94 | break 95 | } 96 | } 97 | for (let j in state.lines[i].points[1].columnIds) { 98 | if (data.id === state.lines[i].points[1].columnIds[j] && tableId !== state.lines[i].points[1].id) { 99 | isFk = true 100 | state.lines[i].points[0].columnIds.splice(j, 1) 101 | state.lines[i].points[1].columnIds.splice(j, 1) 102 | util.changeIdentification(state, util.getData(state.tables, state.lines[i].points[1].id)) 103 | break 104 | } 105 | } 106 | // fk시 해제처리 107 | if (endColumnId != null) { 108 | const endTable = util.getData(state.tables, state.lines[i].points[1].id) 109 | for (let column of endTable.columns) { 110 | if (column.id === endColumnId) { 111 | if (column.ui.pfk) { 112 | column.ui.pk = true 113 | column.ui.pfk = false 114 | } else if (column.ui.fk) { 115 | column.ui.fk = false 116 | } 117 | break 118 | } 119 | } 120 | } 121 | // 관계 컬럼이 0개시 삭제 122 | if (state.lines[i].points[0].columnIds.length === 0 || state.lines[i].points[1].columnIds.length === 0) { 123 | this.commit({ 124 | type: 'lineDelete', 125 | id: state.lines[i].id 126 | }) 127 | i-- 128 | } 129 | // fk시 해제처리 130 | if (isFk) { 131 | const table = util.getData(state.tables, tableId) 132 | const column = util.getData(table.columns, data.id) 133 | if (column.ui.pfk) { 134 | column.ui.pk = true 135 | column.ui.pfk = false 136 | } else if (column.ui.fk) { 137 | column.ui.fk = false 138 | } 139 | } 140 | } 141 | 142 | this.commit({ 143 | type: 'columnWidthReset', 144 | id: tableId 145 | }) 146 | // undo, redo 등록 147 | ERD.core.undoRedo.add({ 148 | undo: ERD.core.undoRedo.undoJson.draggable, 149 | redo: JSON.stringify(state) 150 | }) 151 | }, 152 | // 관계 컬럼 hover 처리 153 | hover (state, data) { 154 | JSLog('mutations', 'line', 'hover') 155 | if (!ERD.core.event.isDraw) { 156 | const line = util.getData(state.lines, data.id) 157 | const startTable = util.getData(state.tables, line.points[0].id) 158 | const endTable = util.getData(state.tables, line.points[1].id) 159 | for (let columnId of line.points[0].columnIds) { 160 | for (let column of startTable.columns) { 161 | if (column.id === columnId) { 162 | column.ui.isHover = data.isHover 163 | break 164 | } 165 | } 166 | } 167 | 168 | for (let columnId of line.points[1].columnIds) { 169 | for (let column of endTable.columns) { 170 | if (column.id === columnId) { 171 | column.ui.isHover = data.isHover 172 | break 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/renderer/store/editor/mutationsMemo.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import * as util from '@/js/editor/util' 3 | import ERD from '@/js/editor/ERD' 4 | 5 | JSLog('store loaded', 'mutationsMemo') 6 | 7 | export default { 8 | // 메모 추가 9 | add (state, data) { 10 | JSLog('mutations', 'memo', 'add') 11 | ERD.core.event.components.CanvasMenu.isSave = false 12 | ERD.core.undoRedo.set() 13 | const undo = JSON.stringify(state) 14 | 15 | const newMemo = { 16 | id: util.guid(), 17 | content: '', 18 | ui: { 19 | selected: false, 20 | top: document.documentElement.scrollTop + 100, 21 | left: document.documentElement.scrollLeft + 200, 22 | width: state.MEMO_WIDTH, 23 | height: state.MEMO_HEIGHT, 24 | zIndex: util.getZIndex() 25 | } 26 | } 27 | 28 | util.setPosition(newMemo) 29 | state.memos.push(newMemo) 30 | this.commit({ 31 | type: 'memoSelected', 32 | id: newMemo.id 33 | }) 34 | 35 | // undo, redo 등록 36 | ERD.core.undoRedo.add({ 37 | undo: undo, 38 | redo: JSON.stringify(state) 39 | }) 40 | }, 41 | // 메모 삭제 42 | delete (state, data) { 43 | JSLog('mutations', 'memo', 'delete') 44 | ERD.core.event.components.CanvasMenu.isSave = false 45 | ERD.core.undoRedo.set() 46 | const undo = JSON.stringify(state) 47 | 48 | for (let i in state.memos) { 49 | if (data.id === state.memos[i].id) { 50 | state.memos.splice(i, 1) 51 | break 52 | } 53 | } 54 | 55 | // undo, redo 등록 56 | ERD.core.undoRedo.add({ 57 | undo: undo, 58 | redo: JSON.stringify(state) 59 | }) 60 | }, 61 | // 메모 크기 수정 62 | setWidthHeight (state, data) { 63 | JSLog('mutations', 'memo', 'setWidthHeight') 64 | ERD.core.event.components.CanvasMenu.isSave = false 65 | const memo = util.getData(state.memos, data.id) 66 | memo.ui.width = data.width 67 | memo.ui.height = data.height 68 | }, 69 | // 메모 선택 70 | selected (state, data) { 71 | JSLog('mutations', 'memo', 'selected') 72 | const memo = util.getData(state.memos, data.id) 73 | // z-index 처리 74 | const zIndex = util.getZIndex() 75 | if (memo && memo.ui.zIndex !== zIndex - 1) { 76 | memo.ui.zIndex = zIndex 77 | } 78 | 79 | // multi select 80 | if (data.ctrlKey) { 81 | memo.ui.selected = true 82 | } else { 83 | state.memos.forEach(v => { 84 | v.ui.selected = data.id === v.id 85 | }) 86 | this.commit({ 87 | type: 'tableSelectedAllNone', 88 | isTable: true, 89 | isColumn: true 90 | }) 91 | } 92 | 93 | if (data.isEvent) { 94 | const tableIds = [] 95 | const memoIds = [] 96 | for (let targetTable of state.tables) { 97 | if (targetTable.ui.selected) { 98 | tableIds.push(targetTable.id) 99 | } 100 | } 101 | for (let targetMemo of state.memos) { 102 | if (targetMemo.ui.selected) { 103 | memoIds.push(targetMemo.id) 104 | } 105 | } 106 | ERD.core.event.onDraggable('start', tableIds, memoIds) 107 | } 108 | }, 109 | // 메모 top, left 변경 110 | draggable (state, data) { 111 | JSLog('mutations', 'memo', 'draggable') 112 | ERD.core.event.components.CanvasMenu.isSave = false 113 | const memo = util.getData(state.memos, data.id) 114 | memo.ui.top += data.y 115 | memo.ui.left += data.x 116 | }, 117 | // 메모 선택 전체 해제 118 | selectedAllNone (state) { 119 | JSLog('mutations', 'memo', 'selectedAllNone') 120 | state.memos.forEach(memo => { 121 | memo.ui.selected = false 122 | }) 123 | }, 124 | // 메모 드래그 selected 125 | multiSelected (state, data) { 126 | JSLog('mutations', 'memo', 'multiSelected') 127 | state.memos.forEach(memo => { 128 | const point = util.getPoint(memo.ui) 129 | if (data.min.x <= point.top.x && 130 | data.min.y <= point.left.y && 131 | data.max.x >= point.top.x && 132 | data.max.y >= point.left.y) { 133 | memo.ui.selected = true 134 | } else { 135 | memo.ui.selected = false 136 | } 137 | }) 138 | }, 139 | // 메모 전체 선택 140 | selectedAll (state) { 141 | JSLog('mutations', 'memo', 'selectedAll') 142 | state.memos.forEach(memo => { 143 | memo.ui.selected = true 144 | }) 145 | }, 146 | // 메모 리사이징 147 | resize (state, data) { 148 | JSLog('mutations', 'memo', 'resize') 149 | ERD.core.event.components.CanvasMenu.isSave = false 150 | const memo = util.getData(state.memos, data.id) 151 | memo.ui.height += data.y 152 | memo.ui.width += data.x 153 | if (memo.ui.height < state.MEMO_HEIGHT) { 154 | memo.ui.height = state.MEMO_HEIGHT 155 | } 156 | if (memo.ui.width < state.MEMO_WIDTH) { 157 | memo.ui.width = state.MEMO_WIDTH 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/renderer/store/editor/mutationsTable.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import * as util from '@/js/editor/util' 3 | import ERD from '@/js/editor/ERD' 4 | import storeTable from './table' 5 | 6 | JSLog('store loaded', 'mutationsTable') 7 | 8 | export default { 9 | // 테이블 추가 10 | add (state, data) { 11 | JSLog('mutations', 'table', 'add') 12 | ERD.core.event.components.CanvasMenu.isSave = false 13 | ERD.core.undoRedo.set() 14 | const undo = JSON.stringify(state) 15 | 16 | ERD.core.event.isEdit = true 17 | 18 | const newTable = { 19 | id: util.guid(), 20 | name: '', 21 | comment: '', 22 | columns: [], 23 | ui: { 24 | selected: false, 25 | top: document.documentElement.scrollTop + 100, 26 | left: document.documentElement.scrollLeft + 200, 27 | width: state.TABLE_WIDTH, 28 | height: state.TABLE_HEIGHT, 29 | zIndex: util.getZIndex(), 30 | isReadName: true, 31 | isReadComment: true 32 | } 33 | } 34 | 35 | util.setPosition(newTable) 36 | state.tables.push(newTable) 37 | this.commit({ 38 | type: 'tableSelected', 39 | id: newTable.id, 40 | isNotRelation: true 41 | }) 42 | 43 | // undo, redo 등록 44 | ERD.core.undoRedo.add({ 45 | undo: undo, 46 | redo: JSON.stringify(state) 47 | }) 48 | }, 49 | // 테이블 삭제 50 | delete (state, data) { 51 | JSLog('mutations', 'table', 'delete') 52 | ERD.core.event.components.CanvasMenu.isSave = false 53 | ERD.core.undoRedo.set() 54 | const undo = JSON.stringify(state) 55 | 56 | // 테이블 상세 그리드 해제 57 | storeTable.commit({ type: 'delete' }) 58 | 59 | for (let i in state.tables) { 60 | if (data.id === state.tables[i].id) { 61 | state.tables.splice(i, 1) 62 | break 63 | } 64 | } 65 | // 관계처리 66 | for (let i = 0; i < state.lines.length; i++) { 67 | let isLine = false 68 | for (let j in state.lines[i].points) { 69 | if (data.id === state.lines[i].points[j].id) { 70 | isLine = true 71 | break 72 | } 73 | } 74 | if (isLine) { 75 | // fk시 해제처리 76 | if (data.id === state.lines[i].points[0].id) { 77 | const endTable = util.getData(state.tables, state.lines[i].points[1].id) 78 | if (endTable) { 79 | for (let column of endTable.columns) { 80 | for (let columnId of state.lines[i].points[1].columnIds) { 81 | if (columnId === column.id) { 82 | if (column.ui.pfk) { 83 | column.ui.pk = true 84 | column.ui.pfk = false 85 | } else if (column.ui.fk) { 86 | column.ui.fk = false 87 | } 88 | break 89 | } 90 | } 91 | } 92 | } 93 | } 94 | // 관계 컬럼이 0개시 삭제 95 | this.commit({ 96 | type: 'lineDelete', 97 | id: state.lines[i].id 98 | }) 99 | i-- 100 | } 101 | } 102 | for (let i in ERD.core.event.tableIds) { 103 | if (ERD.core.event.tableIds[i] === data.id) { 104 | ERD.core.event.tableIds.splice(i, 1) 105 | break 106 | } 107 | } 108 | 109 | // undo, redo 등록 110 | ERD.core.undoRedo.add({ 111 | undo: undo, 112 | redo: JSON.stringify(state) 113 | }) 114 | }, 115 | // 테이블 높이 리셋 116 | heightReset (state) { 117 | JSLog('mutations', 'table', 'heightReset') 118 | ERD.core.event.components.CanvasMenu.isSave = false 119 | for (let table of state.tables) { 120 | table.ui.height = table.columns.length * state.COLUMN_HEIGHT + state.TABLE_HEIGHT 121 | } 122 | }, 123 | // 테이블 선택 124 | selected (state, data) { 125 | JSLog('mutations', 'table', 'selected') 126 | const table = util.getData(state.tables, data.id) 127 | // z-index 처리 128 | const zIndex = util.getZIndex() 129 | if (table && table.ui.zIndex !== zIndex - 1) { 130 | table.ui.zIndex = zIndex 131 | } 132 | 133 | // multi select 134 | if (data.ctrlKey) { 135 | table.ui.selected = true 136 | } else { 137 | state.tables.forEach(v => { 138 | v.ui.selected = data.id === v.id 139 | }) 140 | this.commit({ type: 'memoSelectedAllNone' }) 141 | } 142 | // column 선택 제거 143 | if (!data.isColumnSelected) { 144 | this.commit({ 145 | type: 'tableSelectedAllNone', 146 | isTable: false, 147 | isColumn: true 148 | }) 149 | } 150 | 151 | if (data.isEvent) { 152 | const tableIds = [] 153 | const memoIds = [] 154 | for (let targetTable of state.tables) { 155 | if (targetTable.ui.selected) { 156 | tableIds.push(targetTable.id) 157 | } 158 | } 159 | for (let targetMemo of state.memos) { 160 | if (targetMemo.ui.selected) { 161 | memoIds.push(targetMemo.id) 162 | } 163 | } 164 | ERD.core.event.onDraggable('start', tableIds, memoIds) 165 | } 166 | 167 | // 테이블추가에서 호출시 처리 168 | if (!data.isNotRelation) { 169 | // 관계 drawing 시작 170 | if (ERD.core.event.isCursor && !ERD.core.event.isDraw) { 171 | // table pk 컬럼이 있는지 체크 없으면 자동생성 172 | if (!util.isColumnOption('primaryKey', table.columns)) { 173 | this.commit({ 174 | type: 'columnAdd', 175 | id: table.id, 176 | isInit: true, 177 | column: { 178 | name: util.autoName(table.columns, 'unnamed'), 179 | options: { 180 | primaryKey: true, 181 | notNull: true 182 | }, 183 | ui: { 184 | pk: true 185 | } 186 | } 187 | }) 188 | } 189 | this.commit({ 190 | type: 'lineAdd', 191 | tableId: data.id, 192 | x: table.ui.left, 193 | y: table.ui.top 194 | }) 195 | // 관계 drawing 종료 196 | } else if (ERD.core.event.isDraw) { 197 | ERD.core.event.onDraw('stop', data.id) 198 | } 199 | } 200 | 201 | // 테이블 상세 활성화 202 | storeTable.commit({ 203 | type: 'active', 204 | id: data.id 205 | }) 206 | }, 207 | // 테이블 top, left 변경 208 | draggable (state, data) { 209 | JSLog('mutations', 'table', 'draggable') 210 | ERD.core.event.components.CanvasMenu.isSave = false 211 | const table = util.getData(state.tables, data.id) 212 | table.ui.top += data.y 213 | table.ui.left += data.x 214 | // 관계 업데이트 215 | state.lines.forEach(line => { 216 | line.points.forEach(v => { 217 | if (v.id === data.id) { 218 | v.x = table.ui.left 219 | v.y = table.ui.top 220 | } 221 | }) 222 | }) 223 | }, 224 | // 테이블 및 컬럼 selected All 해제 225 | selectedAllNone (state, data) { 226 | JSLog('mutations', 'table', 'selectedAllNone') 227 | // 테이블 상세 그리드 해제 228 | if (data.isTable) { 229 | storeTable.commit({ type: 'delete' }) 230 | } 231 | 232 | state.tables.forEach(table => { 233 | if (data.isTable) table.ui.selected = false 234 | table.columns.forEach(column => { 235 | if (data.isColumn) column.ui.selected = false 236 | }) 237 | }) 238 | }, 239 | // 테이블 드래그 selected 240 | multiSelected (state, data) { 241 | JSLog('mutations', 'table', 'multiSelected') 242 | state.tables.forEach(table => { 243 | const point = util.getPoint(table.ui) 244 | if (data.min.x <= point.top.x && 245 | data.min.y <= point.left.y && 246 | data.max.x >= point.top.x && 247 | data.max.y >= point.left.y) { 248 | table.ui.selected = true 249 | } else { 250 | table.ui.selected = false 251 | } 252 | }) 253 | }, 254 | // 테이블 전체 선택 255 | selectedAll (state) { 256 | JSLog('mutations', 'table', 'selectedAll') 257 | state.tables.forEach(table => { 258 | table.ui.selected = true 259 | }) 260 | }, 261 | // 테이블 편집모드 262 | edit (state, data) { 263 | JSLog('mutations', 'table', 'edit') 264 | const table = util.getData(state.tables, data.id) 265 | table.ui[data.current] = data.isRead 266 | }, 267 | // 테이블 및 컬럼 edit all 해제 268 | editAllNone (state, data) { 269 | JSLog('mutations', 'table', 'editAllNone') 270 | 271 | state.tables.forEach(table => { 272 | if (data.isTable) { 273 | table.ui.isReadName = true 274 | table.ui.isReadComment = true 275 | } 276 | table.columns.forEach(column => { 277 | if (data.isColumn) { 278 | column.ui.isReadName = true 279 | column.ui.isReadDataType = true 280 | column.ui.isReadComment = true 281 | column.ui.isReadDomain = true 282 | } 283 | }) 284 | }) 285 | 286 | this.commit({ type: 'columnValidDomain' }) 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/renderer/store/editor/table.js: -------------------------------------------------------------------------------- 1 | import JSLog from '@/js/JSLog' 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import ERD from '@/js/editor/ERD' 5 | import * as util from '@/js/editor/util' 6 | 7 | JSLog('store loaded', 'table') 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | state: { 12 | table: null, 13 | rows: [] 14 | }, 15 | mutations: { 16 | // 그리드 활성화 17 | active (state, data) { 18 | JSLog('mutations', 'table grid', 'active') 19 | state.rows = [] 20 | state.table = util.getData(ERD.store().state.tables, data.id) 21 | if (state.table) { 22 | state.table.columns.forEach(column => { 23 | state.rows.push({ 24 | id: column.id, 25 | name: column.name, 26 | dataType: column.dataType, 27 | primaryKey: column.options.primaryKey, 28 | notNull: column.options.notNull, 29 | unique: column.options.unique, 30 | autoIncrement: column.options.autoIncrement, 31 | default: column.default, 32 | comment: column.comment, 33 | ui: { 34 | isReadname: true, 35 | isReaddataType: true, 36 | isReaddefault: true, 37 | isReadcomment: true 38 | } 39 | }) 40 | }) 41 | } 42 | }, 43 | // 삭제 44 | delete (state) { 45 | JSLog('mutations', 'table grid', 'delete') 46 | state.rows = [] 47 | state.table = null 48 | }, 49 | // 컬럼데이터 동기화 50 | sync (state, data) { 51 | JSLog('mutations', 'table grid', 'sync') 52 | ERD.core.event.components.CanvasMenu.isSave = false 53 | 54 | if (state.table) { 55 | ERD.core.undoRedo.set() 56 | const undo = JSON.stringify(ERD.core.erd.store().state) 57 | if (data.isPK) { 58 | state.table.columns.forEach(column => { 59 | if (column.id === data.columnId) { 60 | column.ui.selected = true 61 | } else { 62 | column.ui.selected = false 63 | } 64 | }) 65 | ERD.store().commit({ 66 | type: 'columnKey', 67 | key: 'pk' 68 | }) 69 | util.setData(util.getData(state.rows, data.columnId), data.columnGrid) 70 | } else { 71 | state.table.columns.forEach(column => { 72 | if (column.id === data.columnId) { 73 | column.ui.selected = true 74 | } else { 75 | column.ui.selected = false 76 | } 77 | }) 78 | util.setData(util.getData(state.table.columns, data.columnId), data.column) 79 | util.setData(util.getData(state.rows, data.columnId), data.columnGrid) 80 | if (data.column.dataType) { 81 | // 컬럼 데이터타입 관계 동기화 82 | ERD.store().commit({ 83 | type: 'columnRelationSync', 84 | tableId: state.table.id, 85 | columnId: data.columnId 86 | }) 87 | } 88 | // 도메인 동기화 89 | if (data.column.dataType || data.column.default) { 90 | ERD.store().commit({ 91 | type: 'columnDomainSync', 92 | tableId: state.table.id, 93 | columnId: data.columnId 94 | }) 95 | } 96 | } 97 | ERD.core.erd.store().commit({ 98 | type: 'columnWidthReset', 99 | id: state.table.id 100 | }) 101 | // undo, redo 등록 102 | ERD.core.undoRedo.add({ 103 | undo: undo, 104 | redo: JSON.stringify(ERD.core.erd.store().state) 105 | }) 106 | } 107 | }, 108 | // 수정모드 109 | edit (state, data) { 110 | JSLog('mutations', 'table grid', 'edit') 111 | const column = util.getData(state.rows, data.columnId) 112 | column.ui[data.current] = data.isRead 113 | }, 114 | // edit 해제 115 | editAllNone (state) { 116 | JSLog('mutations', 'table grid', 'editAllNone') 117 | if (state.table) { 118 | state.rows.forEach(row => { 119 | row.ui.isReadname = true 120 | row.ui.isReaddataType = true 121 | row.ui.isReaddefault = true 122 | row.ui.isReadcomment = true 123 | }) 124 | } 125 | } 126 | } 127 | }) 128 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import { createPersistedState, createSharedMutations } from 'vuex-electron' 5 | 6 | import modules from './modules' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | modules, 12 | plugins: [ 13 | createPersistedState(), 14 | createSharedMutations() 15 | ], 16 | strict: process.env.NODE_ENV !== 'production' 17 | }) 18 | -------------------------------------------------------------------------------- /src/renderer/store/modules/Counter.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | main: 0 3 | } 4 | 5 | const mutations = { 6 | DECREMENT_MAIN_COUNTER (state) { 7 | state.main-- 8 | }, 9 | INCREMENT_MAIN_COUNTER (state) { 10 | state.main++ 11 | } 12 | } 13 | 14 | const actions = { 15 | someAsyncTask ({ commit }) { 16 | // do something async 17 | commit('INCREMENT_MAIN_COUNTER') 18 | } 19 | } 20 | 21 | export default { 22 | state, 23 | mutations, 24 | actions 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The file enables `@/store/index.js` to import all vuex modules 3 | * in a one-shot manner. There should not be any reason to edit this file. 4 | */ 5 | 6 | const files = require.context('.', false, /\.js$/) 7 | const modules = {} 8 | 9 | files.keys().forEach(key => { 10 | if (key === './index.js') return 11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /src/renderer/views/ERD.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | 115 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/static/.gitkeep --------------------------------------------------------------------------------