├── .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 | 
158 | ---
159 | 
160 | 
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 |
2 | #app
3 | router-view
4 |
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 |
2 | svg.svg_canvas(:style="`width: ${CANVAS_WIDTH}px; height: ${CANVAS_HEIGHT}px;`")
3 | // relation
4 | g(v-for="data in toLines" :key="data.id"
5 | @mouseover="hover($event, data.id)"
6 | @mouseleave="hoverOff(data.id)")
7 |
8 | // start draw
9 | line(:x1="data.path.line.start.x1" :y1="data.path.line.start.y1"
10 | :x2="data.path.line.start.x2" :y2="data.path.line.start.y2"
11 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
12 | stroke-width="3")
13 |
14 | path(:d="data.path.d()"
15 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
16 | :stroke-dasharray="data.isIdentification ? 0 : 10"
17 | stroke-width="3" fill="transparent")
18 |
19 | line(:x1="data.line.start.x1" :y1="data.line.start.y1"
20 | :x2="data.line.start.x2" :y2="data.line.start.y2"
21 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
22 | stroke-width="3")
23 |
24 | // end draw
25 | line(v-if="data.isDraw"
26 | :x1="data.path.line.end.x1" :y1="data.path.line.end.y1"
27 | :x2="data.path.line.end.x2" :y2="data.path.line.end.y2"
28 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
29 | stroke-width="3")
30 |
31 | circle(v-if="data.isDraw"
32 | :cx="data.circle.cx" :cy="data.circle.cy" r="8"
33 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
34 | fill-opacity="0.0" stroke-width="3")
35 |
36 | line(v-if="data.isDraw"
37 | :x1="data.line.end.base.x1" :y1="data.line.end.base.y1"
38 | :x2="data.line.end.base.x2" :y2="data.line.end.base.y2"
39 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
40 | stroke-width="3")
41 |
42 | line(v-if="data.isDraw && data.type === 'erd-0-1-N'"
43 | :x1="data.line.end.left.x1" :y1="data.line.end.left.y1"
44 | :x2="data.line.end.left.x2" :y2="data.line.end.left.y2"
45 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
46 | stroke-width="3")
47 |
48 | line(v-if="data.isDraw"
49 | :x1="data.line.end.center.x1" :y1="data.line.end.center.y1"
50 | :x2="data.line.end.center.x2" :y2="data.line.end.center.y2"
51 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
52 | stroke-width="3")
53 |
54 | line(v-if="data.isDraw && data.type === 'erd-0-1-N'"
55 | :x1="data.line.end.right.x1" :y1="data.line.end.right.y1"
56 | :x2="data.line.end.right.x2" :y2="data.line.end.right.y2"
57 | :stroke="data.isIdentification ? '#60b9c4' : '#dda8b1'"
58 | stroke-width="3")
59 |
60 |
61 |
133 |
134 |
143 |
--------------------------------------------------------------------------------
/src/renderer/components/editor/Grid.vue:
--------------------------------------------------------------------------------
1 |
2 | .erd_grid
3 | table
4 | thead
5 | tr
6 | th(:colspan="columnData.length")
7 | .table_resize(@mousedown="resize")
8 | button.close(title="ESC" @click="close")
9 | font-awesome-icon(icon="times")
10 | button.add(v-if="gridType === 'domain'" @click="add")
11 | font-awesome-icon(icon="plus")
12 |
13 | tr
14 | th(v-for="column in columnData" :style="style(column)") {{ column.name }}
15 | .table_wrapper(:style="`height: ${reHeight}px;`")
16 | table
17 | tbody
18 | tr(v-for="(entry, col) in data" :index="col")
19 | td(v-for="(column, row) in columnData"
20 | :style="style(column)"
21 | :class="{ edit: !entry.ui[`isRead${column.key}`] && entry.ui[`isRead${column.key}`] !== undefined }")
22 | button(v-if="type(column) === 'button'" @click="onBtn(entry.id)")
23 | font-awesome-icon(:icon="column.icon")
24 | input(v-else :type="type(column)" :value="entry[column.key]" :index="row"
25 | :class="{ edit: !entry.ui[`isRead${column.key}`] && entry.ui[`isRead${column.key}`] !== undefined }"
26 | :checked="type(column) === 'checkbox' && entry[column.key]"
27 | :readonly="entry.ui[`isRead${column.key}`]"
28 | spellcheck="false"
29 | :placeholder="column.name"
30 | @keyup.enter="onEnterEditor($event, entry.ui[`isRead${column.key}`], column.key, entry.id)"
31 | @dblclick="onEnterEditor($event, entry.ui[`isRead${column.key}`], column.key, entry.id)"
32 | @keydown="onKeyArrowMove($event, entry.ui[`isRead${column.key}`])"
33 | @keydown.9="lastTabFocus($event, row === columnData.length - 1)"
34 | @focus="onFocus($event, type(column) === 'checkbox')"
35 | @blur="onBlur($event, type(column) === 'checkbox')"
36 | @change="change($event, column.key, entry.id)")
37 |
38 |
39 |
250 |
251 |
369 |
--------------------------------------------------------------------------------
/src/renderer/components/editor/Modal.vue:
--------------------------------------------------------------------------------
1 |
2 | .modal
3 | .modal_background
4 | .modal_box
5 | .modal_btn
6 | button.close(title="ESC" @click="onClose")
7 | font-awesome-icon(icon="times")
8 | button.add(v-if="type === 'project'" @click="projectAdd" title="Ctrl + N")
9 | font-awesome-icon(icon="plus")
10 |
11 | .modal_head
12 | h3(v-if="type === 'view'") view setting
13 | h3(v-else-if="type === 'help'") help
14 | h3(v-else-if="type === 'project'") project
15 | h3(v-else-if="type === 'model'") model
16 | .modal_body(:class="{ help: type === 'help', project: type === 'project' || type === 'model' }")
17 |
18 | .modal_title(v-if="type === 'view'") canvas size
19 | .modal_content(v-if="type === 'view'")
20 | span x
21 | input(type="text" v-model="CANVAS_WIDTH" spellcheck="false"
22 | @change="onChangeCanvasWidth")
23 | span y
24 | input(type="text" v-model="CANVAS_HEIGHT" spellcheck="false"
25 | @change="onChangeCanvasHeight")
26 |
27 | .modal_title.help(v-if="type === 'help'") base
28 | .modal_content.help(v-if="type === 'help'")
29 | span(v-html="`right click${space(18)}- QuickMenu`")
30 | span(v-html="`Ctrl + Enter${space(6)}- Hint Choice`")
31 | .modal_content.help(v-if="type === 'help'")
32 | span(v-html="`Ctrl + Z${space(23)}- Undo`")
33 | span(v-html="`Ctrl + Shift + Z - Redo`")
34 | .modal_content.help(v-if="type === 'help'")
35 | span(v-html="`ESC${space(29)}- Event All stop`")
36 | span(v-html="`Ctrl + 1 - 9${space(8)} - Model Choice`")
37 | .modal_content.help(v-if="type === 'help'")
38 | span(v-html="`Ctrl + Shift + Delete - Model delete`")
39 | span(v-html="`Arrow key${space(7)} - Model focus move`")
40 | .modal_content.help(v-if="type === 'help'")
41 | span(v-html="`drag${space(27)}- Model move`")
42 | span(v-html="`Ctrl + S${space(14)}- save`")
43 |
44 | .modal_title.help(v-if="type === 'help'") table, memo
45 | .modal_content.help(v-if="type === 'help'")
46 | span(v-html="`Ctrl + drag${space(16)}- multi selected`")
47 | span(v-html="`Ctrl + click${space(8)}- multi selected`")
48 | .modal_content.help(v-if="type === 'help'")
49 | span(v-html="`Ctrl + A${space(22)}- selected All`")
50 | span(v-html="`Ctrl + Delete${space(4)}- delete`")
51 | .modal_content.help(v-if="type === 'help'")
52 | span(v-html="`drag${space(26)}- move`")
53 | span(v-html="`Arrow key${space(8)}- focus move`")
54 | .modal_content.help(v-if="type === 'help'")
55 | span(v-html="`Enter${space(25)}- edit on/off`")
56 | span(v-html="`Alt + Enter${space(8)}- column add`")
57 | .modal_content.help(v-if="type === 'help'")
58 | span(v-html="`Alt + Delete${space(14)}- column delete`")
59 | span(v-html="`drag${space(18)}- column move`")
60 |
61 | .modal_title.help(v-if="type === 'help'") Canvas
62 | .modal_content.help(v-if="type === 'help'")
63 | span(v-html="`drag${space(26)}- move`")
64 | span(v-html="`preview drag${space(3)}- move`")
65 |
66 | .modal_title.help(v-if="type === 'help'") QuickMenu
67 | .modal_content.help(v-if="type === 'help'")
68 | span(v-html="`Alt + N${space(22)}- New Model`")
69 | span(v-html="`Alt + T${space(15)}- New Table`")
70 | .modal_content.help(v-if="type === 'help'")
71 | span(v-html="`Alt + M${space(22)} - New Memo`")
72 | span(v-html="`Alt + K${space(15)}- Primary key`")
73 | .modal_content.help(v-if="type === 'help'")
74 | span(v-html="`Alt + 1${space(23)}- 1 : 1`")
75 | span(v-html="`Alt + 2${space(16)}- 1 : N`")
76 |
77 | ul.modal_content(v-if="type === 'project'")
78 | li(v-for="item in projectList" :class="{ project_active: projectId === item.id }")
79 | span(@click="historyLoaded(item.id)")
80 | font-awesome-icon(:icon="projectId === item.id ? 'folder-open' : 'folder'")
81 | input(type="text" :value="item.name" @change="projectNameChange($event, item.id)" spellcheck="false" :title="item.path")
82 | button(@click="historyDelete(item.id)")
83 | font-awesome-icon(icon="times")
84 |
85 | ul.modal_content(v-if="type === 'model'")
86 | li(v-for="item in modelList")
87 | span(@click="historyLoaded(item.id)")
88 | font-awesome-icon(icon="history")
89 | span.model {{ item.name }} - {{ item.update_date }}
90 | button(@click="historyDelete(item.id)")
91 | font-awesome-icon(icon="times")
92 |
93 |
94 |
214 |
215 |
402 |
--------------------------------------------------------------------------------
/src/renderer/components/editor/QuickMenu.vue:
--------------------------------------------------------------------------------
1 |
2 | ul.quick_menu(v-if="isShow"
3 | :style="`top: ${top}px; left: ${left}px; z-index: ${zIndex};`")
4 |
5 | li(v-for="menu in menus" :key="menu.id"
6 | :class="{ quick_menu_pk: menu.type === 'pk' }"
7 | @click="menuAction(menu.type)")
8 |
9 | span
10 | img(v-if="menu.icon !== '' && menu.isImg"
11 | :src="menu.icon")
12 |
13 | font-awesome-icon(v-else-if="!menu.isImg"
14 | :icon="menu.icon"
15 | :class="{ pk: menu.icon === 'key' }")
16 |
17 | span(v-else)
18 | span {{ menu.name }}
19 | span {{ menu.keymap }}
20 |
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 |
2 | #erd
3 | canvas-menu
4 | quick-menu
5 | .canvas
6 | canvas-main
7 | canvas-svg
8 |
9 |
10 |
31 |
32 |
115 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dineug/vuerd-electron/4e9be05b9405b09dfcd6dbc87dd2af140e846c48/static/.gitkeep
--------------------------------------------------------------------------------