First beta version

This commit is contained in:
Molunerfinn
2017-11-28 08:21:12 +08:00
commit d1b9e0f592
54 changed files with 10526 additions and 0 deletions

23
src/datastore/index.js Normal file
View File

@@ -0,0 +1,23 @@
import Datastore from 'lowdb'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import { remote, app } from 'electron'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
const db = Datastore(adapter)
if (!db.has('uploaded').value()) {
db.set('uploaded', []).write()
}
if (!db.has('picBed').value()) {
db.set('picBed', {
current: 'weibo'
}).write()
}
export default db

22
src/index.ejs Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PicGo</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push("<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>")
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
</script>
<!-- webpack builds are automatically injected -->
</body>
</html>

27
src/main/index.dev.js Normal file
View File

@@ -0,0 +1,27 @@
/**
* This file is used specifically and only for development. It installs
* `electron-debug` & `vue-devtools`. There shouldn't be any need to
* modify this file, but it can be used to extend your development
* environment.
*/
/* eslint-disable */
// Set environment for development
process.env.NODE_ENV = 'development'
// Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: false })
// Install `vue-devtools`
require('electron').app.on('ready', () => {
let installExtension = require('electron-devtools-installer')
installExtension.default(installExtension.VUEJS_DEVTOOLS)
.then(() => {})
.catch(err => {
console.log('Unable to install `vue-devtools`: \n', err)
})
})
// Require `main` process to boot app
require('./index')

230
src/main/index.js Normal file
View File

@@ -0,0 +1,230 @@
'use strict'
import { weiboUpload } from './utils/weiboUpload.js'
import { app, BrowserWindow, Tray, Menu, Notification, clipboard, ipcMain } from 'electron'
import db from '../datastore/index'
/**
* Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/
if (process.env.NODE_ENV !== 'development') {
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
}
let window
let settingWindow
let tray
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#setting/upload`
: `file://${__dirname}/index.html#setting/upload`
function createTray () {
tray = new Tray(`${__static}/menubarDefaultTemplate.png`)
const contextMenu = Menu.buildFromTemplate([
{
role: 'quit',
label: 'Quit'
},
{
label: 'Open setting Window',
click () {
if (settingWindow === null) {
createSettingWindow()
} else {
settingWindow.show()
settingWindow.focus()
}
}
}
])
tray.on('right-click', () => {
window.hide()
tray.popUpContextMenu(contextMenu)
})
tray.on('click', () => {
let img = clipboard.readImage()
let obj = []
if (!img.isEmpty()) {
obj.push({
width: img.getSize().width,
height: img.getSize().height,
imgUrl: img.toDataURL()
})
}
toggleWindow()
setTimeout(() => {
window.webContents.send('clipboardFiles', obj)
}, 0)
})
tray.on('drag-enter', () => {
tray.setImage(`${__static}/upload.png`)
})
tray.on('drag-end', () => {
tray.setImage(`${__static}/menubarDefaultTemplate.png`)
})
tray.on('drop-files', async (event, files) => {
const imgs = await weiboUpload(files, 'imgFromPath')
for (let i in imgs) {
clipboard.writeText(imgs[i].imgUrl)
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl,
icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
}
console.log('drag-files')
window.webContents.send('dragFiles', imgs)
})
toggleWindow()
}
const createWindow = () => {
window = new BrowserWindow({
height: 350,
width: 196, // 196
show: false,
frame: false,
fullscreenable: false,
resizable: false,
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
backgroundThrottling: false
}
})
window.loadURL(winURL)
window.on('closed', () => {
window = null
})
window.on('blur', () => {
window.hide()
})
createSettingWindow()
}
const createSettingWindow = () => {
settingWindow = new BrowserWindow({
height: 450,
width: 800,
show: true,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
title: 'Pic',
vibrancy: 'ultra-dark',
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false
}
})
settingWindow.loadURL(settingWinURL)
settingWindow.on('closed', () => {
settingWindow = null
})
}
const getWindowPosition = () => {
const windowBounds = window.getBounds()
const trayBounds = tray.getBounds()
const x = Math.round(trayBounds.x + (trayBounds.width / 2) - (windowBounds.width / 2))
const y = Math.round(trayBounds.y + trayBounds.height - 10)
return {
x,
y
}
}
const toggleWindow = () => {
if (window.isVisible()) {
window.hide()
} else {
showWindow()
}
}
// const toggleSettingWindow = () => {
// if (settingWindow.isVisible()) {
// settingWindow.hide()
// } else {
// settingWindow.show()
// settingWindow.focus()
// }
// }
const showWindow = () => {
const position = getWindowPosition()
window.setPosition(position.x, position.y, false)
window.show()
window.focus()
}
ipcMain.on('uploadClipboardFiles', async (evt, file) => {
const img = await weiboUpload(file, 'imgFromClipboard')
clipboard.writeText(img[0].imgUrl)
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl,
icon: file[0]
})
notification.show()
clipboard.clear()
window.webContents.send('clipboardFiles', [])
window.webContents.send('uploadClipboardFiles', img)
console.log('clipboard-upload')
})
app.on('ready', () => {
createWindow()
createTray()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (window === null || settingWindow === null) {
createWindow()
createTray()
}
})
/**
* Auto Updater
*
* Uncomment the following code below and install `electron-updater` to
* support auto updating. Code Signing with a valid certificate is required.
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
*/
// import { autoUpdater } from 'electron-updater'
// autoUpdater.on('update-downloaded', () => {
// autoUpdater.quitAndInstall()
// })
// app.on('ready', () => {
// if (process.env.NODE_ENV === 'production') {
// autoUpdater.checkForUpdates()
// }
// })

