├── .babelrc
├── .electron-vue
├── build.js
├── dev-client.js
├── dev-runner.js
├── webpack.main.config.js
├── webpack.renderer.config.js
└── webpack.web.config.js
├── .gitignore
├── .jsbeautifyrc
├── .prettierrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── appveyor.yml
├── build
└── icons
│ ├── 256x256.png
│ ├── icon old.ico
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.psd
├── dist
├── electron
│ └── .gitkeep
└── web
│ └── .gitkeep
├── github_image
├── 001.png
├── 002.png
└── 003.png
├── package-lock.json
├── package.json
├── src
├── config.js
├── index.ejs
├── main
│ ├── bean
│ │ └── FileItem.ts
│ ├── index.dev.js
│ ├── index.js
│ └── service
│ │ ├── AlbumDirSorter.ts
│ │ ├── AlbumServiceImpl.ts
│ │ └── DirBrowser.ts
└── renderer
│ ├── App.vue
│ ├── CoreApp.vue
│ ├── Home.vue
│ ├── assets
│ └── value
│ │ ├── bookInstruction.js
│ │ ├── instruction.js
│ │ ├── string.js
│ │ ├── tags.js
│ │ └── version.js
│ ├── bean
│ ├── DialogBean.ts
│ ├── DialogOperation.ts
│ ├── ImgPageInfo.ts
│ ├── ServerMessage.ts
│ └── ThumbInfo.ts
│ ├── components
│ ├── AlbumBookView.vue
│ ├── AlbumScrollView.vue
│ ├── LoadingView.vue
│ ├── ModalManager.vue
│ ├── PageView.vue
│ ├── ReaderView.vue
│ ├── ThumbScrollView.vue
│ ├── TopBar.vue
│ ├── base
│ │ └── AwesomeScrollView.vue
│ └── widget
│ │ ├── CircleIconButton.vue
│ │ ├── DropOption.vue
│ │ ├── FlatButton.vue
│ │ ├── Pagination.vue
│ │ ├── PopSlider.vue
│ │ ├── Popover.vue
│ │ ├── SimpleDialog.vue
│ │ ├── SimpleSwitch.vue
│ │ └── Slider.vue
│ ├── main.js
│ ├── router
│ └── index.js
│ ├── service
│ ├── AlbumService.ts
│ ├── InfoService.ts
│ ├── PlatformService.js
│ ├── SettingService.ts
│ ├── StringService.ts
│ ├── request
│ │ ├── MultiAsyncReq.ts
│ │ ├── ReqQueue.ts
│ │ └── TextReq.ts
│ └── storage
│ │ ├── LocalStorage.ts
│ │ ├── SyncStorage.ts
│ │ └── base
│ │ └── Storage.js
│ ├── store
│ ├── index.js
│ ├── modules
│ │ ├── AlbumView.js
│ │ ├── Modal.js
│ │ ├── String.js
│ │ └── index.js
│ └── mutation-types.js
│ ├── style
│ ├── _markdown.scss
│ ├── _normalize.scss
│ ├── _responsive.scss
│ └── _variables.scss
│ └── utils
│ ├── DateUtil.js
│ ├── DateWrapper.js
│ ├── Logger.js
│ ├── MdRenderer.js
│ ├── Utils.ts
│ ├── VueUtil.js
│ ├── bezier-easing.js
│ ├── formatter.js
│ └── react-native-storage
│ ├── error.js
│ └── storage.js
├── static
└── .gitkeep
├── test
├── .eslintrc
└── unit
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
│ └── LandingPage.spec.js
├── tsconfig.json
├── update.json
├── yarn-error.log
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "comments": false,
3 | "env": {
4 | "test": {
5 | "presets": [
6 | ["env", {
7 | "targets": { "node": 7 }
8 | }],
9 | "stage-0"
10 | ],
11 | "plugins": ["istanbul"]
12 | },
13 | "main": {
14 | "presets": [
15 | ["env", {
16 | "targets": { "node": 7 }
17 | }],
18 | "stage-0"
19 | ]
20 | },
21 | "renderer": {
22 | "presets": [
23 | ["env", {
24 | "modules": false
25 | }],
26 | "stage-0"
27 | ]
28 | },
29 | "web": {
30 | "presets": [
31 | ["env", {
32 | "modules": false
33 | }],
34 | "stage-0"
35 | ]
36 | }
37 | },
38 | "plugins": ["transform-runtime"]
39 | }
40 |
--------------------------------------------------------------------------------
/.electron-vue/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | const { say } = require('cfonts')
6 | const chalk = require('chalk')
7 | const del = require('del')
8 | const { spawn } = require('child_process')
9 | const webpack = require('webpack')
10 | const Multispinner = require('multispinner')
11 |
12 |
13 | const mainConfig = require('./webpack.main.config')
14 | const rendererConfig = require('./webpack.renderer.config')
15 | const webConfig = require('./webpack.web.config')
16 |
17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' '
18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' '
19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
20 | const isCI = process.env.CI || false
21 |
22 | if (process.env.BUILD_TARGET === 'clean') clean()
23 | else if (process.env.BUILD_TARGET === 'web') web()
24 | else build()
25 |
26 | function clean () {
27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
28 | console.log(`\n${doneLog}\n`)
29 | process.exit()
30 | }
31 |
32 | function build () {
33 | greeting()
34 |
35 | del.sync(['dist/electron/*', '!.gitkeep'])
36 |
37 | const tasks = ['main', 'renderer']
38 | const m = new Multispinner(tasks, {
39 | preText: 'building',
40 | postText: 'process'
41 | })
42 |
43 | let results = ''
44 |
45 | m.on('success', () => {
46 | process.stdout.write('\x1B[2J\x1B[0f')
47 | console.log(`\n\n${results}`)
48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
49 | process.exit()
50 | })
51 |
52 | pack(mainConfig).then(result => {
53 | results += result + '\n\n'
54 | m.success('main')
55 | }).catch(err => {
56 | m.error('main')
57 | console.log(`\n ${errorLog}failed to build main process`)
58 | console.error(`\n${err}\n`)
59 | process.exit(1)
60 | })
61 |
62 | pack(rendererConfig).then(result => {
63 | results += result + '\n\n'
64 | m.success('renderer')
65 | }).catch(err => {
66 | m.error('renderer')
67 | console.log(`\n ${errorLog}failed to build renderer process`)
68 | console.error(`\n${err}\n`)
69 | process.exit(1)
70 | })
71 | }
72 |
73 | function pack (config) {
74 | return new Promise((resolve, reject) => {
75 | config.mode = 'production'
76 | webpack(config, (err, stats) => {
77 | if (err) reject(err.stack || err)
78 | else if (stats.hasErrors()) {
79 | let err = ''
80 |
81 | stats.toString({
82 | chunks: false,
83 | colors: true
84 | })
85 | .split(/\r?\n/)
86 | .forEach(line => {
87 | err += ` ${line}\n`
88 | })
89 |
90 | reject(err)
91 | } else {
92 | resolve(stats.toString({
93 | chunks: false,
94 | colors: true
95 | }))
96 | }
97 | })
98 | })
99 | }
100 |
101 | function web () {
102 | del.sync(['dist/web/*', '!.gitkeep'])
103 | webConfig.mode = 'production'
104 | webpack(webConfig, (err, stats) => {
105 | if (err || stats.hasErrors()) console.log(err)
106 |
107 | console.log(stats.toString({
108 | chunks: false,
109 | colors: true
110 | }))
111 |
112 | process.exit()
113 | })
114 | }
115 |
116 | function greeting () {
117 | const cols = process.stdout.columns
118 | let text = ''
119 |
120 | if (cols > 85) text = 'lets-build'
121 | else if (cols > 60) text = 'lets-|build'
122 | else text = false
123 |
124 | if (text && !isCI) {
125 | say(text, {
126 | colors: ['yellow'],
127 | font: 'simple3d',
128 | space: false
129 | })
130 | } else console.log(chalk.yellow.bold('\n lets-build'))
131 | console.log()
132 | }
--------------------------------------------------------------------------------
/.electron-vue/dev-client.js:
--------------------------------------------------------------------------------
1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
2 |
3 | hotClient.subscribe(event => {
4 | /**
5 | * Reload browser when HTMLWebpackPlugin emits a new index.html
6 | *
7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
8 | * https://github.com/SimulatedGREG/electron-vue/issues/437
9 | * https://github.com/jantimon/html-webpack-plugin/issues/680
10 | */
11 | // if (event.action === 'reload') {
12 | // window.location.reload()
13 | // }
14 |
15 | /**
16 | * Notify `mainWindow` when `main` process is compiling,
17 | * giving notice for an expected reload of the `electron` process
18 | */
19 | if (event.action === 'compiling') {
20 | document.body.innerHTML += `
21 |
34 |
35 |
36 | Compiling Main Process...
37 |
38 | `
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/.electron-vue/dev-runner.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const chalk = require('chalk')
4 | const electron = require('electron')
5 | const path = require('path')
6 | const { say } = require('cfonts')
7 | const { spawn } = require('child_process')
8 | const webpack = require('webpack')
9 | const WebpackDevServer = require('webpack-dev-server')
10 | const webpackHotMiddleware = require('webpack-hot-middleware')
11 |
12 | const mainConfig = require('./webpack.main.config')
13 | const rendererConfig = require('./webpack.renderer.config')
14 |
15 | let electronProcess = null
16 | let manualRestart = false
17 | let hotMiddleware
18 |
19 | function logStats (proc, data) {
20 | let log = ''
21 |
22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
23 | log += '\n\n'
24 |
25 | if (typeof data === 'object') {
26 | data.toString({
27 | colors: true,
28 | chunks: false
29 | }).split(/\r?\n/).forEach(line => {
30 | log += ' ' + line + '\n'
31 | })
32 | } else {
33 | log += ` ${data}\n`
34 | }
35 |
36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'
37 |
38 | console.log(log)
39 | }
40 |
41 | function startRenderer () {
42 | return new Promise((resolve, reject) => {
43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
44 | rendererConfig.mode = 'development'
45 | const compiler = webpack(rendererConfig)
46 | hotMiddleware = webpackHotMiddleware(compiler, {
47 | log: false,
48 | heartbeat: 2500
49 | })
50 |
51 | compiler.hooks.compilation.tap('compilation', compilation => {
52 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
53 | hotMiddleware.publish({ action: 'reload' })
54 | cb()
55 | })
56 | })
57 |
58 | compiler.hooks.done.tap('done', stats => {
59 | logStats('Renderer', stats)
60 | })
61 |
62 | const server = new WebpackDevServer(
63 | compiler,
64 | {
65 | contentBase: path.join(__dirname, '../'),
66 | quiet: true,
67 | before (app, ctx) {
68 | app.use(hotMiddleware)
69 | ctx.middleware.waitUntilValid(() => {
70 | resolve()
71 | })
72 | }
73 | }
74 | )
75 |
76 | server.listen(9080)
77 | })
78 | }
79 |
80 | function startMain () {
81 | return new Promise((resolve, reject) => {
82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
83 | mainConfig.mode = 'development'
84 | const compiler = webpack(mainConfig)
85 |
86 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
87 | logStats('Main', chalk.white.bold('compiling...'))
88 | hotMiddleware.publish({ action: 'compiling' })
89 | done()
90 | })
91 |
92 | compiler.watch({}, (err, stats) => {
93 | if (err) {
94 | console.log(err)
95 | return
96 | }
97 |
98 | logStats('Main', stats)
99 |
100 | if (electronProcess && electronProcess.kill) {
101 | manualRestart = true
102 | process.kill(electronProcess.pid)
103 | electronProcess = null
104 | startElectron()
105 |
106 | setTimeout(() => {
107 | manualRestart = false
108 | }, 5000)
109 | }
110 |
111 | resolve()
112 | })
113 | })
114 | }
115 |
116 | function startElectron () {
117 | var args = [
118 | '--inspect=5858',
119 | path.join(__dirname, '../dist/electron/main.js')
120 | ]
121 |
122 | // detect yarn or npm and process commandline args accordingly
123 | if (process.env.npm_execpath.endsWith('yarn.js')) {
124 | args = args.concat(process.argv.slice(3))
125 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
126 | args = args.concat(process.argv.slice(2))
127 | }
128 |
129 | electronProcess = spawn(electron, args)
130 |
131 | electronProcess.stdout.on('data', data => {
132 | electronLog(data, 'blue')
133 | })
134 | electronProcess.stderr.on('data', data => {
135 | electronLog(data, 'red')
136 | })
137 |
138 | electronProcess.on('close', () => {
139 | if (!manualRestart) process.exit()
140 | })
141 | }
142 |
143 | function electronLog (data, color) {
144 | let log = ''
145 | data = data.toString().split(/\r?\n/)
146 | data.forEach(line => {
147 | log += ` ${line}\n`
148 | })
149 | if (/[0-9A-z]+/.test(log)) {
150 | console.log(
151 | chalk[color].bold('┏ Electron -------------------') +
152 | '\n\n' +
153 | log +
154 | chalk[color].bold('┗ ----------------------------') +
155 | '\n'
156 | )
157 | }
158 | }
159 |
160 | function greeting () {
161 | const cols = process.stdout.columns
162 | let text = ''
163 |
164 | if (cols > 104) text = 'electron-vue'
165 | else if (cols > 76) text = 'electron-|vue'
166 | else text = false
167 |
168 | if (text) {
169 | say(text, {
170 | colors: ['yellow'],
171 | font: 'simple3d',
172 | space: false
173 | })
174 | } else console.log(chalk.yellow.bold('\n electron-vue'))
175 | console.log(chalk.blue(' getting ready...') + '\n')
176 | }
177 |
178 | function init () {
179 | greeting()
180 |
181 | Promise.all([startRenderer(), startMain()])
182 | .then(() => {
183 | startElectron()
184 | })
185 | .catch(err => {
186 | console.error(err)
187 | })
188 | }
189 |
190 | init()
191 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'main'
4 |
5 | const path = require('path')
6 | const {
7 | dependencies
8 | } = require('../package.json')
9 | const webpack = require('webpack')
10 |
11 | const BabiliWebpackPlugin = require('babili-webpack-plugin')
12 |
13 | let mainConfig = {
14 | entry: {
15 | main: path.join(__dirname, '../src/main/index.js')
16 | },
17 | externals: [
18 | ...Object.keys(dependencies || {})
19 | ],
20 | module: {
21 | rules: [{
22 | test: /\.js$/,
23 | use: 'babel-loader',
24 | exclude: /node_modules/
25 | },
26 | {
27 | test: /\.node$/,
28 | use: 'node-loader'
29 | },
30 | {
31 | test: /\.tsx?$/,
32 | loader: 'ts-loader',
33 | exclude: /node_modules/,
34 | options: {
35 | appendTsSuffixTo: [/\.vue$/]
36 | }
37 | }
38 | ]
39 | },
40 | node: {
41 | __dirname: process.env.NODE_ENV !== 'production',
42 | __filename: process.env.NODE_ENV !== 'production'
43 | },
44 | output: {
45 | filename: '[name].js',
46 | libraryTarget: 'commonjs2',
47 | path: path.join(__dirname, '../dist/electron')
48 | },
49 | plugins: [
50 | new webpack.NoEmitOnErrorsPlugin()
51 | ],
52 | resolve: {
53 | extensions: ['.ts', '.js', '.json', '.node']
54 | },
55 | target: 'electron-main'
56 | }
57 |
58 | /**
59 | * Adjust mainConfig for development settings
60 | */
61 | if (process.env.NODE_ENV !== 'production') {
62 | mainConfig.plugins.push(
63 | new webpack.DefinePlugin({
64 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
65 | })
66 | )
67 | }
68 |
69 | /**
70 | * Adjust mainConfig for production settings
71 | */
72 | if (process.env.NODE_ENV === 'production') {
73 | mainConfig.plugins.push(
74 | new BabiliWebpackPlugin(),
75 | new webpack.DefinePlugin({
76 | 'process.env.NODE_ENV': '"production"'
77 | })
78 | )
79 | }
80 |
81 | module.exports = mainConfig
--------------------------------------------------------------------------------
/.electron-vue/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'renderer'
4 |
5 | const path = require('path')
6 | const {
7 | dependencies
8 | } = require('../package.json')
9 | const webpack = require('webpack')
10 |
11 | const BabiliWebpackPlugin = require('babili-webpack-plugin')
12 | const CopyWebpackPlugin = require('copy-webpack-plugin')
13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
14 | const HtmlWebpackPlugin = require('html-webpack-plugin')
15 | const {
16 | VueLoaderPlugin
17 | } = require('vue-loader')
18 |
19 | /**
20 | * List of node_modules to include in webpack bundle
21 | *
22 | * Required for specific packages like Vue UI libraries
23 | * that provide pure *.vue files that need compiling
24 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
25 | */
26 | let whiteListedModules = ['vue']
27 |
28 | function resolve(dir) {
29 | return path.join(__dirname, '..', dir)
30 | }
31 |
32 | let rendererConfig = {
33 | devtool: '#cheap-module-eval-source-map',
34 | entry: {
35 | renderer: path.join(__dirname, '../src/renderer/main.js')
36 | },
37 | externals: [
38 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
39 | ],
40 | module: {
41 | rules: [{
42 | test: /\.scss$/,
43 | use: ['vue-style-loader', 'css-loader', 'sass-loader']
44 | },
45 | {
46 | test: /\.sass$/,
47 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
48 | },
49 | {
50 | test: /\.less$/,
51 | use: ['vue-style-loader', 'css-loader', 'less-loader']
52 | },
53 | {
54 | test: /\.css$/,
55 | use: ['vue-style-loader', 'css-loader']
56 | },
57 | {
58 | test: /\.html$/,
59 | use: 'vue-html-loader'
60 | },
61 | {
62 | test: /\.js$/,
63 | use: 'babel-loader',
64 | exclude: /node_modules/
65 | },
66 | {
67 | test: /\.node$/,
68 | use: 'node-loader'
69 | },
70 | {
71 | test: /\.vue$/,
72 | use: {
73 | loader: 'vue-loader',
74 | options: {
75 | extractCSS: process.env.NODE_ENV === 'production',
76 | loaders: {
77 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
78 | scss: 'vue-style-loader!css-loader!sass-loader',
79 | less: 'vue-style-loader!css-loader!less-loader'
80 | }
81 | }
82 | }
83 | },
84 | {
85 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
86 | use: {
87 | loader: 'url-loader',
88 | query: {
89 | limit: 10000,
90 | name: 'imgs/[name]--[folder].[ext]'
91 | }
92 | }
93 | },
94 | {
95 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
96 | loader: 'url-loader',
97 | options: {
98 | limit: 10000,
99 | name: 'media/[name]--[folder].[ext]'
100 | }
101 | },
102 | {
103 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
104 | use: {
105 | loader: 'url-loader',
106 | query: {
107 | limit: 10000,
108 | name: 'fonts/[name]--[folder].[ext]'
109 | }
110 | }
111 | },
112 | {
113 | test: /\.tsx?$/,
114 | loader: 'ts-loader',
115 | exclude: /node_modules/,
116 | options: {
117 | appendTsSuffixTo: [/\.vue$/]
118 | }
119 | }
120 | ]
121 | },
122 | node: {
123 | __dirname: process.env.NODE_ENV !== 'production',
124 | __filename: process.env.NODE_ENV !== 'production'
125 | },
126 | plugins: [
127 | new VueLoaderPlugin(),
128 | new MiniCssExtractPlugin({
129 | filename: 'styles.css'
130 | }),
131 | new HtmlWebpackPlugin({
132 | filename: 'index.html',
133 | template: path.resolve(__dirname, '../src/index.ejs'),
134 | templateParameters(compilation, assets, options) {
135 | return {
136 | compilation: compilation,
137 | webpack: compilation.getStats().toJson(),
138 | webpackConfig: compilation.options,
139 | htmlWebpackPlugin: {
140 | files: assets,
141 | options: options
142 | },
143 | process,
144 | };
145 | },
146 | minify: {
147 | collapseWhitespace: true,
148 | removeAttributeQuotes: true,
149 | removeComments: true
150 | },
151 | nodeModules: process.env.NODE_ENV !== 'production' ?
152 | path.resolve(__dirname, '../node_modules') : false
153 | }),
154 | new webpack.HotModuleReplacementPlugin(),
155 | new webpack.NoEmitOnErrorsPlugin()
156 | ],
157 | output: {
158 | filename: '[name].js',
159 | libraryTarget: 'commonjs2',
160 | path: path.join(__dirname, '../dist/electron')
161 | },
162 | resolve: {
163 | alias: {
164 | '@': path.join(__dirname, '../src/renderer'),
165 | 'vue$': 'vue/dist/vue.esm.js'
166 | },
167 | extensions: ['.ts', '.js', '.vue', '.json', '.css', '.node']
168 | },
169 | target: 'electron-renderer'
170 | }
171 |
172 | /**
173 | * Adjust rendererConfig for development settings
174 | */
175 | if (process.env.NODE_ENV !== 'production') {
176 | rendererConfig.plugins.push(
177 | new webpack.DefinePlugin({
178 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
179 | })
180 | )
181 | }
182 |
183 | /**
184 | * Adjust rendererConfig for production settings
185 | */
186 | if (process.env.NODE_ENV === 'production') {
187 | rendererConfig.devtool = ''
188 |
189 | rendererConfig.plugins.push(
190 | new BabiliWebpackPlugin(),
191 | new CopyWebpackPlugin([{
192 | from: path.join(__dirname, '../static'),
193 | to: path.join(__dirname, '../dist/electron/static'),
194 | ignore: ['.*']
195 | }]),
196 | new webpack.DefinePlugin({
197 | 'process.env.NODE_ENV': '"production"'
198 | }),
199 | new webpack.LoaderOptionsPlugin({
200 | minimize: true
201 | })
202 | )
203 | }
204 |
205 | module.exports = rendererConfig
--------------------------------------------------------------------------------
/.electron-vue/webpack.web.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'web'
4 |
5 | const path = require('path')
6 | const webpack = require('webpack')
7 |
8 | const BabiliWebpackPlugin = require('babili-webpack-plugin')
9 | const CopyWebpackPlugin = require('copy-webpack-plugin')
10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
11 | const HtmlWebpackPlugin = require('html-webpack-plugin')
12 | const { VueLoaderPlugin } = require('vue-loader')
13 |
14 | let webConfig = {
15 | devtool: '#cheap-module-eval-source-map',
16 | entry: {
17 | web: path.join(__dirname, '../src/renderer/main.js')
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.scss$/,
23 | use: ['vue-style-loader', 'css-loader', 'sass-loader']
24 | },
25 | {
26 | test: /\.sass$/,
27 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
28 | },
29 | {
30 | test: /\.less$/,
31 | use: ['vue-style-loader', 'css-loader', 'less-loader']
32 | },
33 | {
34 | test: /\.css$/,
35 | use: ['vue-style-loader', 'css-loader']
36 | },
37 | {
38 | test: /\.html$/,
39 | use: 'vue-html-loader'
40 | },
41 | {
42 | test: /\.js$/,
43 | use: 'babel-loader',
44 | include: [ path.resolve(__dirname, '../src/renderer') ],
45 | exclude: /node_modules/
46 | },
47 | {
48 | test: /\.vue$/,
49 | use: {
50 | loader: 'vue-loader',
51 | options: {
52 | extractCSS: true,
53 | loaders: {
54 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
55 | scss: 'vue-style-loader!css-loader!sass-loader',
56 | less: 'vue-style-loader!css-loader!less-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 VueLoaderPlugin(),
85 | new MiniCssExtractPlugin({filename: 'styles.css'}),
86 | new HtmlWebpackPlugin({
87 | filename: 'index.html',
88 | template: path.resolve(__dirname, '../src/index.ejs'),
89 | templateParameters(compilation, assets, options) {
90 | return {
91 | compilation: compilation,
92 | webpack: compilation.getStats().toJson(),
93 | webpackConfig: compilation.options,
94 | htmlWebpackPlugin: {
95 | files: assets,
96 | options: options
97 | },
98 | process,
99 | };
100 | },
101 | minify: {
102 | collapseWhitespace: true,
103 | removeAttributeQuotes: true,
104 | removeComments: true
105 | },
106 | nodeModules: false
107 | }),
108 | new webpack.DefinePlugin({
109 | 'process.env.IS_WEB': 'true'
110 | }),
111 | new webpack.HotModuleReplacementPlugin(),
112 | new webpack.NoEmitOnErrorsPlugin()
113 | ],
114 | output: {
115 | filename: '[name].js',
116 | path: path.join(__dirname, '../dist/web')
117 | },
118 | resolve: {
119 | alias: {
120 | '@': path.join(__dirname, '../src/renderer'),
121 | 'vue$': 'vue/dist/vue.esm.js'
122 | },
123 | extensions: ['.js', '.vue', '.json', '.css']
124 | },
125 | target: 'web'
126 | }
127 |
128 | /**
129 | * Adjust webConfig for production settings
130 | */
131 | if (process.env.NODE_ENV === 'production') {
132 | webConfig.devtool = ''
133 |
134 | webConfig.plugins.push(
135 | new BabiliWebpackPlugin(),
136 | new CopyWebpackPlugin([
137 | {
138 | from: path.join(__dirname, '../static'),
139 | to: path.join(__dirname, '../dist/web/static'),
140 | ignore: ['.*']
141 | }
142 | ]),
143 | new webpack.DefinePlugin({
144 | 'process.env.NODE_ENV': '"production"'
145 | }),
146 | new webpack.LoaderOptionsPlugin({
147 | minimize: true
148 | })
149 | )
150 | }
151 |
152 | module.exports = webConfig
153 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | dist/electron/*
3 | dist/web/*
4 | build/*
5 | !build/icons
6 | coverage
7 | node_modules/
8 | npm-debug.log
9 | npm-debug.log.*
10 | thumbs.db
11 | !.gitkeep
12 |
--------------------------------------------------------------------------------
/.jsbeautifyrc:
--------------------------------------------------------------------------------
1 | {
2 | "indent_with_tabs": false,
3 | "max_preserve_newlines": 2,
4 | "preserve_newlines": true,
5 | "keep_array_indentation": false,
6 | "break_chained_methods": false,
7 | "wrap_line_length": 200,
8 | "end_with_newline": true,
9 | "brace_style": "collapse,preserve-inline",
10 | "unformatted": ["a", "abbr", "area", "audio", "b", "bdi", "bdo", "br", "button", "canvas", "cite", "code", "data",
11 | "datalist", "del", "dfn", "em", "embed", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "map",
12 | "mark", "math", "meter", "noscript", "object", "output", "progress", "q", "ruby", "s", "samp", "select", "small",
13 | "span", "strong", "sub", "sup", "template", "textarea", "time", "u", "var", "video", "wbr", "text", "acronym",
14 | "address", "big", "dt", "ins", "small", "strike", "tt", "pre", "h1", "h2", "h3", "h4", "h5", "h6"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | parser: 'flow',
4 | tabWidth: 4,
5 | singleQuote: true
6 | };
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Commented sections below can be used to run tests on the CI server
2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
3 | osx_image: xcode8.3
4 | sudo: required
5 | dist: trusty
6 | language: c
7 | matrix:
8 | include:
9 | - os: osx
10 | - os: linux
11 | env: CC=clang CXX=clang++ npm_config_clang=1
12 | compiler: clang
13 | cache:
14 | directories:
15 | - node_modules
16 | - "$HOME/.electron"
17 | - "$HOME/.cache"
18 | addons:
19 | apt:
20 | packages:
21 | - libgnome-keyring-dev
22 | - icnsutils
23 | #- xvfb
24 | before_install:
25 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([
26 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz
27 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
28 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
29 | install:
30 | #- export DISPLAY=':99.0'
31 | #- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
32 | - nvm install 7
33 | - curl -o- -L https://yarnpkg.com/install.sh | bash
34 | - source ~/.bashrc
35 | - npm install -g xvfb-maybe
36 | - yarn
37 | script:
38 | #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js
39 | - yarn run build
40 | branches:
41 | only:
42 | - master
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Alex
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eHunter_local
2 | eHunter_local is local version of [eHunter](https://github.com/hanFengSan/eHunter),supporting for Windows and MacOS. It's a lightweight image reader for reading manga.
3 |
4 | ### Preview
5 | 
6 |
7 | 
8 |
9 | 
10 |
11 | ### Download
12 | [Github Release](https://github.com/hanFengSan/eHunter_local/releases)
13 |
14 | [百度网盘](https://pan.baidu.com/s/1wEnBe9uGoBKzNd4DCfbuAg) 提取码: czft
15 |
16 | ### Build Setup
17 |
18 | ``` bash
19 | # install dependencies
20 | npm install
21 |
22 | # serve with hot reload at localhost:9080
23 | npm run dev
24 |
25 | # build electron application for production
26 | npm run build
27 |
28 | ```
29 |
30 | ---
31 |
32 | This project was generated with [electron-vue](https://github.com/SimulatedGREG/electron-vue) using [vue-cli](https://github.com/vuejs/vue-cli). Documentation about the original structure can be found [here](https://simulatedgreg.gitbooks.io/electron-vue/content/index.html).
33 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Commented sections below can be used to run tests on the CI server
2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
3 | version: 0.1.{build}
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | image: Visual Studio 2017
10 | platform:
11 | - x64
12 |
13 | cache:
14 | - node_modules
15 | - '%APPDATA%\npm-cache'
16 | - '%USERPROFILE%\.electron'
17 | - '%USERPROFILE%\AppData\Local\Yarn\cache'
18 |
19 | init:
20 | - git config --global core.autocrlf input
21 |
22 | install:
23 | - ps: Install-Product node 8 x64
24 | - git reset --hard HEAD
25 | - yarn
26 | - node --version
27 |
28 | build_script:
29 | #- yarn test
30 | - yarn build
31 |
32 | test: off
33 |
--------------------------------------------------------------------------------
/build/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/build/icons/256x256.png
--------------------------------------------------------------------------------
/build/icons/icon old.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/build/icons/icon old.ico
--------------------------------------------------------------------------------
/build/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/build/icons/icon.icns
--------------------------------------------------------------------------------
/build/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/build/icons/icon.ico
--------------------------------------------------------------------------------
/build/icons/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/build/icons/icon.psd
--------------------------------------------------------------------------------
/dist/electron/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/dist/electron/.gitkeep
--------------------------------------------------------------------------------
/dist/web/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/dist/web/.gitkeep
--------------------------------------------------------------------------------
/github_image/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/github_image/001.png
--------------------------------------------------------------------------------
/github_image/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/github_image/002.png
--------------------------------------------------------------------------------
/github_image/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanFengSan/eHunter_local/4e8be3242b586aa07bcf56f4b9da1e9d116da4d2/github_image/003.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ehunter_local",
3 | "version": "1.1.0",
4 | "author": "Alex Chen ",
5 | "description": "ehunter",
6 | "license": "MIT",
7 | "main": "./dist/electron/main.js",
8 | "scripts": {
9 | "build": "node .electron-vue/build.js && electron-builder",
10 | "build:dir": "node .electron-vue/build.js && electron-builder --dir",
11 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
12 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
13 | "dev": "node .electron-vue/dev-runner.js",
14 | "pack": "npm run pack:main && npm run pack:renderer",
15 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
16 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
17 | "test": "npm run unit",
18 | "unit": "karma start test/unit/karma.conf.js",
19 | "postinstall": ""
20 | },
21 | "build": {
22 | "productName": "eHunter-local",
23 | "appId": "info.alexskye.ehunter",
24 | "directories": {
25 | "output": "build"
26 | },
27 | "files": [
28 | "dist/electron/**/*"
29 | ],
30 | "dmg": {
31 | "contents": [
32 | {
33 | "x": 410,
34 | "y": 150,
35 | "type": "link",
36 | "path": "/Applications"
37 | },
38 | {
39 | "x": 130,
40 | "y": 150,
41 | "type": "file"
42 | }
43 | ]
44 | },
45 | "mac": {
46 | "icon": "build/icons/icon.icns"
47 | },
48 | "win": {
49 | "target": "portable",
50 | "icon": "build/icons/icon.ico"
51 | },
52 | "linux": {
53 | "icon": "build/icons"
54 | }
55 | },
56 | "dependencies": {
57 | "@babel/polyfill": "^7.4.3",
58 | "axios": "^0.18.0",
59 | "css.escape": "^1.5.1",
60 | "file-url": "^3.0.0",
61 | "image-size": "^0.7.3",
62 | "markdown-it": "^8.4.2",
63 | "markdown-it-emoji": "^1.4.0",
64 | "react-native-storage": "0.2.2",
65 | "ts-loader": "^5.3.3",
66 | "twemoji": "^12.0.1",
67 | "typescript": "^3.4.4",
68 | "vue": "^2.5.16",
69 | "vue-electron": "^1.0.6",
70 | "vue-router": "^3.0.1",
71 | "vuex": "^3.0.1",
72 | "vuex-electron": "^1.0.0"
73 | },
74 | "devDependencies": {
75 | "ajv": "^6.5.0",
76 | "babel-core": "^6.26.3",
77 | "babel-loader": "^7.1.4",
78 | "babel-plugin-istanbul": "^4.1.6",
79 | "babel-plugin-transform-runtime": "^6.23.0",
80 | "babel-preset-env": "^1.7.0",
81 | "babel-preset-stage-0": "^6.24.1",
82 | "babel-register": "^6.26.0",
83 | "babili-webpack-plugin": "^0.1.2",
84 | "cfonts": "^2.1.2",
85 | "chai": "^4.1.2",
86 | "chalk": "^2.4.1",
87 | "copy-webpack-plugin": "^4.5.1",
88 | "cross-env": "^5.1.6",
89 | "css-loader": "^0.28.11",
90 | "del": "^3.0.0",
91 | "devtron": "^1.4.0",
92 | "electron": "^2.0.4",
93 | "electron-builder": "^21.2.0",
94 | "electron-debug": "^1.5.0",
95 | "electron-devtools-installer": "^2.2.4",
96 | "file-loader": "^1.1.11",
97 | "html-webpack-plugin": "^3.2.0",
98 | "inject-loader": "^4.0.1",
99 | "karma": "^2.0.2",
100 | "karma-chai": "^0.1.0",
101 | "karma-coverage": "^1.1.2",
102 | "karma-electron": "^6.0.0",
103 | "karma-mocha": "^1.3.0",
104 | "karma-sourcemap-loader": "^0.3.7",
105 | "karma-spec-reporter": "^0.0.32",
106 | "karma-webpack": "^3.0.0",
107 | "mini-css-extract-plugin": "0.4.0",
108 | "mocha": "^5.2.0",
109 | "multispinner": "^0.2.1",
110 | "node-loader": "^0.6.0",
111 | "node-sass": "^4.12.0",
112 | "sass-loader": "^7.1.0",
113 | "style-loader": "^0.21.0",
114 | "url-loader": "^1.0.1",
115 | "vue-html-loader": "^1.2.4",
116 | "vue-loader": "^15.2.4",
117 | "vue-style-loader": "^4.1.0",
118 | "vue-template-compiler": "^2.5.16",
119 | "webpack": "^4.15.1",
120 | "webpack-cli": "^3.0.8",
121 | "webpack-dev-server": "^3.1.4",
122 | "webpack-hot-middleware": "^2.22.2",
123 | "webpack-merge": "^4.1.3"
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | version: '1.1.0',
3 | homePage: 'https://github.com/hanFengSan/eHunter_local',
4 | email: 'c360785655@gmail.com',
5 | updateServer1: 'https://raw.githubusercontent.com/hanFengSan/eHunter_local/master/update.json',
6 | updateServer2: 'https://raw.githubusercontent.com/hanFengSan/eHunter_local/master/update.json'
7 | };
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | eHunter-local
6 |
17 | <% if (htmlWebpackPlugin.options.nodeModules) { %>
18 |
19 |
22 |
32 | <% } %>
33 |
34 |
35 |
36 |
37 | <% if (!process.browser) { %>
38 |
41 | <% } %>
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/main/bean/FileItem.ts:
--------------------------------------------------------------------------------
1 | export interface FileItem {
2 | name: string;
3 | path: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/index.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is used specifically and only for development. It installs
3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to
4 | * modify this file, but it can be used to extend your development
5 | * environment.
6 | */
7 |
8 | /* eslint-disable */
9 |
10 | // Install `electron-debug` with `devtron`
11 | require('electron-debug')({ showDevTools: true })
12 |
13 | // Install `vue-devtools`
14 | require('electron').app.on('ready', () => {
15 | let installExtension = require('electron-devtools-installer')
16 | installExtension.default(installExtension.VUEJS_DEVTOOLS)
17 | .then(() => {})
18 | .catch(err => {
19 | console.log('Unable to install `vue-devtools`: \n', err)
20 | })
21 | })
22 |
23 | // Require `main` process to boot app
24 | require('./index')
--------------------------------------------------------------------------------
/src/main/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | app,
3 | BrowserWindow,
4 | ipcMain,
5 | shell
6 | } from 'electron'
7 |
8 | import {
9 | AlbumDirSorter
10 | } from './service/AlbumDirSorter.ts'
11 |
12 | import {
13 | AlbumServiceImpl
14 | } from './service/AlbumServiceImpl.ts'
15 |
16 | /**
17 | * Set `__static` path to static files in production
18 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
19 | */
20 | if (process.env.NODE_ENV !== 'development') {
21 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
22 | }
23 |
24 | let mainWindow
25 | const winURL = process.env.NODE_ENV === 'development' ?
26 | `http://localhost:9080` :
27 | `file://${__dirname}/index.html`
28 |
29 | function createWindow() {
30 | /**
31 | * Initial window options
32 | */
33 | mainWindow = new BrowserWindow({
34 | title: 'eHunter-local',
35 | height: 763,
36 | useContentSize: true,
37 | width: 1200,
38 | minWidth: 400,
39 | minHeight: 300,
40 | backgroundColor: '#333333',
41 | webPreferences: {
42 | webSecurity: false
43 | }
44 | })
45 |
46 | mainWindow.loadURL(winURL)
47 |
48 | mainWindow.on('closed', () => {
49 | mainWindow = null
50 | })
51 |
52 | // Hook new window, and open url in Browser
53 | mainWindow.webContents.on('new-window', function(event, url) {
54 | event.preventDefault();
55 | shell.openExternal(url);
56 | });
57 | }
58 |
59 | app.on('ready', createWindow)
60 |
61 | app.on('window-all-closed', () => {
62 | if (process.platform !== 'darwin') {
63 | app.quit()
64 | }
65 | })
66 |
67 | app.on('activate', () => {
68 | if (mainWindow === null) {
69 | createWindow()
70 | }
71 | })
72 |
73 | ipcMain.on('SELECT_ALBUM_DIR', async (event, path) => {
74 | try {
75 | let fileItems = await (new AlbumDirSorter(path)).sort();
76 | let albumServiceImpl = new AlbumServiceImpl();
77 | await albumServiceImpl.parseFileItems(path, fileItems);
78 | event.sender.send('ALBUM_DATA', albumServiceImpl);
79 | } catch (err) {
80 | event.sender.send('ERROR', err.message);
81 | }
82 | });
83 |
84 | ipcMain.on('OPEN_HOME', async (event, path) => {
85 | try {
86 | let fileItems = await (new AlbumDirSorter(path)).sort();
87 | let albumServiceImpl = new AlbumServiceImpl();
88 | await albumServiceImpl.parseFileItems(path, fileItems);
89 | event.sender.send('ALBUM_DATA', albumServiceImpl);
90 | } catch (err) {
91 | event.sender.send('ERROR', err.message);
92 | }
93 | });
94 |
95 | /**
96 | * Auto Updater
97 | *
98 | * Uncomment the following code below and install `electron-updater` to
99 | * support auto updating. Code Signing with a valid certificate is required.
100 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
101 | */
102 |
103 | /*
104 | import { autoUpdater } from 'electron-updater'
105 |
106 | autoUpdater.on('update-downloaded', () => {
107 | autoUpdater.quitAndInstall()
108 | })
109 |
110 | app.on('ready', () => {
111 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
112 | })
113 | */
114 |
--------------------------------------------------------------------------------
/src/main/service/AlbumDirSorter.ts:
--------------------------------------------------------------------------------
1 | import { Stats } from "fs";
2 | import { FileItem } from "../bean/FileItem";
3 |
4 | const util = require('util');
5 | const fs = require('fs');
6 | const path = require('path');
7 |
8 | const stat = util.promisify(fs.stat);
9 | const readdir = util.promisify(fs.readdir);
10 |
11 | export class AlbumDirSorter {
12 | private dirPath: string;
13 | private stats: Stats | undefined;
14 |
15 | constructor(path: string) {
16 | this.dirPath = path;
17 | }
18 |
19 | async sort(): Promise> {
20 | this.stats = await stat(this.dirPath);
21 | if (this.noDir())
22 | throw new Error('ERROR_NO_DIR');
23 | let fileItems = await this.getImgsFromDir();
24 | if (fileItems.length === 0)
25 | throw new Error('NO_IMG');
26 | var collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
27 | fileItems.sort((a, b) => {
28 | return collator.compare(a.name, b.name);
29 | });
30 | return fileItems;
31 | }
32 |
33 |
34 | private noDir(): boolean {
35 | return !this.stats!.isDirectory();
36 | }
37 |
38 | private async getImgsFromDir(): Promise> {
39 | let files = await readdir(this.dirPath);
40 | let fileItems = files.filter(i => /(\.jpg|\.png)$/i.test(i)).map(i => {
41 | return {
42 | name: i,
43 | path: path.join(this.dirPath, i)
44 | }
45 | })
46 | return fileItems;
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/service/AlbumServiceImpl.ts:
--------------------------------------------------------------------------------
1 | import { AlbumService, PreviewThumbnailStyle } from '../../renderer/service/AlbumService';
2 | import { FileItem } from '../bean/FileItem';
3 | import { ImgPageInfo } from '../../renderer/bean/ImgPageInfo';
4 | import { ThumbInfo, ThumbMode } from '../../renderer/bean/ThumbInfo';
5 |
6 | const util = require('util');
7 | const fs = require('fs');
8 | const path = require('path');
9 | const ImageSize = require('image-size');
10 |
11 | const stat = util.promisify(fs.stat);
12 | const readdir = util.promisify(fs.readdir);
13 | const sizeOf = util.promisify(ImageSize);
14 | const fileUrl = require('file-url');
15 | const CSSEscape = require('css.escape');
16 |
17 | export class AlbumServiceImpl extends AlbumService {
18 | private imgPageInfos: Array = [];
19 | private thumbInfos: Array = [];
20 | private title: string = '';
21 |
22 | constructor() {
23 | super();
24 | }
25 |
26 | static fromJSON(o: Object): AlbumServiceImpl {
27 | return Object.assign(new AlbumServiceImpl(), o);
28 | }
29 |
30 | async parseFileItems(dirPath: string, fileItems: Array): Promise {
31 | this.imgPageInfos = [];
32 | this.thumbInfos = [];
33 | for (let i = 0; i < fileItems.length; i++) {
34 | let id = fileItems[i].name;
35 | let index = i;
36 | let pageUrl = fileItems[i].path;
37 | let src = fileUrl(fileItems[i].path);
38 | let dimensions = await sizeOf(fileItems[i].path);
39 | let heightOfWidth = dimensions.height / dimensions.width;
40 | this.imgPageInfos.push({ id, index, pageUrl, src, heightOfWidth });
41 | this.thumbInfos.push({ id, src: CSSEscape(src), mode: ThumbMode.IMG });
42 | }
43 | this.title = path.basename(dirPath);
44 | }
45 |
46 | async getPageCount(): Promise {
47 | return this.imgPageInfos.length;
48 | }
49 |
50 | async getCurPageNum(): Promise {
51 | return 0;
52 | }
53 |
54 | async getTitle(): Promise {
55 | return this.title;
56 | }
57 |
58 | async getImgPageInfos(): Promise> {
59 | return this.imgPageInfos;
60 | }
61 |
62 | async getImgPageInfo(index: number): Promise {
63 | return this.imgPageInfos[index];
64 | }
65 |
66 | async getImgSrc(index: number, mode): Promise {
67 | return this.imgPageInfos[index].src;
68 | }
69 |
70 | async getNewImgSrc(index: number, mode): Promise {
71 | return this.imgPageInfos[index].src;
72 | }
73 | async getThumbInfos(noCache?: boolean): Promise> {
74 | return this.thumbInfos;
75 | }
76 |
77 | async getThumbInfo(index: number): Promise {
78 | return this.thumbInfos[index];
79 | }
80 |
81 | async getAlbumId(): Promise {
82 | return await this.getTitle();
83 | }
84 |
85 | async getPreviewThumbnailStyle(index: number, imgPageInfo: ImgPageInfo, thumbInfo: ThumbInfo): Promise {
86 | return {
87 | 'background-image': '',
88 | 'background-position': '',
89 | 'background-size': ''
90 | };
91 | }
92 |
93 | supportOriginImg(): boolean {
94 | return false;
95 | }
96 | supportImgChangeSource(): boolean {
97 | return false;
98 | }
99 |
100 | supportThumbView(): boolean {
101 | return true;
102 | }
103 | }
--------------------------------------------------------------------------------
/src/main/service/DirBrowser.ts:
--------------------------------------------------------------------------------
1 | import { Stats } from "fs";
2 | import { FileItem } from "../bean/FileItem";
3 |
4 | const util = require('util');
5 | const fs = require('fs');
6 | const path = require('path');
7 |
8 | const stat = util.promisify(fs.stat);
9 | const readdir = util.promisify(fs.readdir);
10 |
11 | export class DirBrowser {
12 | private dirPath: string;
13 | private stats: Stats | undefined;
14 |
15 | constructor(path: string) {
16 | this.dirPath = path;
17 | }
18 |
19 | async sort(): Promise> {
20 | this.stats = await stat(this.dirPath);
21 | if (this.noDir())
22 | throw new Error('ERROR_NO_DIR');
23 | let fileItems = await this.getImgsFromDir();
24 | if (fileItems.length === 0)
25 | throw new Error('NO_IMG');
26 | fileItems = this.sortByNum(fileItems);
27 | return fileItems;
28 | }
29 |
30 |
31 | private noDir(): boolean {
32 | return !this.stats!.isDirectory();
33 | }
34 |
35 | private async getImgsFromDir(): Promise> {
36 | let files = await readdir(this.dirPath);
37 | let fileItems = files.filter(i => /(\.jpg|\.png)$/i.test(i)).map(i => {
38 | return {
39 | name: i,
40 | path: path.join(this.dirPath, i)
41 | }
42 | })
43 | return fileItems;
44 | }
45 |
46 | private sortByNum(fileItems: Array): Array {
47 | let numTailList: Array = [];
48 | let numHeadList: Array = [];
49 | let otherList: Array = [];
50 | fileItems.forEach(i => {
51 | if (/^.*?\d+?(\.jpg|\.png)$/i.test(i.name)) {
52 | numTailList.push(i);
53 | } else if (/^\d+?.*?(\.jpg|\.png)$/i.test(i.name)) {
54 | numHeadList.push(i);
55 | } else {
56 | otherList.push(i);
57 | }
58 | });
59 | numTailList.sort((a, b) => {
60 | let reg = /^.*?(\d+?)(\.jpg|\.png)$/i;
61 | a.name.match(reg)
62 | let num1 = Number(RegExp.$1);
63 | b.name.match(reg)
64 | let num2 = Number(RegExp.$1);
65 | return num1 - num2;
66 | });
67 | numHeadList.sort((a, b) => {
68 | let reg = /^(\d+?).*?(\.jpg|\.png)$/i;
69 | a.name.match(reg)
70 | let num1 = Number(RegExp.$1);
71 | b.name.match(reg)
72 | let num2 = Number(RegExp.$1);
73 | return num1 - num2;
74 | });
75 | return numTailList.concat(numHeadList, otherList);
76 | }
77 | }
--------------------------------------------------------------------------------
/src/renderer/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
28 |
--------------------------------------------------------------------------------
/src/renderer/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ string.dropDir }}
5 |
9 |
10 |
EHUNTER
11 |
12 | - {{ lang.name }}
13 |
14 |
15 |
16 |
17 |
118 |
119 |
224 |
--------------------------------------------------------------------------------
/src/renderer/assets/value/bookInstruction.js:
--------------------------------------------------------------------------------
1 | export default {
2 | cn: `
3 | 支持\`A\`. \`D\`, \`Left(左)\`, \`Right(右)\`和\`Space(空格)\`键翻页.
4 | `,
5 | en: `
6 | You can use the keyboard's \`A\`, \`D\`, \`Left\`, \`Right\` and \`Space\` keys to page.
7 | `,
8 | jp: `
9 | You can use the keyboard's \`A\`, \`D\`, \`Left\`, \`Right\` and \`Space\` keys to page.
10 | `
11 | }
12 |
--------------------------------------------------------------------------------
/src/renderer/assets/value/instruction.js:
--------------------------------------------------------------------------------
1 | export default {
2 | cn: `
3 | 1.Change language/切换语言/言語を変更
4 | 
5 |
6 | 2.显示/隐藏顶栏和关闭eHunter
7 | 
8 |
9 | 3.\`滚动\`模式下, 支持\`A\`. \`D\`, \`Left(左)\`和\`Right(右)\`键翻页.
10 |
11 | 4.\`书页\`模式下, 支持\`A\`. \`D\`, \`Left(左)\`, \`Right(右)\`和\`Space(空格)\`键翻页.
12 |
13 | 5.\`分卷页数\`对性能要求较高,请不要设置过高,可能会导致卡顿.
14 |
15 | 6.如果图片很多, 可以关闭\`缩略图\`以提升性能.
16 |
17 | 7.有更多想要的功能, 可以反馈给我, 如果该功能可以有的话, 我有空的时候会支持的.
18 |
19 | ### eHunter
20 | eHunter-local是eHunter的本地版本. eHunter本身是浏览器插件, 支持ehentai和nhentai. [项目主页](https://github.com/hanfengSan/eHunter)
21 |
22 | [chrome商店安装](https://chrome.google.com/webstore/detail/ehunter-more-powerful-e-h/dnnicnedpmjkbkdeijccbjkkcpcbmdoo) [油猴安装](https://greasyfork.org/zh-CN/scripts/39198-ehunter) [Firefox](https://addons.mozilla.org/zh-CN/firefox/addon/ehunter/) [crx/xpi安装](https://pan.baidu.com/s/1YTONumJwAAHMPpUfcyqkhg)
23 |
24 | ### 反馈和建议
25 | * 可在[Github]($$HOME_PAGE$$)上开issue给我.
26 | * 可发邮件到我邮箱: c360785655@gmail.com
27 |
28 | ### 关于
29 | * 版本: $$VERSION$$
30 | * 作者: Alex Chen (hanFeng)
31 | * 项目开源地址: [Github]($$HOME_PAGE$$)
32 | `,
33 | en: `
34 | 1.Change language/切换语言/言語を変更
35 | 
36 |
37 | 2.Show/hide top bar and close the eHunter
38 | 
39 |
40 | 3.When the \`Mode\` is \`Scroll\`, you can use the keyboard's \`A\`, \`D\`, \`Left\` and \`Right\` keys to page.
41 |
42 | 4.When the \`Mode\` is \`Book\`, you can use the keyboard's \`A\`, \`D\`, \`Left\`, \`Right\` and \`Space\` keys to page.
43 |
44 | 5.This is a high performance requirements on \`Volume size\`. If too big, the program will be slow.
45 |
46 | 6.If there are many images, you can turn off \`Thumbnail\` to improve performance.
47 |
48 | 7.If you want EHunter to support more features, you can give me feedback.
49 |
50 | ### eHunter
51 | The eHunter-local is local version of eHunter. The eHunter is browser plugin, supporting ehentai and nhentai. [Home Page](https://github.com/hanfengSan/eHunter)
52 |
53 | [Chrome Web Store](https://chrome.google.com/webstore/detail/ehunter-more-powerful-e-h/dnnicnedpmjkbkdeijccbjkkcpcbmdoo) [Tampermonkey](https://greasyfork.org/zh-CN/scripts/39198-ehunter) [Firefox](https://addons.mozilla.org/zh-CN/firefox/addon/ehunter/)
54 |
55 | ### Feedback & Suggestion
56 | * Create issue on [Github]($$HOME_PAGE$$) to me.
57 | * Send email to c360785655@gmail.com
58 |
59 | ### About
60 | * Version: $$VERSION$$
61 | * Author: Alex Chen (hanFeng)
62 | * Home page of this project: [Github]($$HOME_PAGE$$)
63 | `,
64 | jp: `
65 | 1.Change language/切换语言/言語を変更
66 | 
67 |
68 | 2.トップバーを表示/非表示にしてeHunterを閉じる
69 | 
70 |
71 | 3.When the \`Mode\` is \`Scroll\`, you can use the keyboard's \`A\`, \`D\`, \`Left\` and \`Right\` keys to page.
72 |
73 | 4.When the \`Mode\` is \`Book\`, you can use the keyboard's \`A\`, \`D\`, \`Left\`, \`Right\` and \`Space\` keys to page.
74 |
75 | 5.これは\`ボリュームサイズ\`の高性能要件です。 大きすぎるとプログラムが遅くなります。
76 |
77 | 6.画像が多い場合は、パフォーマンスを向上させるために "サムネイル"をオフにすることができます。
78 |
79 | 7.あなたがEHunterにもっと多くの機能をサポートさせたいならば、あなたは私にフィードバックを与えることができます。
80 |
81 | ### eHunter
82 | eHunter-localはeHunterのローカル版です。eHunterは、ehentaiとnhentaiをサポートするブラウザプラグインです。 [Home Page](https://github.com/hanfengSan/eHunter)
83 |
84 | [Chrome Web Store](https://chrome.google.com/webstore/detail/ehunter-more-powerful-e-h/dnnicnedpmjkbkdeijccbjkkcpcbmdoo) [Tampermonkey](https://greasyfork.org/zh-CN/scripts/39198-ehunter) [Firefox](https://addons.mozilla.org/zh-CN/firefox/addon/ehunter/)
85 |
86 | ### フィードバックと提案
87 | * 私にGITHUBのオープンな問題 [Github]($$HOME_PAGE$$)
88 | * c360785655@gmail.comにメールを送信する
89 |
90 | ### 〜について
91 | * バージョン: $$VERSION$$
92 | * 著者: Alex Chen (hanFeng)
93 | * このプロジェクトのホームページ: [Github]($$HOME_PAGE$$)
94 | `
95 | }
96 |
--------------------------------------------------------------------------------
/src/renderer/assets/value/string.js:
--------------------------------------------------------------------------------
1 | export default {
2 | lang: {
3 | cn: 'CN',
4 | en: 'EN',
5 | jp: 'JP'
6 | },
7 | dropDir: {
8 | cn: '拖拽文件夹到此处',
9 | en: 'Drop folder to here',
10 | jp: 'ここにフォルダをドロップ'
11 | },
12 | openDir: {
13 | cn: '选择文件夹',
14 | en: 'OPEN FOLDER',
15 | jp: '開いたフォルダ'
16 | },
17 | noDir: {
18 | cn: '不是文件夹',
19 | en: 'It isn\'t folder',
20 | jp: 'フォルダではありません'
21 | },
22 | noImg: {
23 | cn: '文件夹中不存在图片(*.jpg, *.png)',
24 | en: 'No image exists in the folder(*.jpg, *.png)',
25 | jp: 'フォルダに画像が存在しません'
26 | },
27 | error: {
28 | cn: '错误',
29 | en: 'Error',
30 | jp: 'エラー'
31 | },
32 | readingMode: {
33 | cn: '阅读模式',
34 | en: 'Mode',
35 | jp: 'モード'
36 | },
37 | readingModeTip: {
38 | cn: '设置阅读模式',
39 | en: 'Change reading mode',
40 | jp: '読むモードを変更する'
41 | },
42 | scrollMode: {
43 | cn: '滚动',
44 | en: 'Scroll',
45 | jp: 'スクロール'
46 | },
47 | bookMode: {
48 | cn: '书页',
49 | en: 'Book',
50 | jp: 'ページ'
51 | },
52 | widthScale: {
53 | cn: '页面比例',
54 | en: 'Page scale',
55 | jp: 'ページの割合'
56 | },
57 | widthScaleTip: {
58 | cn: '设置页面比例',
59 | en: 'Change page scale',
60 | jp: 'ページの割合を設定'
61 | },
62 | custom: {
63 | cn: '自定义',
64 | en: 'Custom',
65 | jp: 'カスタム'
66 | },
67 | loadNum: {
68 | cn: '加载页数',
69 | en: 'Loading quantity',
70 | jp: '積載量'
71 | },
72 | loadNumTip: {
73 | cn: '越大则内存占用越高',
74 | en: 'The greater quantity, the higher usage of Memory',
75 | jp: '量が多いほど、メモリの使用量は多くなります'
76 | },
77 | volSize: {
78 | cn: '分卷页数',
79 | en: 'Volume size',
80 | jp: 'ボリュームサイズ'
81 | },
82 | volSizeTip: {
83 | cn: '设置过大会导致卡顿',
84 | en: 'If too big, the program will be slow',
85 | jp: '大きすぎると、プログラムは遅くなります'
86 | },
87 | thumbView: {
88 | cn: '缩略图栏',
89 | en: 'Thumbnail',
90 | jp: 'サムネイル'
91 | },
92 | thumbViewTip: {
93 | cn: '开启/关闭缩略图栏',
94 | en: 'Show/hide the column of thumbnail',
95 | jp: 'サムネイルの列を表示または非表示'
96 | },
97 | screenSize: {
98 | cn: '同屏页数',
99 | en: 'Pages/screen',
100 | jp: 'ページ/画面'
101 | },
102 | screenSizeTip: {
103 | cn: '一个屏幕下的页数',
104 | en: 'The number of pages on the screen',
105 | jp: '画面上のページ数'
106 | },
107 | bookDirection: {
108 | cn: '阅读方向',
109 | en: 'Direction',
110 | jp: '読み取り方向'
111 | },
112 | bookDirectionTip: {
113 | cn: '阅读方向',
114 | en: 'Reading direction',
115 | jp: '読み取り方向'
116 | },
117 | rtl: {
118 | cn: 'RTL (从右到左)',
119 | en: 'RTL (Right To Left)',
120 | jp: 'RTL (右から左に)'
121 | },
122 | ltr: {
123 | cn: 'LTR (从左到右)',
124 | en: 'LTR (Left to Right)',
125 | jp: 'LTR (左から右へ)'
126 | },
127 | pagination: {
128 | cn: '页目录',
129 | en: 'Pagination',
130 | jp: 'ページネーション'
131 | },
132 | paginationTip: {
133 | cn: '显示/隐藏底部悬浮页目录',
134 | en: 'Show/hide the bottom floating pagination',
135 | jp: 'ボトムフローティングページネーションの表示/非表示'
136 | },
137 | bookAnimation: {
138 | cn: '换页动画',
139 | en: 'Sliding animation',
140 | jp: 'アニメーション'
141 | },
142 | bookAnimationTip: {
143 | cn: '开启/关闭换页时的滑动动画(测试中)',
144 | en: 'show/hide the sliding animation when changing location(Beta)',
145 | jp: '場所を変更するときのスライドアニメーションの表示/非表示(测试中)'
146 | },
147 | reverseFlip: {
148 | cn: '反转翻页',
149 | en: 'Reverse flip',
150 | jp: '反転フリップ'
151 | },
152 | reverseFlipTip: {
153 | cn: '反转翻页方向',
154 | en: 'Reverse page turning direction',
155 | jp: 'ページめくり方向を逆にする'
156 | },
157 | autoFlip: {
158 | cn: '自动翻页',
159 | en: 'Auto',
160 | jp: '自動ページめくり'
161 | },
162 | autoFlipTip: {
163 | cn: '自动翻页',
164 | en: 'Automatic page turning',
165 | jp: '自動ページめくり'
166 | },
167 | autoFlipFrequency: {
168 | cn: '翻页频率',
169 | en: 'Frequency',
170 | jp: '頻度'
171 | },
172 | autoFlipFrequencyTip: {
173 | cn: '自动翻页的频率',
174 | en: 'Automatic page turning frequency',
175 | jp: '自動ページめくり頻度'
176 | },
177 | refresh: {
178 | cn: '刷新',
179 | en: 'Refresh',
180 | jp: 'リフレッシュ'
181 | },
182 | refreshTip: {
183 | cn: '再次获取普通图片',
184 | en: 'Refresh to load normal image',
185 | jp: 'リフレッシュ; 通常の画像を読み込みます'
186 | },
187 | originImg: {
188 | cn: '原图',
189 | en: 'Original',
190 | jp: '元画像'
191 | },
192 | originImgTip: {
193 | cn: '加载原图',
194 | en: 'Load original image',
195 | jp: '元画像を読み込む'
196 | },
197 | refreshByOtherSource: {
198 | cn: '换源刷新',
199 | en: 'Other source',
200 | jp: '他のサーバー'
201 | },
202 | refreshByOtherSourceTip: {
203 | cn: '从其他服务器获取普通图片',
204 | en: 'Load normal image from other server',
205 | jp: '他のサーバーから通常の画像を取得する'
206 | },
207 | loadingImg: {
208 | cn: '加载图片中...',
209 | en: 'Loading image...',
210 | jp: '画像を読み込む..'
211 | },
212 | loadingImgUrl: {
213 | cn: '加载图片地址中...',
214 | en: 'Loading image url..',
215 | jp: '画像URLを読み込む..'
216 | },
217 | reload: {
218 | cn: '重载',
219 | en: 'Reload',
220 | jp: 'リロード'
221 | },
222 | loadingImgFailed: {
223 | cn: '加载图片失败, 请刷新',
224 | en: 'Loading failed, please refresh',
225 | jp: '読み込みに失敗しました。更新してください'
226 | },
227 | noOriginalImg: {
228 | cn: '无原图, 请刷新',
229 | en: 'No original Image, please refresh',
230 | jp: 'オリジナルイメージはありません。リフレッシュしてください'
231 | },
232 | loadingFailed: {
233 | cn: '加载错误',
234 | en: 'Loading failed',
235 | jp: '読み込み失敗'
236 | },
237 | imgLoaded: {
238 | cn: '图片加载完成',
239 | en: 'Image loaded',
240 | jp: '画像が読み込まれた'
241 | },
242 | waiting: {
243 | cn: '等待中..',
244 | en: 'Waiting..',
245 | jp: '待っている..'
246 | },
247 | fullScreen: {
248 | cn: '全屏',
249 | en: 'Full screen',
250 | jp: '全画面表示'
251 | },
252 | closeEHunter: {
253 | cn: '返回 [Q]',
254 | en: 'Return [Q]',
255 | jp: '返す [Q]'
256 | },
257 | toggleTopBar: {
258 | cn: '显示/隐藏顶栏 [Esc]',
259 | en: 'Show/hide top bar [Esc]',
260 | jp: 'トップバーの表示/非表示 [Esc]'
261 | },
262 | toggleMoreSettings: {
263 | cn: '显示/隐藏更多设置 [Shift]',
264 | en: 'Show/hide more settings [Shift]',
265 | jp: '他の設定を表示/隠す [Shift]'
266 | },
267 | confirm: {
268 | cn: '确定',
269 | en: 'CONFIRM',
270 | jp: '確認'
271 | },
272 | cancel: {
273 | cn: '取消',
274 | en: 'CANCEL',
275 | jp: '取り消し'
276 | },
277 | infoTip: {
278 | cn: '查看说明和关于',
279 | en: 'Look the Instructions and About',
280 | jp: '指示と情報を見てください'
281 | },
282 | resetTip: {
283 | cn: '重置缓存和数据',
284 | en: 'Reset cache and data',
285 | jp: 'Reset cache and data'
286 | },
287 | githubTip: {
288 | cn: '前往项目主页(Github)',
289 | en: 'Go to the project home page(Github)',
290 | jp: 'プロジェクトのホームページに行く(Github)'
291 | },
292 | instructionsAndAbouts: {
293 | cn: '说明和关于',
294 | en: 'Instructions & About',
295 | jp: '説明と概要'
296 | },
297 | instructions: {
298 | cn: '说明',
299 | en: 'Instructions',
300 | jp: '説明'
301 | },
302 | later: {
303 | cn: '以后再说',
304 | en: 'LATER',
305 | jp: '後で'
306 | },
307 | changingToSmallFailed: {
308 | cn: '无缝切换至`"Normal"`模式失败,可能是网络错误,可刷新重试或者返回前一页将预览图的大小模式切换为`"Normal"`。',
309 | en: 'Changing to `"Normal"` mode failed, because of poor network. You can reload this page or go back to previous page and change the mode of thumbnails to `"Normal"`',
310 | jp: 'ネットワークが不十分であるため、`「Normal」`モードに変更できませんでした。 このページをリロードするか、前のページに戻ってサムネイルのモードを`「Normal」`に変更することができます'
311 | },
312 | loadingTip: {
313 | cn: '在前页采用Normal模式查看缩略图可加速加载',
314 | en: 'You can use "Normal" mode of thumbnail in previous page to accelerate the load.',
315 | jp: '前のページでサムネイルの「Normal」モードを使用して、読み込みを高速化できます。'
316 | },
317 | versionUpdate: {
318 | cn: '版本更新说明',
319 | en: 'The update of this version',
320 | jp: 'このバージョンの更新'
321 | },
322 | loadingFailedAndRefresh: {
323 | cn: '加载错误, 刷新重试',
324 | en: 'Loading failed, please refresh to retry',
325 | jp: '読み込みに失敗しました。もう一度試してください'
326 | },
327 | failedMsg: {
328 | cn: '错误信息',
329 | en: 'Error message',
330 | jp: 'エラーメッセージ'
331 | },
332 | version: {
333 | cn: '版本',
334 | en: 'Version',
335 | jp: 'Version'
336 | },
337 | ContractAuthor: {
338 | cn: '联系作者',
339 | en: 'Contact author',
340 | jp: '作者に連絡する'
341 | },
342 | wheelSensitivity: {
343 | cn: '滚轮翻页',
344 | en: 'Wheel flip',
345 | jp: 'ホイール'
346 | },
347 | wheelSensitivityTip: {
348 | cn: '鼠标滚轮翻页灵敏度',
349 | en: 'Wheel sensitivity',
350 | jp: 'ホイール感度'
351 | },
352 | wheelDirection: {
353 | cn: '滚轮方向',
354 | en: 'Wheel Direction',
355 | jp: 'ホイール方向'
356 | },
357 | wheelDirectionTip: {
358 | cn: '反转滚轮翻页方向',
359 | en: 'Reverse Wheel Direction to flip',
360 | jp: 'リバースホイール方向'
361 | },
362 | tips: {
363 | cn: '提示',
364 | en: 'TIPS',
365 | jp: 'ヒント'
366 | },
367 | numberInputTip: {
368 | cn: '最小值为`{{min}}`, 最大值为`{{max}}`',
369 | en: 'The minimum is `{{min}}` and the maximum is `{{max}}`',
370 | jp: '最小は`{{min}}`, 最大は`{{max}}`です'
371 | },
372 | pageMargin: {
373 | cn: '页间隔',
374 | en: 'Page spacing',
375 | jp: 'ページ間隔'
376 | },
377 | pageMarginTip: {
378 | cn: '页间隔',
379 | en: 'Page spacing',
380 | jp: 'ページ間隔'
381 | },
382 | oddEven: {
383 | cn: '奇偶切换',
384 | en: 'Odd/Even',
385 | jp: '奇/偶'
386 | },
387 | oddEvenTip: {
388 | cn: '切换奇偶页拼接',
389 | en: 'Switching odd or even page stitching',
390 | jp: '奇数または偶数ページステッチの切り替え'
391 | }
392 | }
393 |
--------------------------------------------------------------------------------
/src/renderer/assets/value/tags.js:
--------------------------------------------------------------------------------
1 | export const SCROLL_VIEW = 'SCROLL_VIEW';
2 | export const SCROLL_VIEW_VOL = 'SCROLL_VIEW_VOL';
3 | export const BOOK_VIEW = 'BOOK_VIEW';
4 | export const THUMB_VIEW = 'THUMB_VIEW';
5 | export const READER_VIEW = 'READER_VIEW';
6 | export const TOP_BAR = 'TOP_BAR';
7 |
8 | export const MODE_FAST = 'MODE_FAST';
9 | export const MODE_ORIGIN = 'MODE_ORIGIN';
10 | export const MODE_CHANGE_SOURCE = 'MODE_CHANGE_SOURCE';
11 |
12 | export const ERROR_NO_ORIGIN = 'ERROR_NO_ORIGIN';
13 |
14 | export const ID_START = 'ID_START';
15 | export const ID_END = 'ID_END';
16 | export const TYPE_NORMAL = 'TYPE_NORMAL';
17 | export const TYPE_START = 'TYPE_START';
18 | export const TYPE_END = 'TYPE_END';
19 |
20 | export const LANG_EN = 'LANG_EN';
21 | export const LANG_CN = 'LANG_CN';
22 | export const LANG_JP = 'LANG_JP';
23 |
24 | export const STATE_WAITING = 'STATE_WAITING';
25 | export const STATE_LOADING = 'STATE_LOADING';
26 | export const STATE_ERROR = 'STATE_ERROR';
27 | export const STATE_LOADED = 'STATE_LOADED';
28 |
29 | export const DIALOG_NORMAL = 'DIALOG_NORMAL';
30 | export const DIALOG_COMPULSIVE = 'DIALOG_COMPULSIVE';
31 | export const DIALOG_OPERATION_TYPE_PLAIN = 'DIALOG_OPERATION_PLAIN';
32 | export const DIALOG_OPERATION_TYPE_NEGATIVE = 'DIALOG_OPERATION_TYPE_NEGATIVE';
33 | export const DIALOG_OPERATION_TYPE_POSITIVE = 'DIALOG_OPERATION_TYPE_POSITIVE';
34 | export const DIALOG_OPERATION_TYPE_WARNING = 'DIALOG_OPERATION_TYPE_WARNING';
35 |
36 | export const TYPE_PROXY = 'TYPE_PROXY';
37 |
38 | export const KEYBOARD = 'KEYBOARD';
39 |
--------------------------------------------------------------------------------
/src/renderer/assets/value/version.js:
--------------------------------------------------------------------------------
1 | export default {
2 | cn: `
3 | * \`书页模式\`下支持奇偶页切换
4 | * 支持回车关闭弹窗
5 | * 迁移老的订阅数据
6 | * \`滚动模式\`下支持调整页间距
7 | * 支持手动修改配置值
8 | * 在\`书页模式\`下, 支持鼠标滚轮(触控板)翻页.
9 | * 添加\`滚轮方向\`设置, 用于设置鼠标滚动翻页方向.
10 | `,
11 | en: `
12 | * In \`Book\` mode, you can switch the \`Odd/Even\` to see the big image that crossing screens.
13 | * Support using \`Enter\` key to close the dialog.
14 | * In \`Scroll\` mode, you can change the space between pages.
15 | * Support manually change the value of the configuration.
16 | * Support mouse wheel to flip pages in \`Book\` mode.
17 | * Add \`Wheel Direction\` for customizing the direction of flipping.
18 | `,
19 | jp: `
20 | * In \`Book\` mode, you can switch the \`Odd/Even\` to see the big image that crossing screens.
21 | * Support using \`Enter\` key to close the dialog.
22 | * In \`Scroll\` mode, you can change the space between pages.
23 | * Support manually change the value of the configuration.
24 | * Support mouse wheel to flip pages in \`Book\` mode.
25 | * Add \`Wheel Direction\` for customizing the direction of flipping.
26 | `
27 | }
28 |
--------------------------------------------------------------------------------
/src/renderer/bean/DialogBean.ts:
--------------------------------------------------------------------------------
1 | import { DialogOperation } from './DialogOperation'
2 |
3 | export default class DialogBean {
4 | readonly id: number;
5 | type: string;
6 | title: string;
7 | text: string;
8 | operations: Array;
9 | constructor(type: string, title: string, text: string, ...operations: Array) {
10 | this.id = new Date().getTime();
11 | this.type = type;
12 | this.title = title;
13 | this.text = text;
14 | this.operations = operations;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/renderer/bean/DialogOperation.ts:
--------------------------------------------------------------------------------
1 | export interface DOClick { (): void };
2 | export class DialogOperation {
3 | name: string;
4 | type: string;
5 | onClick: DOClick;
6 | constructor(name: string, type: string, onClick: DOClick) {
7 | this.name = name;
8 | this.type = type;
9 | this.onClick = onClick;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/renderer/bean/ImgPageInfo.ts:
--------------------------------------------------------------------------------
1 | export interface ImgPageInfo {
2 | id: number | string,
3 | index: number,
4 | pageUrl: string,
5 | src: string,
6 | heightOfWidth: number,
7 | thumbHeight?: number,
8 | thumbWidth?: number
9 | }
--------------------------------------------------------------------------------
/src/renderer/bean/ServerMessage.ts:
--------------------------------------------------------------------------------
1 | import store from '../store/index'
2 |
3 | interface MsgOperation {
4 | name: string;
5 | url: string;
6 | }
7 |
8 | interface UpdateMsg {
9 | title: string;
10 | version: string;
11 | text: string;
12 | operations: Array;
13 | time: number;
14 | always: boolean;
15 | duration: number;
16 | }
17 |
18 | interface I18nUpdateMsg {
19 | cn: UpdateMsg;
20 | en: UpdateMsg;
21 | jp: UpdateMsg;
22 | }
23 |
24 | export default class ServerMessage {
25 | title: string;
26 | version: string;
27 | text: string;
28 | operations: Array;
29 | time: number;
30 | always: boolean;
31 | duration: number;
32 |
33 | constructor(data: I18nUpdateMsg) {
34 | let message;
35 | switch (store.getters.string.lang) {
36 | case 'CN':
37 | message = data.cn;
38 | break;
39 | case 'JP':
40 | message = data.jp;
41 | break;
42 | case 'EN':
43 | default:
44 | message = data.en;
45 | }
46 | this.title = message.title;
47 | this.version = message.version;
48 | this.text = message.text;
49 | this.operations = message.operations;
50 | this.time = message.time;
51 | this.always = message.always;
52 | this.duration = message.duration;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/renderer/bean/ThumbInfo.ts:
--------------------------------------------------------------------------------
1 | export enum ThumbMode {
2 | SPIRIT = 0,
3 | IMG
4 | }
5 |
6 | export interface ThumbInfo {
7 | id: string | number,
8 | src: string;
9 | mode: ThumbMode,
10 | offset?: number;
11 | }
--------------------------------------------------------------------------------
/src/renderer/components/LoadingView.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
32 |
33 |
--------------------------------------------------------------------------------
/src/renderer/components/ModalManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
43 |
44 |
--------------------------------------------------------------------------------
/src/renderer/components/PageView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ index + 1 }}
6 |
7 |
8 | {}">
9 | {{ loadingInfo }}
10 |
11 |
19 |
20 |
27 |
28 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
![]()
48 |
49 |
50 |
51 |
52 |
197 |
198 |
--------------------------------------------------------------------------------
/src/renderer/components/ReaderView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Loading...
6 |
7 | {{ string .loadingTip }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
{{ location }}
15 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
42 |
43 |
44 |
45 |
46 |
47 |
119 |
120 |
--------------------------------------------------------------------------------
/src/renderer/components/ThumbScrollView.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
122 |
123 |
252 |
--------------------------------------------------------------------------------
/src/renderer/components/base/AwesomeScrollView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
99 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/CircleIconButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
40 |
41 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/DropOption.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ curVal }}
4 |
8 |
9 |
10 |
11 | {{ item.name || item }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
53 |
54 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/FlatButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
38 |
39 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
97 |
98 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/PopSlider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
97 |
98 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/Popover.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
55 |
56 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/SimpleDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ data.title }}
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
87 |
88 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/SimpleSwitch.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
34 |
35 |
--------------------------------------------------------------------------------
/src/renderer/components/widget/Slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
104 |
105 |
--------------------------------------------------------------------------------
/src/renderer/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import axios from 'axios'
3 | import VueUtil from './utils/VueUtil'
4 |
5 | import App from './App'
6 | import router from './router'
7 | import store from './store'
8 |
9 | if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
10 | Vue.http = Vue.prototype.$http = axios
11 | Vue.config.productionTip = false
12 | Vue.mixin(VueUtil)
13 |
14 | /* eslint-disable no-new */
15 | new Vue({
16 | components: { App },
17 | router,
18 | store,
19 | template: ''
20 | }).$mount('#app')
21 |
--------------------------------------------------------------------------------
/src/renderer/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from '../Home'
4 | import CoreApp from '../CoreApp'
5 |
6 | Vue.use(Router)
7 |
8 | export default new Router({
9 | routes: [{
10 | path: '/',
11 | name: 'Home',
12 | component: Home
13 | },
14 | {
15 | path: '*',
16 | redirect: '/'
17 | },
18 | {
19 | path: '/CoreApp',
20 | name: 'CoreApp',
21 | component: CoreApp
22 | }
23 | ]
24 | })
--------------------------------------------------------------------------------
/src/renderer/service/AlbumService.ts:
--------------------------------------------------------------------------------
1 | import { ImgPageInfo } from "../bean/ImgPageInfo";
2 | import { ThumbInfo } from "../bean/ThumbInfo";
3 |
4 | export interface PreviewThumbnailStyle {
5 | 'background-image': string;
6 | 'background-position': string;
7 | 'background-size': string;
8 | }
9 |
10 | export interface IndexInfo {
11 | val: number;
12 | updater: string;
13 | }
14 |
15 | export abstract class AlbumService {
16 | abstract async getPageCount(): Promise;
17 | abstract async getCurPageNum(): Promise;
18 | abstract async getTitle(): Promise;
19 | abstract async getImgPageInfos(): Promise>;
20 | abstract async getImgPageInfo(index: number): Promise;
21 | abstract async getImgSrc(index: number, mode): Promise;
22 | abstract async getNewImgSrc(index: number, mode): Promise;
23 | abstract async getThumbInfos(noCache?: boolean): Promise>;
24 | abstract async getThumbInfo(index: number): Promise;
25 | abstract async getAlbumId(): Promise;
26 | abstract async getPreviewThumbnailStyle(index: number, imgPageInfo: ImgPageInfo, thumbInfo: ThumbInfo): Promise;
27 | abstract supportOriginImg(): boolean;
28 | abstract supportImgChangeSource(): boolean;
29 | abstract supportThumbView(): boolean;
30 |
31 | getBookScreenCount(pageCount: number, screenSize: number): number {
32 | // 2 is start page and end page
33 | return Math.ceil((pageCount + 2) / screenSize);
34 | }
35 |
36 | getRealCurIndexInfo(pageCount: number, curIndex: IndexInfo): IndexInfo {
37 | let index = curIndex.val;
38 | index = index >= pageCount ? pageCount - 1 : index;
39 | return { val: index, updater: curIndex.updater };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/renderer/service/InfoService.ts:
--------------------------------------------------------------------------------
1 | import store from '../store'
2 | import DialogBean from '../bean/DialogBean'
3 | import { DialogOperation, DOClick } from '../bean/DialogOperation'
4 | import * as tags from '../assets/value/tags.js'
5 | import { TextReq } from '../service/request/TextReq'
6 | import ServerMessage from '../bean/ServerMessage'
7 | import SettingService from '../service/SettingService'
8 | import Logger from '../utils/Logger'
9 | import Formatter from '../utils/formatter'
10 |
11 | class InfoService {
12 | async showInstruction(config, isCompulsive) {
13 | let dialog = new DialogBean(
14 | isCompulsive ? tags.DIALOG_COMPULSIVE : tags.DIALOG_NORMAL,
15 | store.getters.string.instructionsAndAbouts,
16 | Formatter.replaceKey(store.getters.string.p_instruction, {
17 | HOME_PAGE: config.homePage,
18 | VERSION: config.version
19 | }),
20 | new DialogOperation(store.getters.string.confirm, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
21 | return true;
22 | })
23 | )
24 | store.dispatch('addDialog', dialog);
25 | }
26 |
27 | async showBookInstruction(isCompulsive): Promise {
28 | let dialog = new DialogBean(
29 | isCompulsive ? tags.DIALOG_COMPULSIVE : tags.DIALOG_NORMAL,
30 | store.getters.string.instructions,
31 | store.getters.string.p_bookInstruction,
32 | new DialogOperation(store.getters.string.confirm, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
33 | return true;
34 | })
35 | );
36 | store.dispatch('addDialog', dialog);
37 | }
38 |
39 | async checkUpdate(config): Promise {
40 | let message;
41 | let lastShowDialogTime = await SettingService.getUpdateTime();
42 | Promise
43 | .race([
44 | new TextReq(config.updateServer1, true, false).request(),
45 | new TextReq(config.updateServer2, true, false).request()
46 | ])
47 | .then(data => {
48 | message = new ServerMessage(JSON.parse(data));
49 | let isNewVersion = message.version !== config.version;
50 | let isReleaseTime = new Date().getTime() > message.time;
51 | let isOverDuration = (new Date().getTime() - lastShowDialogTime) > message.duration;
52 | if (isNewVersion && isReleaseTime && isOverDuration) {
53 | SettingService.setUpdateTime(new Date().getTime());
54 | this.showUpdateInfo(message);
55 | }
56 | })
57 | .catch(e => {
58 | Logger.logObj('InfoService', e);
59 | });
60 | }
61 |
62 | showUpdateInfo(message): void {
63 | let operations: Array = [];
64 | operations.push(new DialogOperation(store.getters.string.later, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
65 | return true;
66 | }));
67 | message.operations.forEach(i => {
68 | operations.push(new DialogOperation(i.name, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
69 | window.open(i.url, '_blank');
70 | return true;
71 | }));
72 | });
73 | let dialog = new DialogBean(
74 | tags.DIALOG_COMPULSIVE,
75 | message.title,
76 | message.text,
77 | ...operations
78 | );
79 | store.dispatch('addDialog', dialog);
80 | }
81 |
82 | showReloadError(text): void {
83 | let dialog = new DialogBean(
84 | tags.DIALOG_COMPULSIVE,
85 | store.getters.string.loadingFailed,
86 | text,
87 | new DialogOperation(store.getters.string.reload, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
88 | window.location.reload();
89 | return true;
90 | })
91 | );
92 | store.dispatch('addDialog', dialog);
93 | }
94 |
95 | // if updated a new version, shows messages
96 | async checkNewVersion(config): Promise {
97 | if (await SettingService.getVersion() !== config.version && config.version !== '1.0.0') {
98 | let dialog = new DialogBean(
99 | tags.DIALOG_COMPULSIVE,
100 | `${store.getters.string.versionUpdate} v${config.version}`,
101 | store.getters.string.p_version,
102 | new DialogOperation(store.getters.string.confirm, tags.DIALOG_OPERATION_TYPE_PLAIN, () => {
103 | SettingService.setVersion(config.version);
104 | return true;
105 | })
106 | );
107 | store.dispatch('addDialog', dialog);
108 | }
109 | }
110 | }
111 |
112 | let instance = new InfoService();
113 | export default instance;
114 |
--------------------------------------------------------------------------------
/src/renderer/service/PlatformService.js:
--------------------------------------------------------------------------------
1 | // a service for crossing platform
2 | /* eslint-disable no-undef */
3 |
4 | // hack for test
5 | if (typeof chrome === 'undefined') {
6 | var chrome = { extension: null };
7 | }
8 |
9 | export default {
10 | storage: {
11 | get sync() {
12 | if (chrome && chrome.storage) {
13 | return chrome.storage.sync.QUOTA_BYTES ? chrome.storage.sync : chrome.storage.local;
14 | } else {
15 | return window.localStorage;
16 | }
17 | },
18 | local: window.localStorage
19 | },
20 | getExtension() {
21 | return chrome.extension;
22 | },
23 | fetch(url, option) {
24 | /* eslint-disable camelcase */
25 | if (typeof GM_info !== 'undefined' && GM_info.version) { // the ENV is Tampermonkey
26 | return new Promise((resolve, reject) => {
27 | GM_xmlhttpRequest({
28 | method: option.method,
29 | url,
30 | onload: x => {
31 | let responseText = x.responseText;
32 | x.text = async function() {
33 | return responseText;
34 | }
35 | resolve(x);
36 | },
37 | onerror: e => {
38 | reject(`GM_xhr error, ${e.status}`);
39 | }
40 | });
41 | });
42 | } else { // the ENV is Chrome or Firefox
43 | return window.fetch(url, option);
44 | }
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/src/renderer/service/StringService.ts:
--------------------------------------------------------------------------------
1 | import string from '../assets/value/string'
2 | import instruction from '../assets/value/instruction'
3 | import bookInstruction from '../assets/value/bookInstruction'
4 | import version from '../assets/value/version'
5 |
6 | class StringService {
7 | cn = {};
8 | en = {};
9 | jp = {};
10 |
11 | constructor() {
12 | this.initString();
13 | }
14 |
15 | initString() {
16 | for (let key in string) {
17 | this.cn[key] = string[key].cn;
18 | this.en[key] = string[key].en;
19 | this.jp[key] = string[key].jp;
20 | }
21 | this.cn['p_instruction'] = instruction.cn;
22 | this.en['p_instruction'] = instruction.en;
23 | this.jp['p_instruction'] = instruction.jp;
24 | this.cn['p_bookInstruction'] = bookInstruction.cn;
25 | this.en['p_bookInstruction'] = bookInstruction.en;
26 | this.jp['p_bookInstruction'] = bookInstruction.jp;
27 | this.cn['p_version'] = version.cn;
28 | this.en['p_version'] = version.en;
29 | this.jp['p_version'] = version.jp;
30 | }
31 | }
32 |
33 | let instance = new StringService();
34 | export default instance;
35 |
--------------------------------------------------------------------------------
/src/renderer/service/request/MultiAsyncReq.ts:
--------------------------------------------------------------------------------
1 | // a service for sync multi asynchronous text requests
2 | import { TextReq } from './TextReq'
3 |
4 | export class MultiAsyncReq {
5 | private urls: Array = [];
6 | private resultMap: Map = new Map();
7 | private fetchSetting = null;
8 | private gen;
9 |
10 | constructor(urls) {
11 | this.urls = urls;
12 | this.fetchSetting = null;
13 | }
14 |
15 | request(): Promise