├── .babelrc ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .eslintrc.js ├── .gitignore ├── README.md ├── build └── icons │ ├── 128.png │ ├── 256.png │ ├── 32.png │ ├── 64.png │ ├── icon.icns │ ├── icon.ico │ ├── out │ ├── choose.png │ ├── logo-vue1.png │ ├── logo-vue2.png │ ├── logo-vue3.png │ ├── logo-vue4.png │ ├── logo-yellow.png │ ├── logo.png │ └── votes.txt │ └── src │ ├── logo-grey-checkers.svg │ ├── logo-grey.svg │ ├── logo-vue1.svg │ ├── logo-vue2.svg │ ├── logo-vue3.svg │ ├── logo-vue4.svg │ └── logo-yellow.svg ├── dist ├── electron │ └── .gitkeep └── web │ └── .gitkeep ├── package.json ├── src ├── index.ejs ├── main │ ├── index.dev.js │ └── index.js └── renderer │ ├── App.vue │ ├── RepositoryHub.js │ ├── assets │ ├── .gitkeep │ ├── css │ │ ├── animate.css │ │ ├── custom.css │ │ ├── fonts │ │ │ ├── MaterialIcons-Regular.woff2 │ │ │ ├── Roboto-Regular.woff2 │ │ │ ├── Roboto │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ └── Roboto-ThinItalic.ttf │ │ │ └── icon.css │ │ ├── materialize-0.1.min.css │ │ └── materialize.min.css │ ├── img │ │ └── user.png │ └── logo.png │ ├── components │ ├── Avatar.vue │ ├── Blih.vue │ ├── Blih │ │ ├── Page.vue │ │ ├── Repositories.vue │ │ ├── Repository.vue │ │ ├── Settings.vue │ │ ├── SshKey.vue │ │ └── SshKeys.vue │ ├── Charts │ │ ├── ContributionsChart.js │ │ ├── HistoryChart.js │ │ └── LanguageChart.js │ ├── Dialogs │ │ ├── DialogBasic.vue │ │ ├── DialogFile.vue │ │ └── DialogForm.vue │ ├── Error.vue │ ├── FileTree.vue │ ├── Git.vue │ ├── Loader.vue │ ├── Login.vue │ ├── TileAvatar.vue │ └── Welcome.vue │ ├── helpers.js │ ├── main.js │ ├── mixins.js │ ├── router │ └── index.js │ └── store │ ├── actions.js │ ├── defaults.js │ ├── getters.js │ ├── index.js │ ├── mutations.js │ └── state.js ├── static ├── .gitkeep └── 256.png └── todo.txt /.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 | webpack(config, (err, stats) => { 76 | if (err) reject(err.stack || err) 77 | else if (stats.hasErrors()) { 78 | let err = '' 79 | 80 | stats.toString({ 81 | chunks: false, 82 | colors: true 83 | }) 84 | .split(/\r?\n/) 85 | .forEach(line => { 86 | err += ` ${line}\n` 87 | }) 88 | 89 | reject(err) 90 | } else { 91 | resolve(stats.toString({ 92 | chunks: false, 93 | colors: true 94 | })) 95 | } 96 | }) 97 | }) 98 | } 99 | 100 | function web () { 101 | del.sync(['dist/web/*', '!.gitkeep']) 102 | webpack(webConfig, (err, stats) => { 103 | if (err || stats.hasErrors()) console.log(err) 104 | 105 | console.log(stats.toString({ 106 | chunks: false, 107 | colors: true 108 | })) 109 | 110 | process.exit() 111 | }) 112 | } 113 | 114 | function greeting () { 115 | const cols = process.stdout.columns 116 | let text = '' 117 | 118 | if (cols > 85) text = 'lets-build' 119 | else if (cols > 60) text = 'lets-|build' 120 | else text = false 121 | 122 | if (text && !isCI) { 123 | say(text, { 124 | colors: ['yellow'], 125 | font: 'simple3d', 126 | space: false 127 | }) 128 | } else console.log(chalk.yellow.bold('\n lets-build')) 129 | console.log() 130 | } 131 | -------------------------------------------------------------------------------- /.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 | 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.plugin('compilation', compilation => { 52 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.plugin('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 | 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.plugin('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 | electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')]) 118 | 119 | electronProcess.stdout.on('data', data => { 120 | electronLog(data, 'blue') 121 | }) 122 | electronProcess.stderr.on('data', data => { 123 | electronLog(data, 'red') 124 | }) 125 | 126 | electronProcess.on('close', () => { 127 | if (!manualRestart) process.exit() 128 | }) 129 | } 130 | 131 | function electronLog (data, color) { 132 | let log = '' 133 | data = data.toString().split(/\r?\n/) 134 | data.forEach(line => { 135 | log += ` ${line}\n` 136 | }) 137 | if (/[0-9A-z]+/.test(log)) { 138 | console.log( 139 | chalk[color].bold('┏ Electron -------------------') + 140 | '\n\n' + 141 | log + 142 | chalk[color].bold('┗ ----------------------------') + 143 | '\n' 144 | ) 145 | } 146 | } 147 | 148 | function greeting () { 149 | const cols = process.stdout.columns 150 | let text = '' 151 | 152 | if (cols > 104) text = 'electron-vue' 153 | else if (cols > 76) text = 'electron-|vue' 154 | else text = false 155 | 156 | if (text) { 157 | say(text, { 158 | colors: ['yellow'], 159 | font: 'simple3d', 160 | space: false 161 | }) 162 | } else console.log(chalk.yellow.bold('\n electron-vue')) 163 | console.log(chalk.blue(' getting ready...') + '\n') 164 | } 165 | 166 | function init () { 167 | greeting() 168 | 169 | Promise.all([startRenderer(), startMain()]) 170 | .then(() => { 171 | startElectron() 172 | }) 173 | .catch(err => { 174 | console.error(err) 175 | }) 176 | } 177 | 178 | init() 179 | -------------------------------------------------------------------------------- /.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 ExtractTextPlugin = require('extract-text-webpack-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | 14 | /** 15 | * List of node_modules to include in webpack bundle 16 | * 17 | * Required for specific packages like Vue UI libraries 18 | * that provide pure *.vue files that need compiling 19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 20 | */ 21 | let whiteListedModules = ['vue'] 22 | 23 | let rendererConfig = { 24 | devtool: '#cheap-module-eval-source-map', 25 | entry: { 26 | renderer: path.join(__dirname, '../src/renderer/main.js') 27 | }, 28 | externals: [ 29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.(js|vue)$/, 35 | enforce: 'pre', 36 | exclude: /node_modules/, 37 | use: { 38 | loader: 'eslint-loader', 39 | options: { 40 | formatter: require('eslint-friendly-formatter') 41 | } 42 | } 43 | }, 44 | { 45 | test: /\.css$/, 46 | use: ExtractTextPlugin.extract({ 47 | fallback: 'style-loader', 48 | use: 'css-loader' 49 | }) 50 | }, 51 | { 52 | test: /\.html$/, 53 | use: 'vue-html-loader' 54 | }, 55 | { 56 | test: /\.js$/, 57 | use: 'babel-loader', 58 | exclude: /node_modules/ 59 | }, 60 | { 61 | test: /\.node$/, 62 | use: 'node-loader' 63 | }, 64 | { 65 | test: /\.vue$/, 66 | use: { 67 | loader: 'vue-loader', 68 | options: { 69 | extractCSS: process.env.NODE_ENV === 'production', 70 | loaders: { 71 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 72 | scss: 'vue-style-loader!css-loader!sass-loader' 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 79 | use: { 80 | loader: 'url-loader', 81 | query: { 82 | limit: 10000, 83 | name: 'imgs/[name]--[folder].[ext]' 84 | } 85 | } 86 | }, 87 | { 88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | name: 'media/[name]--[folder].[ext]' 93 | } 94 | }, 95 | { 96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 97 | use: { 98 | loader: 'url-loader', 99 | query: { 100 | limit: 10000, 101 | name: 'fonts/[name]--[folder].[ext]' 102 | } 103 | } 104 | } 105 | ] 106 | }, 107 | node: { 108 | __dirname: process.env.NODE_ENV !== 'production', 109 | __filename: process.env.NODE_ENV !== 'production' 110 | }, 111 | plugins: [ 112 | new ExtractTextPlugin('styles.css'), 113 | new HtmlWebpackPlugin({ 114 | filename: 'index.html', 115 | template: path.resolve(__dirname, '../src/index.ejs'), 116 | minify: { 117 | collapseWhitespace: true, 118 | removeAttributeQuotes: true, 119 | removeComments: true 120 | }, 121 | nodeModules: process.env.NODE_ENV !== 'production' 122 | ? path.resolve(__dirname, '../node_modules') 123 | : false 124 | }), 125 | new webpack.HotModuleReplacementPlugin(), 126 | new webpack.NoEmitOnErrorsPlugin() 127 | ], 128 | output: { 129 | filename: '[name].js', 130 | libraryTarget: 'commonjs2', 131 | path: path.join(__dirname, '../dist/electron') 132 | }, 133 | resolve: { 134 | alias: { 135 | '@': path.join(__dirname, '../src/renderer'), 136 | 'vue$': 'vue/dist/vue.esm.js' 137 | }, 138 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 139 | }, 140 | target: 'electron-renderer' 141 | } 142 | 143 | /** 144 | * Adjust rendererConfig for development settings 145 | */ 146 | if (process.env.NODE_ENV !== 'production') { 147 | rendererConfig.plugins.push( 148 | new webpack.DefinePlugin({ 149 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 150 | }) 151 | ) 152 | } 153 | 154 | /** 155 | * Adjust rendererConfig for production settings 156 | */ 157 | if (process.env.NODE_ENV === 'production') { 158 | rendererConfig.devtool = '' 159 | 160 | rendererConfig.plugins.push( 161 | new BabiliWebpackPlugin(), 162 | new CopyWebpackPlugin([ 163 | { 164 | from: path.join(__dirname, '../static'), 165 | to: path.join(__dirname, '../dist/electron/static'), 166 | ignore: ['.*'] 167 | } 168 | ]), 169 | new webpack.DefinePlugin({ 170 | 'process.env.NODE_ENV': '"production"' 171 | }), 172 | new webpack.LoaderOptionsPlugin({ 173 | minimize: true 174 | }) 175 | ) 176 | } 177 | 178 | module.exports = rendererConfig 179 | -------------------------------------------------------------------------------- /.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 ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | 13 | let webConfig = { 14 | devtool: '#cheap-module-eval-source-map', 15 | entry: { 16 | web: path.join(__dirname, '../src/renderer/main.js') 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js|vue)$/, 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: /\.css$/, 33 | use: ExtractTextPlugin.extract({ 34 | fallback: 'style-loader', 35 | use: 'css-loader' 36 | }) 37 | }, 38 | { 39 | test: /\.html$/, 40 | use: 'vue-html-loader' 41 | }, 42 | { 43 | test: /\.js$/, 44 | use: 'babel-loader', 45 | include: [ path.resolve(__dirname, '../src/renderer') ], 46 | exclude: /node_modules/ 47 | }, 48 | { 49 | test: /\.vue$/, 50 | use: { 51 | loader: 'vue-loader', 52 | options: { 53 | extractCSS: true, 54 | loaders: { 55 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 56 | scss: 'vue-style-loader!css-loader!sass-loader' 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 63 | use: { 64 | loader: 'url-loader', 65 | query: { 66 | limit: 10000, 67 | name: 'imgs/[name].[ext]' 68 | } 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | use: { 74 | loader: 'url-loader', 75 | query: { 76 | limit: 10000, 77 | name: 'fonts/[name].[ext]' 78 | } 79 | } 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | new ExtractTextPlugin('styles.css'), 85 | new HtmlWebpackPlugin({ 86 | filename: 'index.html', 87 | template: path.resolve(__dirname, '../src/index.ejs'), 88 | minify: { 89 | collapseWhitespace: true, 90 | removeAttributeQuotes: true, 91 | removeComments: true 92 | }, 93 | nodeModules: false 94 | }), 95 | new webpack.DefinePlugin({ 96 | 'process.env.IS_WEB': 'true' 97 | }), 98 | new webpack.HotModuleReplacementPlugin(), 99 | new webpack.NoEmitOnErrorsPlugin() 100 | ], 101 | output: { 102 | filename: '[name].js', 103 | path: path.join(__dirname, '../dist/web') 104 | }, 105 | resolve: { 106 | alias: { 107 | '@': path.join(__dirname, '../src/renderer'), 108 | 'vue$': 'vue/dist/vue.esm.js' 109 | }, 110 | extensions: ['.js', '.vue', '.json', '.css'] 111 | }, 112 | target: 'web' 113 | } 114 | 115 | /** 116 | * Adjust webConfig for production settings 117 | */ 118 | if (process.env.NODE_ENV === 'production') { 119 | webConfig.devtool = '' 120 | 121 | webConfig.plugins.push( 122 | new BabiliWebpackPlugin(), 123 | new CopyWebpackPlugin([ 124 | { 125 | from: path.join(__dirname, '../static'), 126 | to: path.join(__dirname, '../dist/web/static'), 127 | ignore: ['.*'] 128 | } 129 | ]), 130 | new webpack.DefinePlugin({ 131 | 'process.env.NODE_ENV': '"production"' 132 | }), 133 | new webpack.LoaderOptionsPlugin({ 134 | minimize: true 135 | }) 136 | ) 137 | } 138 | 139 | module.exports = webConfig 140 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module', 6 | ecmaVersion: '6' 7 | }, 8 | env: { 9 | browser: true, 10 | node: true, 11 | 'es6': true 12 | }, 13 | extends: 'standard', 14 | globals: { 15 | __static: true 16 | }, 17 | plugins: [ 18 | 'html' 19 | ], 20 | 'rules': { 21 | 'indent': [2, 'tab', { SwitchCase: 1 }], 22 | 'no-tabs': 0, 23 | 'arrow-parens': 0, 24 | 'no-console': process.env.NODE_ENV === 'production' ? 2 : 0, 25 | 'no-unused-vars': [2, { 'argsIgnorePattern': '^_' }], 26 | 'semi': [2, 'always'] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | logo 4 |

5 | 6 |

7 | Blih Vue 8 |

9 | 10 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com) 11 | 12 | > Managing repositories has never been easier. 13 | 14 | Blih Vue is an intuitive and easy-to-use app that helps you efficiently manage your Epitech repositories. 15 | 16 | ## Easy to use 17 | 18 | Sick of typing your password or to mess up the arguments order every time you use the Blih python script ? 19 | 20 | With Blih Vue, authenticate once and you're good to go ! Navigate seamlessly through the application and manage your keys and repositories with ease. 21 | 22 | ## Features 23 | 24 | * Browse, create, delete and edit your repositories :cloud: 25 | * Edit collaborators and their rights :busts_in_silhouette: 26 | * Get git insights on a repository :bar_chart: 27 | * Manage your SSH keys :key: 28 | 29 | ## Download 30 | 31 | Coming soon 32 | 33 | ## Build instructions 34 | 35 | Simply run `npm run build`. This will build the application for the current platform into the `build` folder. 36 | 37 | :information_source: You will first need to compile the native `nodegit` it on your platform or to download a pre-built binary. This step takes place when running `npm install`, but it may fail for several reasons. 38 | 39 | Then, `electron-rebuild` will rebuild the module against the version of Node.js that Electron is using. 40 | 41 | ### Troubleshooting 42 | 43 | #### When installing 44 | 45 | > ... 46 | 47 | This means you do not have the build tools required to compile native modules. 48 | * On Windows: 49 | Run `npm i -g windows-build-tools` as administrator to install C++ build tools, or do it manually. 50 | 51 | > fatal error: curl/curl.h: No such file or directory 52 | 53 | This happens when compiling nodegit from source. It requires the libcurl-dev package to be installed. 54 | * On Ubuntu: 55 | Run `apt install libcurl-dev` and install the appropriate package (`libcurl4-gnutls-dev` or similar). 56 | 57 | #### When running 58 | 59 | > libcurl.so.4: cannot open shared object file: No such file or directory 60 | 61 | That means nodegit cannot find the libcurl shared library. -------------------------------------------------------------------------------- /build/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/128.png -------------------------------------------------------------------------------- /build/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/256.png -------------------------------------------------------------------------------- /build/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/32.png -------------------------------------------------------------------------------- /build/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/64.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/icon.ico -------------------------------------------------------------------------------- /build/icons/out/choose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/choose.png -------------------------------------------------------------------------------- /build/icons/out/logo-vue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo-vue1.png -------------------------------------------------------------------------------- /build/icons/out/logo-vue2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo-vue2.png -------------------------------------------------------------------------------- /build/icons/out/logo-vue3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo-vue3.png -------------------------------------------------------------------------------- /build/icons/out/logo-vue4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo-vue4.png -------------------------------------------------------------------------------- /build/icons/out/logo-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo-yellow.png -------------------------------------------------------------------------------- /build/icons/out/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/build/icons/out/logo.png -------------------------------------------------------------------------------- /build/icons/out/votes.txt: -------------------------------------------------------------------------------- 1 | 1: 2 2 | 2: 4 3 | 3: 1 4 | 4: 2 5 | 5: 1 6 | 6: 2 -------------------------------------------------------------------------------- /build/icons/src/logo-grey-checkers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | image/svg+xml 16 | 17 | blih 18 | 19 | 20 | 21 | 22 | blih 23 | 24 | Layer 1 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /build/icons/src/logo-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/icons/src/logo-vue1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/icons/src/logo-vue2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/icons/src/logo-vue3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/icons/src/logo-vue4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/icons/src/logo-yellow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | blih 9 | 10 | 11 | 12 | 13 | blih 14 | 15 | Layer 1 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/dist/electron/.gitkeep -------------------------------------------------------------------------------- /dist/web/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/dist/web/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blih-vue", 3 | "version": "0.0.0", 4 | "author": "Lucien Le Roux ", 5 | "description": "An electron-vue project", 6 | "license": null, 7 | "bugs": { 8 | "url": "https://github.com/kabukki/blih-vue/issues" 9 | }, 10 | "main": "./dist/electron/main.js", 11 | "scripts": { 12 | "build": "node .electron-vue/build.js && electron-builder", 13 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 14 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 15 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 16 | "dev": "node .electron-vue/dev-runner.js", 17 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src", 18 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src", 19 | "pack": "npm run pack:main && npm run pack:renderer", 20 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 21 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 22 | "postinstall": "npm run lint:fix && electron-rebuild -f -w nodegit" 23 | }, 24 | "build": { 25 | "productName": "BlihVue", 26 | "appId": "eu.datsite.kabukki.blihvue", 27 | "directories": { 28 | "output": "build" 29 | }, 30 | "files": [ 31 | "dist/electron/**/*" 32 | ], 33 | "dmg": { 34 | "contents": [ 35 | { 36 | "x": 410, 37 | "y": 150, 38 | "type": "link", 39 | "path": "/Applications" 40 | }, 41 | { 42 | "x": 130, 43 | "y": 150, 44 | "type": "file" 45 | } 46 | ] 47 | }, 48 | "mac": { 49 | "icon": "build/icons/icon.icns" 50 | }, 51 | "win": { 52 | "icon": "build/icons/256.png" 53 | }, 54 | "linux": { 55 | "icon": "build/icons" 56 | } 57 | }, 58 | "dependencies": { 59 | "blih": "^1.7.3", 60 | "chart.js": "^2.7.1", 61 | "electron-store": "^1.3.0", 62 | "fs-extra": "^5.0.0", 63 | "language-map": "^1.3.0", 64 | "moment": "^2.19.3", 65 | "nodegit": "^0.20.3", 66 | "rimraf": "^2.6.2", 67 | "ssh-fingerprint": "0.0.1", 68 | "vue": "^2.3.3", 69 | "vue-chartjs": "^3.2.1", 70 | "vue-router": "^2.5.3", 71 | "vuetify": "^1.0.6", 72 | "vuex": "^2.3.1" 73 | }, 74 | "devDependencies": { 75 | "babel-core": "^6.25.0", 76 | "babel-eslint": "^7.2.3", 77 | "babel-loader": "^7.1.1", 78 | "babel-plugin-transform-runtime": "^6.23.0", 79 | "babel-preset-env": "^1.6.0", 80 | "babel-preset-stage-0": "^6.24.1", 81 | "babel-preset-stage-3": "^6.24.1", 82 | "babel-register": "^6.24.1", 83 | "babili-webpack-plugin": "^0.1.2", 84 | "blih": "^1.7.3", 85 | "cfonts": "^1.1.3", 86 | "chalk": "^2.1.0", 87 | "copy-webpack-plugin": "^4.0.1", 88 | "cross-env": "^5.0.5", 89 | "css-loader": "^0.28.4", 90 | "del": "^3.0.0", 91 | "devtron": "^1.4.0", 92 | "electron": "1.7.11", 93 | "electron-builder": "^19.19.1", 94 | "electron-debug": "^1.4.0", 95 | "electron-devtools-installer": "^2.2.0", 96 | "electron-rebuild": "^1.7.3", 97 | "eslint": "^4.17.0", 98 | "eslint-config-standard": "^11.0.0-beta.0", 99 | "eslint-friendly-formatter": "^3.0.0", 100 | "eslint-loader": "^1.9.0", 101 | "eslint-plugin-html": "^3.1.1", 102 | "eslint-plugin-import": "^2.8.0", 103 | "eslint-plugin-node": "^5.2.1", 104 | "eslint-plugin-promise": "^3.6.0", 105 | "eslint-plugin-standard": "^3.0.1", 106 | "extract-text-webpack-plugin": "^3.0.0", 107 | "file-loader": "^0.11.2", 108 | "html-webpack-plugin": "^2.30.1", 109 | "multispinner": "^0.2.1", 110 | "node-loader": "^0.6.0", 111 | "style-loader": "^0.18.2", 112 | "url-loader": "^0.5.9", 113 | "vue-html-loader": "^1.2.4", 114 | "vue-loader": "^13.0.5", 115 | "vue-style-loader": "^3.0.1", 116 | "vue-template-compiler": "^2.4.2", 117 | "webpack": "^3.5.2", 118 | "webpack-dev-server": "^2.7.1", 119 | "webpack-hot-middleware": "^2.18.2" 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blih Vue 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 | // Set environment for development 11 | process.env.NODE_ENV = 'development' 12 | 13 | // Install `electron-debug` with `devtron` 14 | require('electron-debug')({ showDevTools: true }) 15 | 16 | // Install `vue-devtools` 17 | require('electron').app.on('ready', () => { 18 | let installExtension = require('electron-devtools-installer') 19 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 20 | .then(() => {}) 21 | .catch(err => { 22 | console.log('Unable to install `vue-devtools`: \n', err) 23 | }) 24 | }) 25 | 26 | // Require `main` process to boot app 27 | require('./index') 28 | -------------------------------------------------------------------------------- /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: 900, 24 | useContentSize: true, 25 | width: 1800, 26 | webPreferences: { 27 | webSecurity: false 28 | } 29 | }); 30 | 31 | mainWindow.loadURL(winURL); 32 | 33 | mainWindow.on('closed', () => { 34 | mainWindow = null; 35 | }); 36 | mainWindow.webContents.openDevTools(); 37 | } 38 | 39 | app.on('ready', createWindow); 40 | 41 | app.on('window-all-closed', () => { 42 | if (process.platform !== 'darwin') { 43 | app.quit(); 44 | } 45 | }); 46 | 47 | app.on('activate', () => { 48 | if (mainWindow === null) { 49 | createWindow(); 50 | } 51 | }); 52 | 53 | /** 54 | * Auto Updater 55 | * 56 | * Uncomment the following code below and install `electron-updater` to 57 | * support auto updating. Code Signing with a valid certificate is required. 58 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 59 | */ 60 | 61 | /* 62 | import { autoUpdater } from 'electron-updater' 63 | 64 | autoUpdater.on('update-downloaded', () => { 65 | autoUpdater.quitAndInstall() 66 | }) 67 | 68 | app.on('ready', () => { 69 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 70 | }) 71 | */ 72 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/renderer/RepositoryHub.js: -------------------------------------------------------------------------------- 1 | import Git from 'nodegit'; 2 | import fs from 'fs-extra'; 3 | import rimraf from 'rimraf'; 4 | import path from 'path'; 5 | import store from './store'; 6 | 7 | export default class RepositoryHub { 8 | constructor (directory) { 9 | this.root = directory; 10 | } 11 | 12 | /* 13 | * Set up the hub (bare name) 14 | * Creates directory if it does not exist. 15 | */ 16 | async init () { 17 | return fs.ensureDir(this.root); 18 | } 19 | 20 | /* 21 | * Set the current user 22 | */ 23 | async use (name) { 24 | this.path = path.join(this.root, name); 25 | return fs.ensureDir(this.path); 26 | } 27 | 28 | /* 29 | * Add a repository 30 | */ 31 | async add (name, url) { 32 | if (this.path) { 33 | const repoPath = path.join(this.path, name); 34 | try { 35 | await fs.ensureDir(repoPath); 36 | const repo = await Git.Repository.init(repoPath, 1); // 1 means the repository will be bare 37 | await Git.Remote.create(repo, 'origin', url); 38 | } catch (err) { 39 | return err; 40 | } 41 | } else { 42 | return Promise.reject(new Error('User is not set')); 43 | } 44 | } 45 | 46 | /* 47 | * If 'name' is specified, deletes this repository, otherwise delete all repositories 48 | */ 49 | delete (name) { 50 | if (this.path) { 51 | return new Promise((resolve, reject) => { 52 | rimraf(path.join(this.path, name || '.'), { 53 | disableGlob: true 54 | }, err => { 55 | if (err) { 56 | reject(err); 57 | } else { 58 | resolve(); 59 | } 60 | }); 61 | }); 62 | } else { 63 | return Promise.reject(new Error('User is not set')); 64 | } 65 | } 66 | 67 | /* 68 | * Perform a git fetch on a repository 69 | */ 70 | update (name, onProgress) { 71 | if (this.path) { 72 | return Git.Repository.openBare(path.join(this.path, name)) 73 | .then(repo => { 74 | return repo.fetch('origin', { 75 | callbacks: { 76 | credentials: (_url, _username) => Git.Cred.sshKeyNew( 77 | 'git', 78 | store.getters.publicKeyPath, 79 | store.getters.privateKeyPath, 80 | '' 81 | ), 82 | transferProgress: { 83 | // Reduce throttling to get a more accurate progress 84 | throttle: 50, 85 | callback (progress) { 86 | if (onProgress) { 87 | onProgress(progress); 88 | } 89 | } 90 | } 91 | } 92 | }); 93 | }); 94 | } else { 95 | return Promise.reject(new Error('User is not set')); 96 | } 97 | } 98 | 99 | /* 100 | * Get history for a repository on a given branch, defaulting to master 101 | */ 102 | async history (name, branch) { 103 | if (this.path) { 104 | branch = branch || 'master'; 105 | const repo = await Git.Repository.openBare(path.join(this.path, name)); 106 | const first = await repo.getBranchCommit(`origin/${branch}`); 107 | return new Promise( 108 | (resolve, reject) => { 109 | let history = first.history(Git.Revwalk.SORT.TIME); 110 | history.on('end', resolve); 111 | history.on('error', reject); 112 | history.start(); 113 | } 114 | ); 115 | } else { 116 | return Promise.reject(new Error('User is not set')); 117 | } 118 | } 119 | 120 | /** 121 | * Recursively get children for a given tree 122 | * @param {Tree} tree The tree 123 | * @return {[Object]} Array of children (objects) 124 | */ 125 | async getChildren (tree) { 126 | let files = []; 127 | const filemodeStr = { 128 | '0': 'unreadable', 129 | '16384': 'tree', 130 | '33188': 'blob', 131 | '33261': 'executable', 132 | '40960': 'link', 133 | '57344': 'commit' 134 | }; 135 | 136 | for (const entry of tree.entries()) { 137 | const children = entry.isTree() ? await this.getChildren(await entry.getTree()) : null; 138 | const blob = entry.isBlob() ? await entry.getBlob() : null; 139 | files.push({ 140 | name: entry.name(), 141 | sha: entry.sha(), 142 | type: filemodeStr[entry.filemode()], 143 | children, 144 | blob 145 | }); 146 | } 147 | return files; 148 | } 149 | 150 | /* 151 | * Get file tree for a repository 152 | */ 153 | async tree (name, branch) { 154 | if (this.path) { 155 | branch = branch || 'master'; 156 | const repo = await Git.Repository.openBare(path.join(this.path, name)); 157 | const first = await repo.getBranchCommit(`origin/${branch}`); 158 | const tree = await first.getTree(); 159 | 160 | return this.getChildren(tree); 161 | } else { 162 | return Promise.reject(new Error('User is not set')); 163 | } 164 | } 165 | 166 | /* 167 | * Get branches 168 | */ 169 | async branches (name) { 170 | if (this.path) { 171 | const repo = await Git.Repository.openBare(path.join(this.path, name)); 172 | const refs = await repo.getReferences(Git.Reference.TYPE.LISTALL); 173 | return refs.filter(ref => ref.isRemote()); 174 | } else { 175 | return Promise.reject(new Error('User is not set')); 176 | } 177 | } 178 | 179 | /** 180 | * Copy a repository to a non-bare one at the specified destination 181 | * @param {[type]} name the repository to copy 182 | * @param {[type]} destination where to copy it 183 | * @return {[type]} [description] 184 | */ 185 | async copy (name, destination) { 186 | if (!this.path) { 187 | return Promise.reject(new Error('User is not set')); 188 | } else if (!destination) { 189 | return Promise.reject(new Error('Destination not specified')); 190 | } else { 191 | const newPath = path.join(destination, name); 192 | const oldRepo = await Git.Repository.openBare(path.join(this.path, name)); 193 | /* Create new repo */ 194 | await fs.ensureDir(newPath); 195 | await fs.copy(oldRepo.path(), path.join(newPath, '.git')); 196 | let newRepo = await Git.Repository.init(newPath, 0); 197 | /* Set it to non-bare */ 198 | let config = await newRepo.config(); 199 | await config.setString('core.bare', 'false'); 200 | /* Checkout master */ 201 | const first = await newRepo.getBranchCommit('origin/master'); 202 | const branch = await newRepo.createBranch('master', first, 0); 203 | await newRepo.checkoutBranch(branch); 204 | } 205 | } 206 | }; 207 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/css/animate.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /*! 4 | * animate.css -http://daneden.me/animate 5 | * Version - 3.5.2 6 | * Licensed under the MIT license - http://opensource.org/licenses/MIT 7 | * 8 | * Copyright (c) 2017 Daniel Eden 9 | */ 10 | 11 | .animated { 12 | animation-duration: 1s; 13 | animation-fill-mode: both; 14 | } 15 | 16 | .animated.infinite { 17 | animation-iteration-count: infinite; 18 | } 19 | 20 | .animated.hinge { 21 | animation-duration: 2s; 22 | } 23 | 24 | .animated.flipOutX, 25 | .animated.flipOutY, 26 | .animated.bounceIn, 27 | .animated.bounceOut { 28 | animation-duration: .75s; 29 | } 30 | 31 | @keyframes bounce { 32 | from, 20%, 53%, 80%, to { 33 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 34 | transform: translate3d(0,0,0); 35 | } 36 | 37 | 40%, 43% { 38 | animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 39 | transform: translate3d(0, -30px, 0); 40 | } 41 | 42 | 70% { 43 | animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 44 | transform: translate3d(0, -15px, 0); 45 | } 46 | 47 | 90% { 48 | transform: translate3d(0,-4px,0); 49 | } 50 | } 51 | 52 | .bounce { 53 | animation-name: bounce; 54 | transform-origin: center bottom; 55 | } 56 | 57 | @keyframes flash { 58 | from, 50%, to { 59 | opacity: 1; 60 | } 61 | 62 | 25%, 75% { 63 | opacity: 0; 64 | } 65 | } 66 | 67 | .flash { 68 | animation-name: flash; 69 | } 70 | 71 | /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ 72 | 73 | @keyframes pulse { 74 | from { 75 | transform: scale3d(1, 1, 1); 76 | } 77 | 78 | 50% { 79 | transform: scale3d(1.05, 1.05, 1.05); 80 | } 81 | 82 | to { 83 | transform: scale3d(1, 1, 1); 84 | } 85 | } 86 | 87 | .pulse { 88 | animation-name: pulse; 89 | } 90 | 91 | @keyframes rubberBand { 92 | from { 93 | transform: scale3d(1, 1, 1); 94 | } 95 | 96 | 30% { 97 | transform: scale3d(1.25, 0.75, 1); 98 | } 99 | 100 | 40% { 101 | transform: scale3d(0.75, 1.25, 1); 102 | } 103 | 104 | 50% { 105 | transform: scale3d(1.15, 0.85, 1); 106 | } 107 | 108 | 65% { 109 | transform: scale3d(.95, 1.05, 1); 110 | } 111 | 112 | 75% { 113 | transform: scale3d(1.05, .95, 1); 114 | } 115 | 116 | to { 117 | transform: scale3d(1, 1, 1); 118 | } 119 | } 120 | 121 | .rubberBand { 122 | animation-name: rubberBand; 123 | } 124 | 125 | @keyframes shake { 126 | from, to { 127 | transform: translate3d(0, 0, 0); 128 | } 129 | 130 | 10%, 30%, 50%, 70%, 90% { 131 | transform: translate3d(-10px, 0, 0); 132 | } 133 | 134 | 20%, 40%, 60%, 80% { 135 | transform: translate3d(10px, 0, 0); 136 | } 137 | } 138 | 139 | .shake { 140 | animation-name: shake; 141 | } 142 | 143 | @keyframes headShake { 144 | 0% { 145 | transform: translateX(0); 146 | } 147 | 148 | 6.5% { 149 | transform: translateX(-6px) rotateY(-9deg); 150 | } 151 | 152 | 18.5% { 153 | transform: translateX(5px) rotateY(7deg); 154 | } 155 | 156 | 31.5% { 157 | transform: translateX(-3px) rotateY(-5deg); 158 | } 159 | 160 | 43.5% { 161 | transform: translateX(2px) rotateY(3deg); 162 | } 163 | 164 | 50% { 165 | transform: translateX(0); 166 | } 167 | } 168 | 169 | .headShake { 170 | animation-timing-function: ease-in-out; 171 | animation-name: headShake; 172 | } 173 | 174 | @keyframes swing { 175 | 20% { 176 | transform: rotate3d(0, 0, 1, 15deg); 177 | } 178 | 179 | 40% { 180 | transform: rotate3d(0, 0, 1, -10deg); 181 | } 182 | 183 | 60% { 184 | transform: rotate3d(0, 0, 1, 5deg); 185 | } 186 | 187 | 80% { 188 | transform: rotate3d(0, 0, 1, -5deg); 189 | } 190 | 191 | to { 192 | transform: rotate3d(0, 0, 1, 0deg); 193 | } 194 | } 195 | 196 | .swing { 197 | transform-origin: top center; 198 | animation-name: swing; 199 | } 200 | 201 | @keyframes tada { 202 | from { 203 | transform: scale3d(1, 1, 1); 204 | } 205 | 206 | 10%, 20% { 207 | transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); 208 | } 209 | 210 | 30%, 50%, 70%, 90% { 211 | transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); 212 | } 213 | 214 | 40%, 60%, 80% { 215 | transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); 216 | } 217 | 218 | to { 219 | transform: scale3d(1, 1, 1); 220 | } 221 | } 222 | 223 | .tada { 224 | animation-name: tada; 225 | } 226 | 227 | /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ 228 | 229 | @keyframes wobble { 230 | from { 231 | transform: none; 232 | } 233 | 234 | 15% { 235 | transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); 236 | } 237 | 238 | 30% { 239 | transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); 240 | } 241 | 242 | 45% { 243 | transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); 244 | } 245 | 246 | 60% { 247 | transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); 248 | } 249 | 250 | 75% { 251 | transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); 252 | } 253 | 254 | to { 255 | transform: none; 256 | } 257 | } 258 | 259 | .wobble { 260 | animation-name: wobble; 261 | } 262 | 263 | @keyframes jello { 264 | from, 11.1%, to { 265 | transform: none; 266 | } 267 | 268 | 22.2% { 269 | transform: skewX(-12.5deg) skewY(-12.5deg); 270 | } 271 | 272 | 33.3% { 273 | transform: skewX(6.25deg) skewY(6.25deg); 274 | } 275 | 276 | 44.4% { 277 | transform: skewX(-3.125deg) skewY(-3.125deg); 278 | } 279 | 280 | 55.5% { 281 | transform: skewX(1.5625deg) skewY(1.5625deg); 282 | } 283 | 284 | 66.6% { 285 | transform: skewX(-0.78125deg) skewY(-0.78125deg); 286 | } 287 | 288 | 77.7% { 289 | transform: skewX(0.390625deg) skewY(0.390625deg); 290 | } 291 | 292 | 88.8% { 293 | transform: skewX(-0.1953125deg) skewY(-0.1953125deg); 294 | } 295 | } 296 | 297 | .jello { 298 | animation-name: jello; 299 | transform-origin: center; 300 | } 301 | 302 | @keyframes bounceIn { 303 | from, 20%, 40%, 60%, 80%, to { 304 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 305 | } 306 | 307 | 0% { 308 | opacity: 0; 309 | transform: scale3d(.3, .3, .3); 310 | } 311 | 312 | 20% { 313 | transform: scale3d(1.1, 1.1, 1.1); 314 | } 315 | 316 | 40% { 317 | transform: scale3d(.9, .9, .9); 318 | } 319 | 320 | 60% { 321 | opacity: 1; 322 | transform: scale3d(1.03, 1.03, 1.03); 323 | } 324 | 325 | 80% { 326 | transform: scale3d(.97, .97, .97); 327 | } 328 | 329 | to { 330 | opacity: 1; 331 | transform: scale3d(1, 1, 1); 332 | } 333 | } 334 | 335 | .bounceIn { 336 | animation-name: bounceIn; 337 | } 338 | 339 | @keyframes bounceInDown { 340 | from, 60%, 75%, 90%, to { 341 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 342 | } 343 | 344 | 0% { 345 | opacity: 0; 346 | transform: translate3d(0, -3000px, 0); 347 | } 348 | 349 | 60% { 350 | opacity: 1; 351 | transform: translate3d(0, 25px, 0); 352 | } 353 | 354 | 75% { 355 | transform: translate3d(0, -10px, 0); 356 | } 357 | 358 | 90% { 359 | transform: translate3d(0, 5px, 0); 360 | } 361 | 362 | to { 363 | transform: none; 364 | } 365 | } 366 | 367 | .bounceInDown { 368 | animation-name: bounceInDown; 369 | } 370 | 371 | @keyframes bounceInLeft { 372 | from, 60%, 75%, 90%, to { 373 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 374 | } 375 | 376 | 0% { 377 | opacity: 0; 378 | transform: translate3d(-3000px, 0, 0); 379 | } 380 | 381 | 60% { 382 | opacity: 1; 383 | transform: translate3d(25px, 0, 0); 384 | } 385 | 386 | 75% { 387 | transform: translate3d(-10px, 0, 0); 388 | } 389 | 390 | 90% { 391 | transform: translate3d(5px, 0, 0); 392 | } 393 | 394 | to { 395 | transform: none; 396 | } 397 | } 398 | 399 | .bounceInLeft { 400 | animation-name: bounceInLeft; 401 | } 402 | 403 | @keyframes bounceInRight { 404 | from, 60%, 75%, 90%, to { 405 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 406 | } 407 | 408 | from { 409 | opacity: 0; 410 | transform: translate3d(3000px, 0, 0); 411 | } 412 | 413 | 60% { 414 | opacity: 1; 415 | transform: translate3d(-25px, 0, 0); 416 | } 417 | 418 | 75% { 419 | transform: translate3d(10px, 0, 0); 420 | } 421 | 422 | 90% { 423 | transform: translate3d(-5px, 0, 0); 424 | } 425 | 426 | to { 427 | transform: none; 428 | } 429 | } 430 | 431 | .bounceInRight { 432 | animation-name: bounceInRight; 433 | } 434 | 435 | @keyframes bounceInUp { 436 | from, 60%, 75%, 90%, to { 437 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 438 | } 439 | 440 | from { 441 | opacity: 0; 442 | transform: translate3d(0, 3000px, 0); 443 | } 444 | 445 | 60% { 446 | opacity: 1; 447 | transform: translate3d(0, -20px, 0); 448 | } 449 | 450 | 75% { 451 | transform: translate3d(0, 10px, 0); 452 | } 453 | 454 | 90% { 455 | transform: translate3d(0, -5px, 0); 456 | } 457 | 458 | to { 459 | transform: translate3d(0, 0, 0); 460 | } 461 | } 462 | 463 | .bounceInUp { 464 | animation-name: bounceInUp; 465 | } 466 | 467 | @keyframes bounceOut { 468 | 20% { 469 | transform: scale3d(.9, .9, .9); 470 | } 471 | 472 | 50%, 55% { 473 | opacity: 1; 474 | transform: scale3d(1.1, 1.1, 1.1); 475 | } 476 | 477 | to { 478 | opacity: 0; 479 | transform: scale3d(.3, .3, .3); 480 | } 481 | } 482 | 483 | .bounceOut { 484 | animation-name: bounceOut; 485 | } 486 | 487 | @keyframes bounceOutDown { 488 | 20% { 489 | transform: translate3d(0, 10px, 0); 490 | } 491 | 492 | 40%, 45% { 493 | opacity: 1; 494 | transform: translate3d(0, -20px, 0); 495 | } 496 | 497 | to { 498 | opacity: 0; 499 | transform: translate3d(0, 2000px, 0); 500 | } 501 | } 502 | 503 | .bounceOutDown { 504 | animation-name: bounceOutDown; 505 | } 506 | 507 | @keyframes bounceOutLeft { 508 | 20% { 509 | opacity: 1; 510 | transform: translate3d(20px, 0, 0); 511 | } 512 | 513 | to { 514 | opacity: 0; 515 | transform: translate3d(-2000px, 0, 0); 516 | } 517 | } 518 | 519 | .bounceOutLeft { 520 | animation-name: bounceOutLeft; 521 | } 522 | 523 | @keyframes bounceOutRight { 524 | 20% { 525 | opacity: 1; 526 | transform: translate3d(-20px, 0, 0); 527 | } 528 | 529 | to { 530 | opacity: 0; 531 | transform: translate3d(2000px, 0, 0); 532 | } 533 | } 534 | 535 | .bounceOutRight { 536 | animation-name: bounceOutRight; 537 | } 538 | 539 | @keyframes bounceOutUp { 540 | 20% { 541 | transform: translate3d(0, -10px, 0); 542 | } 543 | 544 | 40%, 45% { 545 | opacity: 1; 546 | transform: translate3d(0, 20px, 0); 547 | } 548 | 549 | to { 550 | opacity: 0; 551 | transform: translate3d(0, -2000px, 0); 552 | } 553 | } 554 | 555 | .bounceOutUp { 556 | animation-name: bounceOutUp; 557 | } 558 | 559 | @keyframes fadeIn { 560 | from { 561 | opacity: 0; 562 | } 563 | 564 | to { 565 | opacity: 1; 566 | } 567 | } 568 | 569 | .fadeIn { 570 | animation-name: fadeIn; 571 | } 572 | 573 | @keyframes fadeInDown { 574 | from { 575 | opacity: 0; 576 | transform: translate3d(0, -100%, 0); 577 | } 578 | 579 | to { 580 | opacity: 1; 581 | transform: none; 582 | } 583 | } 584 | 585 | .fadeInDown { 586 | animation-name: fadeInDown; 587 | } 588 | 589 | @keyframes fadeInDownBig { 590 | from { 591 | opacity: 0; 592 | transform: translate3d(0, -2000px, 0); 593 | } 594 | 595 | to { 596 | opacity: 1; 597 | transform: none; 598 | } 599 | } 600 | 601 | .fadeInDownBig { 602 | animation-name: fadeInDownBig; 603 | } 604 | 605 | @keyframes fadeInLeft { 606 | from { 607 | opacity: 0; 608 | transform: translate3d(-100%, 0, 0); 609 | } 610 | 611 | to { 612 | opacity: 1; 613 | transform: none; 614 | } 615 | } 616 | 617 | .fadeInLeft { 618 | animation-name: fadeInLeft; 619 | } 620 | 621 | @keyframes fadeInLeftBig { 622 | from { 623 | opacity: 0; 624 | transform: translate3d(-2000px, 0, 0); 625 | } 626 | 627 | to { 628 | opacity: 1; 629 | transform: none; 630 | } 631 | } 632 | 633 | .fadeInLeftBig { 634 | animation-name: fadeInLeftBig; 635 | } 636 | 637 | @keyframes fadeInRight { 638 | from { 639 | opacity: 0; 640 | transform: translate3d(100%, 0, 0); 641 | } 642 | 643 | to { 644 | opacity: 1; 645 | transform: none; 646 | } 647 | } 648 | 649 | .fadeInRight { 650 | animation-name: fadeInRight; 651 | } 652 | 653 | @keyframes fadeInRightBig { 654 | from { 655 | opacity: 0; 656 | transform: translate3d(2000px, 0, 0); 657 | } 658 | 659 | to { 660 | opacity: 1; 661 | transform: none; 662 | } 663 | } 664 | 665 | .fadeInRightBig { 666 | animation-name: fadeInRightBig; 667 | } 668 | 669 | @keyframes fadeInUp { 670 | from { 671 | opacity: 0; 672 | transform: translate3d(0, 100%, 0); 673 | } 674 | 675 | to { 676 | opacity: 1; 677 | transform: none; 678 | } 679 | } 680 | 681 | .fadeInUp { 682 | animation-name: fadeInUp; 683 | } 684 | 685 | @keyframes fadeInUpBig { 686 | from { 687 | opacity: 0; 688 | transform: translate3d(0, 2000px, 0); 689 | } 690 | 691 | to { 692 | opacity: 1; 693 | transform: none; 694 | } 695 | } 696 | 697 | .fadeInUpBig { 698 | animation-name: fadeInUpBig; 699 | } 700 | 701 | @keyframes fadeOut { 702 | from { 703 | opacity: 1; 704 | } 705 | 706 | to { 707 | opacity: 0; 708 | } 709 | } 710 | 711 | .fadeOut { 712 | animation-name: fadeOut; 713 | } 714 | 715 | @keyframes fadeOutDown { 716 | from { 717 | opacity: 1; 718 | } 719 | 720 | to { 721 | opacity: 0; 722 | transform: translate3d(0, 100%, 0); 723 | } 724 | } 725 | 726 | .fadeOutDown { 727 | animation-name: fadeOutDown; 728 | } 729 | 730 | @keyframes fadeOutDownBig { 731 | from { 732 | opacity: 1; 733 | } 734 | 735 | to { 736 | opacity: 0; 737 | transform: translate3d(0, 2000px, 0); 738 | } 739 | } 740 | 741 | .fadeOutDownBig { 742 | animation-name: fadeOutDownBig; 743 | } 744 | 745 | @keyframes fadeOutLeft { 746 | from { 747 | opacity: 1; 748 | } 749 | 750 | to { 751 | opacity: 0; 752 | transform: translate3d(-100%, 0, 0); 753 | } 754 | } 755 | 756 | .fadeOutLeft { 757 | animation-name: fadeOutLeft; 758 | } 759 | 760 | @keyframes fadeOutLeftBig { 761 | from { 762 | opacity: 1; 763 | } 764 | 765 | to { 766 | opacity: 0; 767 | transform: translate3d(-2000px, 0, 0); 768 | } 769 | } 770 | 771 | .fadeOutLeftBig { 772 | animation-name: fadeOutLeftBig; 773 | } 774 | 775 | @keyframes fadeOutRight { 776 | from { 777 | opacity: 1; 778 | } 779 | 780 | to { 781 | opacity: 0; 782 | transform: translate3d(100%, 0, 0); 783 | } 784 | } 785 | 786 | .fadeOutRight { 787 | animation-name: fadeOutRight; 788 | } 789 | 790 | @keyframes fadeOutRightBig { 791 | from { 792 | opacity: 1; 793 | } 794 | 795 | to { 796 | opacity: 0; 797 | transform: translate3d(2000px, 0, 0); 798 | } 799 | } 800 | 801 | .fadeOutRightBig { 802 | animation-name: fadeOutRightBig; 803 | } 804 | 805 | @keyframes fadeOutUp { 806 | from { 807 | opacity: 1; 808 | } 809 | 810 | to { 811 | opacity: 0; 812 | transform: translate3d(0, -100%, 0); 813 | } 814 | } 815 | 816 | .fadeOutUp { 817 | animation-name: fadeOutUp; 818 | } 819 | 820 | @keyframes fadeOutUpBig { 821 | from { 822 | opacity: 1; 823 | } 824 | 825 | to { 826 | opacity: 0; 827 | transform: translate3d(0, -2000px, 0); 828 | } 829 | } 830 | 831 | .fadeOutUpBig { 832 | animation-name: fadeOutUpBig; 833 | } 834 | 835 | @keyframes flip { 836 | from { 837 | transform: perspective(400px) rotate3d(0, 1, 0, -360deg); 838 | animation-timing-function: ease-out; 839 | } 840 | 841 | 40% { 842 | transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); 843 | animation-timing-function: ease-out; 844 | } 845 | 846 | 50% { 847 | transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); 848 | animation-timing-function: ease-in; 849 | } 850 | 851 | 80% { 852 | transform: perspective(400px) scale3d(.95, .95, .95); 853 | animation-timing-function: ease-in; 854 | } 855 | 856 | to { 857 | transform: perspective(400px); 858 | animation-timing-function: ease-in; 859 | } 860 | } 861 | 862 | .animated.flip { 863 | -webkit-backface-visibility: visible; 864 | backface-visibility: visible; 865 | animation-name: flip; 866 | } 867 | 868 | @keyframes flipInX { 869 | from { 870 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 871 | animation-timing-function: ease-in; 872 | opacity: 0; 873 | } 874 | 875 | 40% { 876 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 877 | animation-timing-function: ease-in; 878 | } 879 | 880 | 60% { 881 | transform: perspective(400px) rotate3d(1, 0, 0, 10deg); 882 | opacity: 1; 883 | } 884 | 885 | 80% { 886 | transform: perspective(400px) rotate3d(1, 0, 0, -5deg); 887 | } 888 | 889 | to { 890 | transform: perspective(400px); 891 | } 892 | } 893 | 894 | .flipInX { 895 | -webkit-backface-visibility: visible !important; 896 | backface-visibility: visible !important; 897 | animation-name: flipInX; 898 | } 899 | 900 | @keyframes flipInY { 901 | from { 902 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 903 | animation-timing-function: ease-in; 904 | opacity: 0; 905 | } 906 | 907 | 40% { 908 | transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 909 | animation-timing-function: ease-in; 910 | } 911 | 912 | 60% { 913 | transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 914 | opacity: 1; 915 | } 916 | 917 | 80% { 918 | transform: perspective(400px) rotate3d(0, 1, 0, -5deg); 919 | } 920 | 921 | to { 922 | transform: perspective(400px); 923 | } 924 | } 925 | 926 | .flipInY { 927 | -webkit-backface-visibility: visible !important; 928 | backface-visibility: visible !important; 929 | animation-name: flipInY; 930 | } 931 | 932 | @keyframes flipOutX { 933 | from { 934 | transform: perspective(400px); 935 | } 936 | 937 | 30% { 938 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 939 | opacity: 1; 940 | } 941 | 942 | to { 943 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 944 | opacity: 0; 945 | } 946 | } 947 | 948 | .flipOutX { 949 | animation-name: flipOutX; 950 | -webkit-backface-visibility: visible !important; 951 | backface-visibility: visible !important; 952 | } 953 | 954 | @keyframes flipOutY { 955 | from { 956 | transform: perspective(400px); 957 | } 958 | 959 | 30% { 960 | transform: perspective(400px) rotate3d(0, 1, 0, -15deg); 961 | opacity: 1; 962 | } 963 | 964 | to { 965 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 966 | opacity: 0; 967 | } 968 | } 969 | 970 | .flipOutY { 971 | -webkit-backface-visibility: visible !important; 972 | backface-visibility: visible !important; 973 | animation-name: flipOutY; 974 | } 975 | 976 | @keyframes lightSpeedIn { 977 | from { 978 | transform: translate3d(100%, 0, 0) skewX(-30deg); 979 | opacity: 0; 980 | } 981 | 982 | 60% { 983 | transform: skewX(20deg); 984 | opacity: 1; 985 | } 986 | 987 | 80% { 988 | transform: skewX(-5deg); 989 | opacity: 1; 990 | } 991 | 992 | to { 993 | transform: none; 994 | opacity: 1; 995 | } 996 | } 997 | 998 | .lightSpeedIn { 999 | animation-name: lightSpeedIn; 1000 | animation-timing-function: ease-out; 1001 | } 1002 | 1003 | @keyframes lightSpeedOut { 1004 | from { 1005 | opacity: 1; 1006 | } 1007 | 1008 | to { 1009 | transform: translate3d(100%, 0, 0) skewX(30deg); 1010 | opacity: 0; 1011 | } 1012 | } 1013 | 1014 | .lightSpeedOut { 1015 | animation-name: lightSpeedOut; 1016 | animation-timing-function: ease-in; 1017 | } 1018 | 1019 | @keyframes rotateIn { 1020 | from { 1021 | transform-origin: center; 1022 | transform: rotate3d(0, 0, 1, -200deg); 1023 | opacity: 0; 1024 | } 1025 | 1026 | to { 1027 | transform-origin: center; 1028 | transform: none; 1029 | opacity: 1; 1030 | } 1031 | } 1032 | 1033 | .rotateIn { 1034 | animation-name: rotateIn; 1035 | } 1036 | 1037 | @keyframes rotateInDownLeft { 1038 | from { 1039 | transform-origin: left bottom; 1040 | transform: rotate3d(0, 0, 1, -45deg); 1041 | opacity: 0; 1042 | } 1043 | 1044 | to { 1045 | transform-origin: left bottom; 1046 | transform: none; 1047 | opacity: 1; 1048 | } 1049 | } 1050 | 1051 | .rotateInDownLeft { 1052 | animation-name: rotateInDownLeft; 1053 | } 1054 | 1055 | @keyframes rotateInDownRight { 1056 | from { 1057 | transform-origin: right bottom; 1058 | transform: rotate3d(0, 0, 1, 45deg); 1059 | opacity: 0; 1060 | } 1061 | 1062 | to { 1063 | transform-origin: right bottom; 1064 | transform: none; 1065 | opacity: 1; 1066 | } 1067 | } 1068 | 1069 | .rotateInDownRight { 1070 | animation-name: rotateInDownRight; 1071 | } 1072 | 1073 | @keyframes rotateInUpLeft { 1074 | from { 1075 | transform-origin: left bottom; 1076 | transform: rotate3d(0, 0, 1, 45deg); 1077 | opacity: 0; 1078 | } 1079 | 1080 | to { 1081 | transform-origin: left bottom; 1082 | transform: none; 1083 | opacity: 1; 1084 | } 1085 | } 1086 | 1087 | .rotateInUpLeft { 1088 | animation-name: rotateInUpLeft; 1089 | } 1090 | 1091 | @keyframes rotateInUpRight { 1092 | from { 1093 | transform-origin: right bottom; 1094 | transform: rotate3d(0, 0, 1, -90deg); 1095 | opacity: 0; 1096 | } 1097 | 1098 | to { 1099 | transform-origin: right bottom; 1100 | transform: none; 1101 | opacity: 1; 1102 | } 1103 | } 1104 | 1105 | .rotateInUpRight { 1106 | animation-name: rotateInUpRight; 1107 | } 1108 | 1109 | @keyframes rotateOut { 1110 | from { 1111 | transform-origin: center; 1112 | opacity: 1; 1113 | } 1114 | 1115 | to { 1116 | transform-origin: center; 1117 | transform: rotate3d(0, 0, 1, 200deg); 1118 | opacity: 0; 1119 | } 1120 | } 1121 | 1122 | .rotateOut { 1123 | animation-name: rotateOut; 1124 | } 1125 | 1126 | @keyframes rotateOutDownLeft { 1127 | from { 1128 | transform-origin: left bottom; 1129 | opacity: 1; 1130 | } 1131 | 1132 | to { 1133 | transform-origin: left bottom; 1134 | transform: rotate3d(0, 0, 1, 45deg); 1135 | opacity: 0; 1136 | } 1137 | } 1138 | 1139 | .rotateOutDownLeft { 1140 | animation-name: rotateOutDownLeft; 1141 | } 1142 | 1143 | @keyframes rotateOutDownRight { 1144 | from { 1145 | transform-origin: right bottom; 1146 | opacity: 1; 1147 | } 1148 | 1149 | to { 1150 | transform-origin: right bottom; 1151 | transform: rotate3d(0, 0, 1, -45deg); 1152 | opacity: 0; 1153 | } 1154 | } 1155 | 1156 | .rotateOutDownRight { 1157 | animation-name: rotateOutDownRight; 1158 | } 1159 | 1160 | @keyframes rotateOutUpLeft { 1161 | from { 1162 | transform-origin: left bottom; 1163 | opacity: 1; 1164 | } 1165 | 1166 | to { 1167 | transform-origin: left bottom; 1168 | transform: rotate3d(0, 0, 1, -45deg); 1169 | opacity: 0; 1170 | } 1171 | } 1172 | 1173 | .rotateOutUpLeft { 1174 | animation-name: rotateOutUpLeft; 1175 | } 1176 | 1177 | @keyframes rotateOutUpRight { 1178 | from { 1179 | transform-origin: right bottom; 1180 | opacity: 1; 1181 | } 1182 | 1183 | to { 1184 | transform-origin: right bottom; 1185 | transform: rotate3d(0, 0, 1, 90deg); 1186 | opacity: 0; 1187 | } 1188 | } 1189 | 1190 | .rotateOutUpRight { 1191 | animation-name: rotateOutUpRight; 1192 | } 1193 | 1194 | @keyframes hinge { 1195 | 0% { 1196 | transform-origin: top left; 1197 | animation-timing-function: ease-in-out; 1198 | } 1199 | 1200 | 20%, 60% { 1201 | transform: rotate3d(0, 0, 1, 80deg); 1202 | transform-origin: top left; 1203 | animation-timing-function: ease-in-out; 1204 | } 1205 | 1206 | 40%, 80% { 1207 | transform: rotate3d(0, 0, 1, 60deg); 1208 | transform-origin: top left; 1209 | animation-timing-function: ease-in-out; 1210 | opacity: 1; 1211 | } 1212 | 1213 | to { 1214 | transform: translate3d(0, 700px, 0); 1215 | opacity: 0; 1216 | } 1217 | } 1218 | 1219 | .hinge { 1220 | animation-name: hinge; 1221 | } 1222 | 1223 | @keyframes jackInTheBox { 1224 | from { 1225 | opacity: 0; 1226 | transform: scale(0.1) rotate(30deg); 1227 | transform-origin: center bottom; 1228 | } 1229 | 1230 | 50% { 1231 | transform: rotate(-10deg); 1232 | } 1233 | 1234 | 70% { 1235 | transform: rotate(3deg); 1236 | } 1237 | 1238 | to { 1239 | opacity: 1; 1240 | transform: scale(1); 1241 | } 1242 | } 1243 | 1244 | .jackInTheBox { 1245 | animation-name: jackInTheBox; 1246 | } 1247 | 1248 | /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ 1249 | 1250 | @keyframes rollIn { 1251 | from { 1252 | opacity: 0; 1253 | transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); 1254 | } 1255 | 1256 | to { 1257 | opacity: 1; 1258 | transform: none; 1259 | } 1260 | } 1261 | 1262 | .rollIn { 1263 | animation-name: rollIn; 1264 | } 1265 | 1266 | /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ 1267 | 1268 | @keyframes rollOut { 1269 | from { 1270 | opacity: 1; 1271 | } 1272 | 1273 | to { 1274 | opacity: 0; 1275 | transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); 1276 | } 1277 | } 1278 | 1279 | .rollOut { 1280 | animation-name: rollOut; 1281 | } 1282 | 1283 | @keyframes zoomIn { 1284 | from { 1285 | opacity: 0; 1286 | transform: scale3d(.3, .3, .3); 1287 | } 1288 | 1289 | 50% { 1290 | opacity: 1; 1291 | } 1292 | } 1293 | 1294 | .zoomIn { 1295 | animation-name: zoomIn; 1296 | } 1297 | 1298 | @keyframes zoomInDown { 1299 | from { 1300 | opacity: 0; 1301 | transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); 1302 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1303 | } 1304 | 1305 | 60% { 1306 | opacity: 1; 1307 | transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); 1308 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1309 | } 1310 | } 1311 | 1312 | .zoomInDown { 1313 | animation-name: zoomInDown; 1314 | } 1315 | 1316 | @keyframes zoomInLeft { 1317 | from { 1318 | opacity: 0; 1319 | transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); 1320 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1321 | } 1322 | 1323 | 60% { 1324 | opacity: 1; 1325 | transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); 1326 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1327 | } 1328 | } 1329 | 1330 | .zoomInLeft { 1331 | animation-name: zoomInLeft; 1332 | } 1333 | 1334 | @keyframes zoomInRight { 1335 | from { 1336 | opacity: 0; 1337 | transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); 1338 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1339 | } 1340 | 1341 | 60% { 1342 | opacity: 1; 1343 | transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); 1344 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1345 | } 1346 | } 1347 | 1348 | .zoomInRight { 1349 | animation-name: zoomInRight; 1350 | } 1351 | 1352 | @keyframes zoomInUp { 1353 | from { 1354 | opacity: 0; 1355 | transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); 1356 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1357 | } 1358 | 1359 | 60% { 1360 | opacity: 1; 1361 | transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); 1362 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1363 | } 1364 | } 1365 | 1366 | .zoomInUp { 1367 | animation-name: zoomInUp; 1368 | } 1369 | 1370 | @keyframes zoomOut { 1371 | from { 1372 | opacity: 1; 1373 | } 1374 | 1375 | 50% { 1376 | opacity: 0; 1377 | transform: scale3d(.3, .3, .3); 1378 | } 1379 | 1380 | to { 1381 | opacity: 0; 1382 | } 1383 | } 1384 | 1385 | .zoomOut { 1386 | animation-name: zoomOut; 1387 | } 1388 | 1389 | @keyframes zoomOutDown { 1390 | 40% { 1391 | opacity: 1; 1392 | transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); 1393 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1394 | } 1395 | 1396 | to { 1397 | opacity: 0; 1398 | transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); 1399 | transform-origin: center bottom; 1400 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1401 | } 1402 | } 1403 | 1404 | .zoomOutDown { 1405 | animation-name: zoomOutDown; 1406 | } 1407 | 1408 | @keyframes zoomOutLeft { 1409 | 40% { 1410 | opacity: 1; 1411 | transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); 1412 | } 1413 | 1414 | to { 1415 | opacity: 0; 1416 | transform: scale(.1) translate3d(-2000px, 0, 0); 1417 | transform-origin: left center; 1418 | } 1419 | } 1420 | 1421 | .zoomOutLeft { 1422 | animation-name: zoomOutLeft; 1423 | } 1424 | 1425 | @keyframes zoomOutRight { 1426 | 40% { 1427 | opacity: 1; 1428 | transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); 1429 | } 1430 | 1431 | to { 1432 | opacity: 0; 1433 | transform: scale(.1) translate3d(2000px, 0, 0); 1434 | transform-origin: right center; 1435 | } 1436 | } 1437 | 1438 | .zoomOutRight { 1439 | animation-name: zoomOutRight; 1440 | } 1441 | 1442 | @keyframes zoomOutUp { 1443 | 40% { 1444 | opacity: 1; 1445 | transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); 1446 | animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); 1447 | } 1448 | 1449 | to { 1450 | opacity: 0; 1451 | transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); 1452 | transform-origin: center bottom; 1453 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); 1454 | } 1455 | } 1456 | 1457 | .zoomOutUp { 1458 | animation-name: zoomOutUp; 1459 | } 1460 | 1461 | @keyframes slideInDown { 1462 | from { 1463 | transform: translate3d(0, -100%, 0); 1464 | visibility: visible; 1465 | } 1466 | 1467 | to { 1468 | transform: translate3d(0, 0, 0); 1469 | } 1470 | } 1471 | 1472 | .slideInDown { 1473 | animation-name: slideInDown; 1474 | } 1475 | 1476 | @keyframes slideInLeft { 1477 | from { 1478 | transform: translate3d(-100%, 0, 0); 1479 | visibility: visible; 1480 | } 1481 | 1482 | to { 1483 | transform: translate3d(0, 0, 0); 1484 | } 1485 | } 1486 | 1487 | .slideInLeft { 1488 | animation-name: slideInLeft; 1489 | } 1490 | 1491 | @keyframes slideInRight { 1492 | from { 1493 | transform: translate3d(100%, 0, 0); 1494 | visibility: visible; 1495 | } 1496 | 1497 | to { 1498 | transform: translate3d(0, 0, 0); 1499 | } 1500 | } 1501 | 1502 | .slideInRight { 1503 | animation-name: slideInRight; 1504 | } 1505 | 1506 | @keyframes slideInUp { 1507 | from { 1508 | transform: translate3d(0, 100%, 0); 1509 | visibility: visible; 1510 | } 1511 | 1512 | to { 1513 | transform: translate3d(0, 0, 0); 1514 | } 1515 | } 1516 | 1517 | .slideInUp { 1518 | animation-name: slideInUp; 1519 | } 1520 | 1521 | @keyframes slideOutDown { 1522 | from { 1523 | transform: translate3d(0, 0, 0); 1524 | } 1525 | 1526 | to { 1527 | visibility: hidden; 1528 | transform: translate3d(0, 100%, 0); 1529 | } 1530 | } 1531 | 1532 | .slideOutDown { 1533 | animation-name: slideOutDown; 1534 | } 1535 | 1536 | @keyframes slideOutLeft { 1537 | from { 1538 | transform: translate3d(0, 0, 0); 1539 | } 1540 | 1541 | to { 1542 | visibility: hidden; 1543 | transform: translate3d(-100%, 0, 0); 1544 | } 1545 | } 1546 | 1547 | .slideOutLeft { 1548 | animation-name: slideOutLeft; 1549 | } 1550 | 1551 | @keyframes slideOutRight { 1552 | from { 1553 | transform: translate3d(0, 0, 0); 1554 | } 1555 | 1556 | to { 1557 | visibility: hidden; 1558 | transform: translate3d(100%, 0, 0); 1559 | } 1560 | } 1561 | 1562 | .slideOutRight { 1563 | animation-name: slideOutRight; 1564 | } 1565 | 1566 | @keyframes slideOutUp { 1567 | from { 1568 | transform: translate3d(0, 0, 0); 1569 | } 1570 | 1571 | to { 1572 | visibility: hidden; 1573 | transform: translate3d(0, -100%, 0); 1574 | } 1575 | } 1576 | 1577 | .slideOutUp { 1578 | animation-name: slideOutUp; 1579 | } 1580 | -------------------------------------------------------------------------------- /src/renderer/assets/css/custom.css: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Roboto'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Roboto'), local('Roboto-Regular'), url('./fonts/Roboto-Regular.woff2') format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 8 | } 9 | 10 | body { 11 | background-color: #fff; 12 | user-select: none; 13 | } 14 | 15 | html { 16 | overflow-y: auto; 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/Roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/css/fonts/Roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /src/renderer/assets/css/fonts/icon.css: -------------------------------------------------------------------------------- 1 | /* fallback */ 2 | @font-face { 3 | font-family: 'Material Icons'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url('MaterialIcons-Regular.woff2') format('woff2'); 7 | } 8 | 9 | .material-icons { 10 | font-family: 'Material Icons'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: 24px; 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/assets/img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/img/user.png -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | -------------------------------------------------------------------------------- /src/renderer/components/Blih.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 89 | 90 | 92 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/Page.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 64 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/Repositories.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 201 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/Repository.vue: -------------------------------------------------------------------------------- 1 | 217 | 218 | 408 | 409 | 411 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/Settings.vue: -------------------------------------------------------------------------------- 1 | 235 | 236 | 406 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/SshKey.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 180 | -------------------------------------------------------------------------------- /src/renderer/components/Blih/SshKeys.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 152 | -------------------------------------------------------------------------------- /src/renderer/components/Charts/ContributionsChart.js: -------------------------------------------------------------------------------- 1 | import { Pie, mixins } from 'vue-chartjs'; 2 | const { reactiveProp } = mixins; 3 | 4 | export default { 5 | extends: Pie, 6 | mixins: [reactiveProp], 7 | mounted () { 8 | this.renderChart(this.chartData); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/renderer/components/Charts/HistoryChart.js: -------------------------------------------------------------------------------- 1 | import { Line, mixins } from 'vue-chartjs'; 2 | const { reactiveProp } = mixins; 3 | 4 | export default { 5 | extends: Line, 6 | mixins: [reactiveProp], 7 | mounted () { 8 | this.renderChart(this.chartData, { 9 | scales: { 10 | xAxes: [{ 11 | display: true 12 | }], 13 | yAxes: [{ 14 | display: true 15 | }] 16 | } 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/renderer/components/Charts/LanguageChart.js: -------------------------------------------------------------------------------- 1 | import { Pie, mixins } from 'vue-chartjs'; 2 | const { reactiveProp } = mixins; 3 | 4 | export default { 5 | extends: Pie, 6 | mixins: [reactiveProp], 7 | mounted () { 8 | this.renderChart(this.chartData); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/renderer/components/Dialogs/DialogBasic.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 60 | -------------------------------------------------------------------------------- /src/renderer/components/Dialogs/DialogFile.vue: -------------------------------------------------------------------------------- 1 | 27 | 57 | 77 | -------------------------------------------------------------------------------- /src/renderer/components/Dialogs/DialogForm.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 78 | -------------------------------------------------------------------------------- /src/renderer/components/Error.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 38 | 39 | 59 | -------------------------------------------------------------------------------- /src/renderer/components/FileTree.vue: -------------------------------------------------------------------------------- 1 | 12 | 99 | -------------------------------------------------------------------------------- /src/renderer/components/Git.vue: -------------------------------------------------------------------------------- 1 | 188 | 448 | -------------------------------------------------------------------------------- /src/renderer/components/Loader.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /src/renderer/components/Login.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 94 | -------------------------------------------------------------------------------- /src/renderer/components/TileAvatar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | -------------------------------------------------------------------------------- /src/renderer/components/Welcome.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 43 | -------------------------------------------------------------------------------- /src/renderer/helpers.js: -------------------------------------------------------------------------------- 1 | import Store from 'electron-store'; 2 | import map from 'language-map'; 3 | import colors from 'vuetify/es5/util/colors'; 4 | import moment from 'moment'; 5 | import store from './store'; 6 | import path from 'path'; 7 | import os from 'os'; 8 | 9 | const sshDir = path.join(os.homedir(), '.ssh'); 10 | 11 | export function ignoreCaseSort (a, b) { 12 | if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; 13 | else if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; 14 | else return 0; 15 | }; 16 | 17 | export function getConfig () { 18 | return new Store({ 19 | defaults: { 20 | lastEmail: '', 21 | theme: 'Default', 22 | dark: false, 23 | welcome: true, 24 | publicKeyPath: path.join(sshDir, 'id_rsa.pub'), 25 | privateKeyPath: path.join(sshDir, 'id_rsa') 26 | } 27 | }); 28 | } 29 | 30 | export function getData (defaults) { 31 | return new Store({ 32 | name: 'data', 33 | defaults 34 | }); 35 | } 36 | 37 | /** 38 | * Return the language corresponding to this file 39 | */ 40 | export function getFileLanguage (filename) { 41 | const ext = filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 1); 42 | for (const key in map) { 43 | const language = map[key]; 44 | 45 | if ((language.extensions && language.extensions.includes(ext)) || 46 | (language.filenames && language.filenames.includes(filename))) { 47 | return { 48 | name: key, 49 | ...language 50 | }; 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | /** 57 | * Get languages of a tree returned by RepositoryHub 58 | * Returns { lang... } 59 | */ 60 | export function getTreeLanguages (files) { 61 | let languages = {}; 62 | 63 | for (const file of files) { 64 | if (file.children) { 65 | let clanguages = getTreeLanguages(file.children); 66 | for (const lang in clanguages) { 67 | if (!languages.hasOwnProperty(lang)) { 68 | languages[lang] = 0; 69 | } 70 | languages[lang] += clanguages[lang]; 71 | } 72 | } else if (file.type !== 'blob') { 73 | continue; 74 | } 75 | 76 | let language = getFileLanguage(file.name); 77 | if (language === null) continue; 78 | 79 | if (!languages.hasOwnProperty(language.name)) { 80 | languages[language.name] = 0; 81 | } 82 | languages[language.name]++; 83 | } 84 | 85 | return languages; 86 | } 87 | 88 | /** 89 | * Get languages of a tree returned by RepositoryHub as a chart.js data object 90 | */ 91 | export function getTreeLanguagesChart (files) { 92 | let chart = {}; 93 | let languages = getTreeLanguages(files); 94 | 95 | chart.labels = Object.keys(languages); 96 | chart.datasets = [{ 97 | data: Object.values(languages), 98 | backgroundColor: Object.keys(languages).map(lang => map[lang].color || colors.grey.lighten1) 99 | }]; 100 | 101 | return chart; 102 | } 103 | 104 | /** 105 | * Get history of commits returned by RepositoryHub as a chart.js data object 106 | */ 107 | export function getHistoryChart (history) { 108 | let chart = {}; 109 | let commits = {}; 110 | 111 | for (const commit of history) { 112 | const author = commit.author().name(); 113 | const date = moment(commit.date(), 'YYYY-MM-DD hh:mm:ss ZZ'); 114 | 115 | if (!commits.hasOwnProperty(author)) { 116 | commits[author] = {}; 117 | } 118 | if (!commits[author].hasOwnProperty(date)) { 119 | commits[author][date] = 0; 120 | } 121 | commits[author][date]++; 122 | } 123 | 124 | // console.log(commits); 125 | chart.labels = []; 126 | chart.datasets = Object.keys(commits) 127 | .map(author => ({ 128 | label: author, 129 | data: Object.keys(commits[author]) 130 | .map(date => commits[author][date]), 131 | backgroundColor: store.getters.rawColorOf(author) 132 | })); 133 | 134 | return chart; 135 | } 136 | 137 | /** 138 | * Get contributions returned by RepositoryHub as a chart.js data object 139 | */ 140 | export function getContributionsChart (history) { 141 | let chart = {}; 142 | let commits = {}; 143 | 144 | for (const commit of history) { 145 | const author = commit.author().name(); 146 | 147 | if (!commits.hasOwnProperty(author)) { 148 | commits[author] = 0; 149 | } 150 | commits[author]++; 151 | } 152 | 153 | chart.labels = Object.keys(commits); 154 | chart.datasets = [{ 155 | data: Object.values(commits), 156 | backgroundColor: Object.keys(commits).map(author => store.getters.rawColorOf(author)) 157 | }]; 158 | 159 | return chart; 160 | } 161 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import App from './App'; 4 | import store from './store'; 5 | import router from './router'; 6 | 7 | import Vuetify from 'vuetify'; 8 | 9 | /* CSS */ 10 | import 'vuetify/dist/vuetify.min.css'; 11 | import './assets/css/fonts/icon.css'; 12 | import './assets/css/custom.css'; 13 | 14 | const theme = store.getters.themes.find(t => t.name === store.getters.theme); 15 | Vue.use(Vuetify, theme && { 16 | theme: theme.colors 17 | }); 18 | 19 | Vue.config.productionTip = false; 20 | 21 | /* Skip welcome page ? */ 22 | if (!store.getters.welcome) { 23 | router.push({ name: 'login' }); 24 | } 25 | 26 | /* eslint-disable no-new */ 27 | new Vue({ 28 | components: { App }, 29 | router, 30 | store, 31 | template: '' 32 | }).$mount('#app'); 33 | -------------------------------------------------------------------------------- /src/renderer/mixins.js: -------------------------------------------------------------------------------- 1 | export const snackbar = { 2 | data () { 3 | return { 4 | snackbar: { 5 | show: false, 6 | color: '', 7 | message: '' 8 | } 9 | }; 10 | }, 11 | methods: { 12 | showSnackbar (color, message) { 13 | this.snackbar = { 14 | show: true, 15 | color, 16 | message 17 | }; 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | /* eslint object-property-newline: 0 */ 2 | 3 | import Vue from 'vue'; 4 | import VueRouter from 'vue-router'; 5 | 6 | import Welcome from '../components/Welcome'; 7 | import Login from '../components/Login'; 8 | import Blih from '../components/Blih'; 9 | import Repositories from '../components/Blih/Repositories'; 10 | import Repository from '../components/Blih/Repository'; 11 | import SshKeys from '../components/Blih/SshKeys'; 12 | import SshKey from '../components/Blih/SshKey'; 13 | import Settings from '../components/Blih/Settings'; 14 | 15 | Vue.use(VueRouter); 16 | 17 | export default new VueRouter({ 18 | routes: [ 19 | { path: '/', name: 'welcome', component: Welcome }, 20 | { path: '/login', name: 'login', component: Login }, 21 | { path: '/blih', name: 'blih', component: Blih, children: [ 22 | { path: '/repositories', name: 'blih.repositories', component: Repositories }, 23 | { path: '/repository/:name', name: 'blih.repository', component: Repository }, 24 | { path: '/ssh-keys', name: 'blih.ssh-keys', component: SshKeys }, 25 | { path: '/ssh-key/:name', name: 'blih.ssh-key', component: SshKey }, 26 | { path: '/settings', name: 'blih.settings', component: Settings } 27 | ]}, 28 | { path: '*', redirect: '/' } 29 | ] 30 | }); 31 | -------------------------------------------------------------------------------- /src/renderer/store/actions.js: -------------------------------------------------------------------------------- 1 | import Blih from 'blih'; 2 | 3 | import { ignoreCaseSort } from '../helpers'; 4 | 5 | export default { 6 | authenticate (context, credentials) { 7 | try { 8 | let api = new Blih({ email: credentials.email, password: credentials.password }); 9 | return api.whoami() 10 | .then(data => { 11 | context.commit('AUTHENTICATE', { 12 | api, 13 | email: credentials.email, 14 | login: data 15 | }); 16 | }); 17 | } catch (err) { 18 | return Promise.reject(err); 19 | } 20 | }, 21 | async updateRepositories (context) { 22 | const repos = await context.getters.api.listRepositories(); 23 | context.commit('UPDATE_REPOSITORIES', { 24 | repositories: repos.sort(ignoreCaseSort) 25 | .map(r => Object.freeze({ 26 | ...r, 27 | module: context.getters.getModule(r.name) 28 | })) 29 | }); 30 | }, 31 | createRepository (context, name) { 32 | return context.getters.api.createRepository(name) 33 | .then(_ => { 34 | context.commit('ADD_REPOSITORY', { 35 | name, 36 | label: context.getters.getModule(name) 37 | }); 38 | }); 39 | }, 40 | deleteRepository (context, name) { 41 | return context.getters.api.deleteRepository(name) 42 | .then(_ => { 43 | context.commit('REMOVE_REPOSITORY', { 44 | name 45 | }); 46 | }); 47 | }, 48 | updateKeys (context) { 49 | return context.getters.api.listKeys() 50 | .then(data => { 51 | context.commit('UPDATE_KEYS', { keys: data }); 52 | }); 53 | }, 54 | uploadKey (context, key) { 55 | const oldKeys = context.getters.keys; 56 | return context.getters.api.uploadKey(key) 57 | .then(_ => context.dispatch('updateKeys')) 58 | // Return the new key 59 | .then(_ => context.getters.keys.filter(k => !oldKeys.find(ok => ok.name === k.name))[0]); 60 | }, 61 | deleteKey (context, key) { 62 | return context.getters.api.deleteKey(key) 63 | .then(_ => { 64 | context.commit('REMOVE_KEY', { 65 | name: key 66 | }); 67 | }); 68 | }, 69 | /* Config */ 70 | setLastEmail (context, email) { 71 | context.commit('SET_LAST_EMAIL', { email }); 72 | }, 73 | setTheme (context, theme) { 74 | context.commit('SET_THEME', { theme }); 75 | }, 76 | setDark (context, value) { 77 | context.commit('SET_DARK', { value }); 78 | }, 79 | setWelcome (context, value) { 80 | context.commit('SET_WELCOME', { value }); 81 | }, 82 | setPublicKeyPath (context, path) { 83 | context.commit('SET_PUBLIC_KEY_PATH', { path }); 84 | }, 85 | setPrivateKeyPath (context, path) { 86 | context.commit('SET_PRIVATE_KEY_PATH', { path }); 87 | }, 88 | /* Data */ 89 | addCollaborator (context, name) { 90 | if (!context.getters.collaborators.find(c => c.name === name)) { 91 | context.commit('ADD_COLLABORATOR', { 92 | name, 93 | picture: false, 94 | aliases: [] 95 | }); 96 | } 97 | }, 98 | updateCollaborator (context, data) { 99 | if (context.getters.collaborators.find(c => c.name === data.name)) { 100 | context.commit('UPDATE_COLLABORATOR', data); 101 | } 102 | }, 103 | updateModule (context, data) { 104 | if (context.getters.modules.find(c => c.name === data.name)) { 105 | context.commit('UPDATE_MODULE', data); 106 | } 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/renderer/store/defaults.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/es5/util/colors'; 2 | 3 | /* Default data */ 4 | 5 | export const collaborators = [ 6 | { 7 | name: 'ramassage-tek', 8 | picture: false, 9 | aliases: [] 10 | } 11 | ]; 12 | 13 | export const colorMap = { 14 | a: 'red', 15 | b: 'pink', 16 | c: 'purple', 17 | d: 'deep-purple', 18 | e: 'indigo', 19 | f: 'blue', 20 | g: 'light-blue', 21 | h: 'cyan', 22 | i: 'teal', 23 | j: 'green', 24 | k: 'light-green', 25 | l: 'lime', 26 | m: 'yellow darken-1', 27 | n: 'amber', 28 | o: 'orange', 29 | p: 'deep-orange', 30 | q: 'brown', 31 | r: 'blue-grey', 32 | s: 'grey', 33 | t: 'red lighten-2', 34 | u: 'pink lighten-2', 35 | v: 'purple lighten-2', 36 | w: 'deep-purple lighten-2', 37 | x: 'indigo lighten-2', 38 | y: 'blue lighten-2', 39 | z: 'light-blue lighten-2', 40 | '0': 'cyan lighten-2', 41 | '1': 'teal lighten-2', 42 | '2': 'green lighten-2', 43 | '3': 'light-green lighten-2', 44 | '4': 'lime lighten-2', 45 | '5': 'yellow', 46 | '6': 'amber lighten-2', 47 | '7': 'orange lighten-2', 48 | '8': 'deep-orange lighten-2', 49 | '9': 'brown lighten-2', 50 | other: 'black' 51 | }; 52 | 53 | export const rawColorMap = { 54 | a: colors.red.base, 55 | b: colors.pink.base, 56 | c: colors.purple.base, 57 | d: colors.deepPurple.base, 58 | e: colors.indigo.base, 59 | f: colors.blue.base, 60 | g: colors.lightBlue.base, 61 | h: colors.cyan.base, 62 | i: colors.teal.base, 63 | j: colors.green.base, 64 | k: colors.lightGreen.base, 65 | l: colors.lime.base, 66 | m: colors.yellow.darken1, 67 | n: colors.amber.base, 68 | o: colors.orange.base, 69 | p: colors.deepOrange.base, 70 | q: colors.brown.base, 71 | r: colors.blueGrey.base, 72 | s: colors.grey.base, 73 | t: colors.red.lighten2, 74 | u: colors.pink.lighten2, 75 | v: colors.purple.lighten2, 76 | w: colors.deepPurple.lighten2, 77 | x: colors.indigo.lighten2, 78 | y: colors.blue.lighten2, 79 | z: colors.lightBlue.lighten2, 80 | '0': colors.cyan.lighten2, 81 | '1': colors.teal.lighten2, 82 | '2': colors.green.lighten2, 83 | '3': colors.lightGreen.lighten2, 84 | '4': colors.lime.lighten2, 85 | '5': colors.yellow.lighten2, 86 | '6': colors.amber.lighten2, 87 | '7': colors.orange.lighten2, 88 | '8': colors.deepOrange.lighten2, 89 | '9': colors.brown.lighten2, 90 | other: colors.black 91 | }; 92 | 93 | export const modules = [ 94 | { 95 | name: 'Artificial Intelligence', 96 | icon: 'bubble_chart', 97 | color: 'green', 98 | regexp: [ 99 | '^dante$', '^gomoku$', '^need4steak$' 100 | ] 101 | }, { 102 | name: 'Assembly', 103 | icon: 'code', 104 | color: 'brown', 105 | regexp: [ 106 | '^asm_' 107 | ] 108 | }, { 109 | name: 'Mathematics', 110 | icon: 'functions', 111 | color: 'indigo', 112 | regexp: [ 113 | '101pong', '102cipher', '103architect', '104intersection', '105torus', '106bombyx', '107transfer', '108trigo', '109titration', '110borwein', 114 | '201yams', '202unsold', '203hotline', '204ducks', '205IQ', '206neutrinos', '207demography', '208dowels', '208pegs', '209poll', 115 | '301dannon', '302separation', '303make', '304pacman', '305construction', '306radiator', '307multigrains', '308reedpipes', '309pollution' 116 | ] 117 | }, { 118 | name: 'French', 119 | icon: 'edit', 120 | color: 'red', 121 | regexp: [ 122 | 'B1Lettre', 'B1Mailpro', 'B1Rush', 123 | 'B2EMPLOI', 'B2PRECISER', 'B2VENTE', 124 | 'B4BILAN', 'B4DIAPO', 'B4RECADRER', 125 | 'B53EMAILS', 'B5AVOCAT', 'B5MEMO', 'FICTION1', 126 | '^Disparition20\\d{2}' 127 | ] 128 | }, { 129 | name: 'CPE', 130 | icon: 'code', 131 | color: 'grey', 132 | regexp: [ 133 | '^CPE_', '^marvin_', '^sudoki-bi$' 134 | ] 135 | }, { 136 | name: 'PSU', 137 | icon: 'code', 138 | color: 'amber', 139 | regexp: [ 140 | '^PSU_' 141 | ] 142 | }, { 143 | name: 'C++', 144 | icon: 'code', 145 | color: 'lime', 146 | regexp: [ 147 | '^cpp_(nanotekspice|arcade|spider)$' 148 | ] 149 | }, { 150 | name: 'Computer graphics', 151 | icon: 'desktop_windows', 152 | color: 'deep-orange', 153 | regexp: [ 154 | '^(alphachannel|binarizer|chatty|chromatic|flip_part|flashlight|iconofsin|lightning|mystic|mystify|nocss|noise|starfield|stretcher|text_foot|whirlpool|xorshape)_20\\d{2}$', 155 | '^gfx_', '^tekadventure$', '^bsraytracer1$', '^raytracer1$', '^wireframe$', '^wolf3d$' 156 | ] 157 | }, { 158 | name: 'C Pool', 159 | icon: 'pool', 160 | color: 'light-blue', 161 | regexp: [ 162 | '^Piscine_C', '^CPool_' 163 | ] 164 | }, { 165 | name: 'C++ Pool', 166 | icon: 'pool', 167 | color: 'cyan', 168 | regexp: [ 169 | '^cpp_d[01]\\d[am]*', '^cpp_(gkrellm|santa|SKL)$' 170 | ] 171 | }, { 172 | name: '.NET', 173 | icon: 'code', 174 | color: 'teal', 175 | regexp: [ 176 | '^DOT_' 177 | ] 178 | }, { 179 | name: 'OCaml', 180 | icon: 'code', 181 | color: 'yellow', 182 | regexp: [ 183 | '^OCAML_' 184 | ] 185 | }, { 186 | name: 'Shell', 187 | icon: 'code', 188 | color: 'blue-grey', 189 | regexp: [ 190 | '^SHL_' 191 | ] 192 | }, { 193 | name: 'Java', 194 | icon: 'code', 195 | color: 'orange', 196 | regexp: [ 197 | '^Java_\\w+_20\\d{2}' 198 | ] 199 | }, { 200 | name: 'Internship', 201 | icon: 'work', 202 | color: 'pink', 203 | regexp: [ 204 | '^stageTEK', '^internship_20\\d{2}$' 205 | ] 206 | }, { 207 | name: 'Security', 208 | icon: 'lock', 209 | color: 'deep-orange', 210 | regexp: [ 211 | '^binary_seminar$' 212 | ] 213 | }, { 214 | name: 'Cryptography', 215 | icon: 'lock', 216 | color: 'deep-purple', 217 | regexp: [ 218 | '^pamela$' 219 | ] 220 | }, { 221 | name: 'Android', 222 | icon: 'android', 223 | color: 'light-green', 224 | regexp: [ 225 | 226 | ] 227 | }, { 228 | name: 'Synthesis pool', 229 | icon: 'pool', 230 | color: 'blue', 231 | regexp: [ 232 | '^ADM_', 'automakefile', 'calendar', 'FASTAtools', 'palindrome', 'projTester', 'SBMLparser' 233 | ] 234 | } 235 | ]; 236 | 237 | export const themes = [ 238 | { 239 | name: 'Default', 240 | colors: { 241 | 'primary': '#1976D2', 242 | 'secondary': '#424242', 243 | 'accent': '#82B1FF', 244 | 'error': '#FF5252', 245 | 'warning': '#FFC107', 246 | 'info': '#2196F3', 247 | 'success': '#4CAF50' 248 | } 249 | }, { 250 | name: 'Halloween', 251 | colors: { 252 | 'primary': '#FB8C00', 253 | 'secondary': '#000000', 254 | 'accent': '#FFD180', 255 | 'error': '#F44336', 256 | 'warning': '#FFC107', 257 | 'info': '#2196F3', 258 | 'success': '#4CAF50' 259 | } 260 | }, { 261 | name: 'Christmas', 262 | colors: { 263 | 'primary': '#F44336', 264 | 'secondary': '#2E7D32', 265 | 'accent': '#795548', 266 | 'error': '#F44336', 267 | 'warning': '#FFC107', 268 | 'info': '#2196F3', 269 | 'success': '#4CAF50' 270 | } 271 | } 272 | ]; 273 | -------------------------------------------------------------------------------- /src/renderer/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | api: state => state.api, 3 | email: state => state.email, 4 | login: state => state.login, 5 | repositories: state => state.repositories, 6 | keys: state => state.keys, 7 | /* Config */ 8 | lastEmail: state => state.lastEmail, 9 | theme: state => state.theme, 10 | dark: state => state.dark, 11 | welcome: state => state.welcome, 12 | publicKeyPath: state => state.publicKeyPath, 13 | privateKeyPath: state => state.privateKeyPath, 14 | /* Data */ 15 | collaborators: state => state.collaborators, 16 | colorMap: state => state.colorMap, 17 | rawColorMap: state => state.rawColorMap, 18 | colorOf: state => text => { 19 | const letter = (text && text.length) ? text[0].toLowerCase() : '?'; 20 | return state.colorMap[letter] || state.colorMap['other']; 21 | }, 22 | rawColorOf: state => text => { 23 | const letter = (text && text.length) ? text[0].toLowerCase() : '?'; 24 | return state.rawColorMap[letter] || state.rawColorMap['other']; 25 | }, 26 | modules: state => state.modules, 27 | getModule: state => repo => { 28 | for (const module of state.modules) { 29 | for (const regexp of module.regexp) { 30 | if (repo.match(regexp)) { 31 | return { 32 | name: module.name, 33 | icon: module.icon, 34 | color: module.color 35 | }; 36 | } 37 | } 38 | } 39 | return null; 40 | }, 41 | themes: state => state.themes 42 | }; 43 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import state from './state'; 5 | import mutations from './mutations'; 6 | import getters from './getters'; 7 | import actions from './actions'; 8 | 9 | Vue.use(Vuex); 10 | 11 | /* 12 | * Vuex 13 | */ 14 | 15 | export default new Vuex.Store({ 16 | state, 17 | mutations, 18 | getters, 19 | actions, 20 | strict: process.env.NODE_ENV !== 'production' 21 | }); 22 | -------------------------------------------------------------------------------- /src/renderer/store/mutations.js: -------------------------------------------------------------------------------- 1 | import { ignoreCaseSort, getConfig, getData } from '../helpers'; 2 | 3 | let [data, config] = [getData(), getConfig()]; 4 | 5 | export default { 6 | AUTHENTICATE (state, payload) { 7 | state.api = payload.api; 8 | state.email = payload.email; 9 | state.login = payload.login; 10 | }, 11 | UPDATE_REPOSITORIES (state, payload) { 12 | state.repositories = payload.repositories; 13 | }, 14 | ADD_REPOSITORY (state, payload) { 15 | state.repositories.push(payload); 16 | state.repositories = state.repositories.sort(ignoreCaseSort); 17 | }, 18 | REMOVE_REPOSITORY (state, payload) { 19 | state.repositories = state.repositories.filter(r => r.name !== payload.name); 20 | }, 21 | UPDATE_KEYS (state, payload) { 22 | state.keys = payload.keys; 23 | }, 24 | ADD_KEY (state, payload) { 25 | state.keys.push(payload); 26 | state.keys = state.keys.sort(ignoreCaseSort); 27 | }, 28 | REMOVE_KEY (state, payload) { 29 | state.keys = state.keys.filter(k => k.name !== payload.name); 30 | }, 31 | /* Config */ 32 | SET_LAST_EMAIL (state, payload) { 33 | state.lastEmail = payload.email; 34 | config.set('lastEmail', state.lastEmail); 35 | }, 36 | SET_THEME (state, payload) { 37 | state.theme = payload.theme; 38 | config.set('theme', state.theme); 39 | }, 40 | SET_DARK (state, payload) { 41 | state.dark = payload.value; 42 | config.set('dark', state.dark); 43 | }, 44 | SET_WELCOME (state, payload) { 45 | state.welcome = payload.value; 46 | config.set('welcome', state.welcome); 47 | }, 48 | SET_PUBLIC_KEY_PATH (state, payload) { 49 | state.publicKeyPath = payload.path; 50 | config.set('publicKeyPath', state.publicKeyPath); 51 | }, 52 | SET_PRIVATE_KEY_PATH (state, payload) { 53 | state.privateKeyPath = payload.path; 54 | config.set('privateKeyPath', state.privateKeyPath); 55 | }, 56 | /* Data */ 57 | ADD_COLLABORATOR (state, payload) { 58 | state.collaborators.push(payload); 59 | data.set('collaborators', state.collaborators.sort(ignoreCaseSort)); 60 | }, 61 | UPDATE_COLLABORATOR (state, payload) { 62 | let index = state.collaborators.findIndex(c => c.name === payload.name); 63 | if (index >= 0) { 64 | state.collaborators[index] = payload; 65 | data.set('collaborators', state.collaborators.sort(ignoreCaseSort)); 66 | } 67 | }, 68 | UPDATE_MODULE (state, payload) { 69 | let index = state.modules.findIndex(c => c.name === payload.name); 70 | if (index >= 0) { 71 | state.modules[index] = payload; 72 | data.set('modules', state.modules.sort(ignoreCaseSort)); 73 | } 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/renderer/store/state.js: -------------------------------------------------------------------------------- 1 | import * as defaults from './defaults'; 2 | import { ignoreCaseSort, getConfig, getData } from '../helpers'; 3 | 4 | let [data, config] = [getData(defaults), getConfig()]; 5 | 6 | // IDEA: one object for all config and data 7 | export default { 8 | api: null, 9 | email: null, 10 | login: null, 11 | repositories: [], 12 | keys: [], 13 | /* Config */ 14 | lastEmail: config.get('lastEmail'), 15 | theme: config.get('theme'), 16 | dark: config.get('dark'), 17 | welcome: config.get('welcome'), 18 | publicKeyPath: config.get('publicKeyPath'), 19 | privateKeyPath: config.get('privateKeyPath'), 20 | /* Data */ 21 | collaborators: data.get('collaborators').sort(ignoreCaseSort), 22 | colorMap: defaults.colorMap, // data.get('colorMap'), 23 | rawColorMap: defaults.rawColorMap, 24 | modules: defaults.modules.sort(ignoreCaseSort), // data.get('modules').sort(ignoreCaseSort), 25 | themes: defaults.themes.sort(ignoreCaseSort) // data.get('themes').sort(ignoreCaseSort) 26 | }; 27 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/static/.gitkeep -------------------------------------------------------------------------------- /static/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kabukki/blih-vue/65a30b26aa49ea56a5c37a57d11ff8b4111909d3/static/256.png -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | Upload component (vuetify front-end pack, Q2 2018) 2 | opti: page repositories keep-alive? 3 | diff dialog 4 | Bouton try again dans Git.vue on error 5 | 6 | --- problems --- 7 | production: 8 | 9 | --- ameliorations --- 10 | Dialogs: 11 | customisation des fields, decorations et leur position (avec form) 12 | repositoryHub global (avec vuex) 13 | --------------------------------------------------------------------------------