View File

@@ -0,0 +1,35 @@
import fs from 'fs-extra'
import path from 'path'
import sizeOf from 'image-size'
const imgFromPath = async (imgPath) => {
let results = []
await Promise.all(imgPath.map(async item => {
let buffer = await fs.readFile(item)
let base64Image = Buffer.from(buffer, 'binary').toString('base64')
let fileName = path.basename(item)
let imgSize = sizeOf(item)
results.push({
base64Image,
fileName,
width: imgSize.width,
height: imgSize.height
})
}))
return results
}
const imgFromClipboard = (file) => {
let result = []
result.push({
base64Image: file.imgUrl.replace(/^data\S+,/, ''),
width: file.width,
height: file.height
})
return result
}
export {
imgFromPath,
imgFromClipboard
}

View File

@@ -0,0 +1,66 @@
import request from 'request-promise'
import * as img2Base64 from './img2base64'
import db from '../../datastore/index'
import { Notification } from 'electron'
const j = request.jar()
const rp = request.defaults({jar: j})
const UPLOAD_URL = 'http://picupload.service.weibo.com/interface/pic_upload.php?ori=1&mime=image%2Fjpeg&data=base64&url=0&markpos=1&logo=&nick=0&marks=1&app=miniblog'
function postOptions (formData) {
return {
method: 'POST',
url: 'https://passport.weibo.cn/sso/login',
headers: {
Referer: 'https://passport.weibo.cn/signin/login',
contentType: 'application/x-www-form-urlencoded'
},
formData,
json: true,
resolveWithFullResponse: true
}
}
const weiboUpload = async function (img, type) {
try {
const formData = {
username: db.read().get('picBed.weibo.username').value(),
password: db.read().get('picBed.weibo.password').value()
}
const options = postOptions(formData)
const res = await rp(options)
if (res.body.retcode === 20000000) {
for (let i in res.body.data.crossdomainlist) {
await rp.get(res.body.data.crossdomainlist[i])
}
const imgList = await img2Base64[type](img)
let resText = []
for (let i in imgList) {
let result = await rp.post(UPLOAD_URL, {
formData: {
b64_data: imgList[i].base64Image
}
})
resText.push(result.replace(/<.*?\/>/, '').replace(/<(\w+).*?>.*?<\/\1>/, ''))
}
for (let i in imgList) {
const resTextJson = JSON.parse(resText[i])
imgList[i]['imgUrl'] = `https://ws1.sinaimg.cn/large/${resTextJson.data.pics.pic_1.pid}`
delete imgList[i].base64Image
}
return imgList
} else {
const notification = new Notification({
title: '上传失败!',
body: res.body.msg
})
notification.show()
}
} catch (err) {
console.log('This is error', err, err.name === 'RequestError')
throw new Error(err)
}
}
export {
weiboUpload
}

20
src/renderer/App.vue Normal file
View File

@@ -0,0 +1,20 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'picgo'
}
</script>
<style lang="stylus">
body,
html
padding 0
margin 0
height 100%
font-family "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
</style>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,83 @@
<template>
<div id="setting-page">
<div class="fake-title-bar">
PicGo
</div>
<el-row>
<el-col :span="5">
<el-menu
class="picgo-sidebar"
:default-active="defaultActive"
@select="handleSelect"
>
<el-menu-item index="upload">
<i class="el-icon-upload"></i>
<span slot="title">上传区</span>
</el-menu-item>
<el-menu-item index="weibo">
<i class="el-icon-setting"></i>
<span slot="title">微博设置</span>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="19">
<router-view></router-view>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'setting-page',
data () {
return {
defaultActive: 'upload'
}
},
methods: {
handleSelect (index) {
this.$router.push({
name: index
})
}
},
beforeRouteEnter: (to, from, next) => {
next(vm => {
vm.defaultActive = to.name
})
}
}
</script>
<style lang='stylus'>
#setting-page
.fake-title-bar
-webkit-app-region drag
height h = 22px
width 100%
text-align center
color #eee
font-size 12px
line-height h
.picgo-sidebar
height calc(100vh - 22px)
.el-menu
border-right none
background transparent
&-item
color #eee
position relative
&:focus,
&:hover
color #fff
background transparent
&.is-active
color active-color = #409EFF
&:before
content ''
position absolute
width 3px
height 20px
right 0
top 18px
background active-color
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div id="choose-pic-bed">
<span>选择 {{ label }} 作为你默认图床</span>
<el-switch
v-model="value"
@change="choosePicBed"
>
</el-switch>
</div>
</template>
<script>
export default {
name: 'choose-pic-bed',
props: {
type: String,
label: String
},
data () {
return {
value: false
}
},
created () {
if (this.type === this.$db.get('picBed.current').value()) {
this.value = true
}
},
methods: {
choosePicBed (val) {
this.$db.set('picBed.current', this.type)
this.$emit('update:choosed', this.type)
}
}
}
</script>
<style lang='stylus'>
</style>

View File

@@ -0,0 +1,17 @@
<template>
<div id="upload-view">
upload
</div>
</template>
<script>
export default {
name: 'upload',
data () {
return {
msg: '123'
}
}
}
</script>
<style lang='stylus'>
</style>

View File

@@ -0,0 +1,94 @@
<template>
<div id="weibo-view">
<el-row :gutter="16">
<el-col :span="12" :offset="6">
<div class="view-title">
微博图床设置
</div>
<el-form
ref="weiboForm"
label-position="top"
label-width="80px"
:model="form">
<el-form-item
label="设定用户名"
prop="username"
:rules="{
required: true, message: '用户名不能为空', trigger: 'blur'
}">
<el-input v-model="form.username" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item
label="设定密码"
prop="password"
:rules="{
required: true, message: '密码不能为空', trigger: 'blur'
}">
<el-input v-model="form.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="confirm('weiboForm')">确定</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'weibo',
data () {
return {
form: {
username: '',
password: ''
}
}
},
created () {
const account = this.$db.get('picBed.weibo').value()
console.log(account)
if (account) {
this.form.username = account.username
this.form.password = account.password
}
},
methods: {
confirm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$db.set('picBed.weibo', {
username: this.form.username,
password: this.form.password
}).write()
console.log(this.$db.get('picBed.weibo').value())
this.$message({
type: 'success',
message: '设置成功'
})
} else {
return false
}
})
}
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
#weibo-view
.view-title
color #eee
font-size 20px
text-align center
margin 20px auto
.el-form
label
line-height 22px
padding-bottom 0
color #eee
.el-button
width 100%
margin-top 10px
</style>

View File

@@ -0,0 +1,142 @@
<template>
<div id="tray-page">
<div class="content">
<div class="wait-upload-img" v-if="clipboardFiles.length > 0">
<div class="list-title">等待上传</div>
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div class="upload-img__container" @click="uploadClipboardFiles">
<img :src="item.imgUrl" class="upload-img">
</div>
</div>
</div>
<div class="uploaded-img">
<div class="list-title">已上传</div>
<div v-for="(item, index) in files" :key="index" class="img-list" :style="{height: calcHeight(item.width, item.height) + 'px'}">
<div class="upload-img__container" @click="copyTheLink(item)">
<img :src="item.imgUrl" class="upload-img">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'tray-page',
data () {
return {
files: [],
notification: {
title: '复制链接成功',
body: '',
icon: ''
},
clipboardFiles: []
}
},
computed: {
reverseList () {
return this.files.slice().reverse()
}
},
mounted () {
this.disableDragFile()
this.getData()
this.$electron.ipcRenderer.on('dragFiles', (event, files) => {
this.$db.get('uploaded').push(...files).write()
this.files = this.$db.get('uploaded').slice().reverse().slice(0, 5).value()
})
this.$electron.ipcRenderer.on('clipboardFiles', (event, files) => {
this.clipboardFiles = files
})
this.$electron.ipcRenderer.on('uploadClipboardFiles', (event, files) => {
this.$db.get('uploaded').push(...files).write()
this.files = this.$db.get('uploaded').slice().reverse().slice(0, 5).value()
})
},
methods: {
getData () {
this.files = this.$db.get('uploaded').slice().reverse().slice(0, 5).value()
},
copyTheLink (item) {
this.notification.body = item.imgUrl
this.notification.icon = item.imgUrl
const myNotification = new window.Notification(this.notification.title, this.notification)
myNotification.onclick = () => {
this.$electron.clipboard.writeText(item.imgUrl)
}
},
calcHeight (width, height) {
return height * 160 / width
},
disableDragFile () {
window.addEventListener('dragover', (e) => {
e = e || event
e.preventDefault()
}, false)
window.addEventListener('drop', (e) => {
e = e || event
e.preventDefault()
}, false)
},
uploadClipboardFiles () {
this.$electron.ipcRenderer.send('uploadClipboardFiles', this.clipboardFiles[0])
}
}
}
</script>
<style lang="stylus">
#tray-page
.list-title
text-align center
color #858585
font-size 12px
padding 6px 0
position relative
&:before
content ''
position absolute
height 1px
width calc(100% - 36px)
bottom 0
left 18px
background #858585
.header-arrow
position absolute
top 12px
left 50%
margin-left -10px
width: 0;
height: 0;
border-left: 10px solid transparent
border-right: 10px solid transparent
border-bottom: 10px solid rgba(255,255,255, 1)
.content
position absolute
top 0px
width 100%
// padding-top 10px
// background-color rgba(255,255,255, 1)
.img-list
padding 16px 8px
display flex
justify-content space-between
align-items center
height 45px
cursor pointer
transition all .2s ease-in-out
&:hover
background #49B1F5
.upload-img__index
color #fff
.upload-img
height 100%
width 100%
margin 0 auto
&__container
width 100%
padding 8px 10px
height 100%
</style>

22
src/renderer/main.js Normal file
View File

@@ -0,0 +1,22 @@
import Vue from 'vue'
import axios from 'axios'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import router from './router'
import store from './store'
import db from '../datastore/index'
Vue.use(ElementUI)
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.prototype.$db = db
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
components: { App },
router,
store,
template: '<App/>'
}).$mount('#app')

View File

@@ -0,0 +1,35 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'tray-page',
component: require('@/components/TrayPage').default
},
{
path: '/setting',
name: 'setting-page',
component: require('@/components/SettingPage').default,
children: [
{
path: 'upload',
component: require('@/components/SettingView/Upload').default,
name: 'upload'
},
{
path: 'weibo',
component: require('@/components/SettingView/Weibo').default,
name: 'weibo'
}
]
},
{
path: '*',
redirect: '/'
}
]
})

View File

@@ -0,0 +1,11 @@
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules,
strict: process.env.NODE_ENV !== 'production'
})

View File

@@ -0,0 +1,25 @@
const state = {
main: 0
}
const mutations = {
DECREMENT_MAIN_COUNTER (state) {
state.main--
},
INCREMENT_MAIN_COUNTER (state) {
state.main++
}
}
const actions = {
someAsyncTask ({ commit }) {
// do something async
commit('INCREMENT_MAIN_COUNTER')
}
}
export default {
state,
mutations,
actions
}

View File

@@ -0,0 +1,14 @@
/**
* The file enables `@/store/index.js` to import all vuex modules
* in a one-shot manner. There should not be any reason to edit this file.
*/
const files = require.context('.', false, /\.js$/)
const modules = {}
files.keys().forEach(key => {
if (key === './index.js') return
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default modules