add front-todofeed

This commit is contained in:
elegant651
2020-03-30 11:26:28 +09:00
parent 3face74a64
commit dd3033be9c
26 changed files with 1139 additions and 1 deletions

21
todofeed/front-todofeed/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,30 @@
#
frontend-todofeed
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
}

View File

@@ -0,0 +1,56 @@
{
"name": "frontend-todofeed",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"caver-js": "^1.3.2",
"core-js": "^2.6.5",
"identicon.js": "^2.3.3",
"js-sha256": "^0.9.0",
"moment": "^2.24.0",
"vue": "^2.6.10",
"vuetify": "^2.2.18",
"vuex": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.1.1",
"@vue/cli-plugin-eslint": "^3.1.1",
"@vue/cli-service": "^3.1.1",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"sass": "^1.19.0",
"sass-loader": "^8.0.0",
"vue-cli-plugin-vuetify": "^2.0.5",
"vue-template-compiler": "^2.6.10",
"vuetify-loader": "^1.3.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>frontend-todofeed</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong>We're sorry but frontend-todofeed doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,181 @@
<template>
<v-app>
<v-app-bar app clipped-left dense>
<v-toolbar-title>TodoFeed</v-toolbar-title>
<v-spacer></v-spacer>
<template v-if="isConnectWallet">
<h3 class="mr-3">{{balance}} Klay</h3>
<v-btn outlined @click="removeWallet">Remove Wallet</v-btn>
</template>
<template v-else>
<v-btn outlined @click="showLoginBox = true">Connect to Wallet</v-btn>
</template>
</v-app-bar>
<v-content>
<v-container fluid>
<v-btn color="blue darken-1" class="mb-3" v-if="isConnectWallet" @click="showWriteBox = true">New ToDo</v-btn>
<Feeds v-on:verify="onVerify" />
</v-container>
</v-content>
<v-dialog v-model="showLoginBox" max-width="500px">
<v-card>
<v-card-title>
<span class="headline">Login</span>
</v-card-title>
<LoginBox />
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" @click="showLoginBox = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showWriteBox" max-width="500px">
<v-card>
<v-card-title>
<span class="headline">New Todo</span>
</v-card-title>
<WriteBox v-on:success-write="onSuccessWrite" v-on:error-validate="onErrorValidate" />
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" @click="showWriteBox = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar
v-model="snackbar" top>
{{ snackbarMsg }}
<v-btn
color="pink"
text
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
</v-app>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import KlaytnService from './klaytn/klaytnService'
import Feeds from '@/components/Feeds.vue'
import LoginBox from '@/components/LoginBox.vue'
import WriteBox from '@/components/WriteBox.vue'
export default {
name: 'app',
components: {
Feeds,
LoginBox,
WriteBox
},
data: () => ({
showLoginBox: false,
showWriteBox: false,
snackbar: false,
snackbarMsg: ''
}),
computed: {
...mapGetters('wallet', [
'klaytn',
'isConnectWallet',
'myaddress',
'balance'
])
},
async mounted () {
await this.connect()
await this.getFeeds()
},
methods: {
...mapMutations('wallet', [
'setKlaytn',
'setIsConnectWallet',
'setMyAddress',
'setBalance'
]),
...mapMutations('todos', [
'setTodos'
]),
async connect () {
const klaytn = new KlaytnService()
this.setKlaytn(klaytn)
const address = await klaytn.init()
if (address) {
this.setMyAddress(address)
this.getBalance()
this.setIsConnectWallet(true)
} else {
this.setIsConnectWallet(false)
}
},
async getBalance () {
if (this.myaddress) {
const balance = await this.klaytn.getBalance(this.myaddress)
this.setBalance(balance)
}
},
removeWallet () {
this.klaytn.removeWallet()
this.setIsConnectWallet(false)
},
onErrorValidate(msg) {
this.snackbarMsg = msg
this.snackbar = true
},
onSuccessWrite(msg) {
this.snackbarMsg = msg
this.snackbar = true
this.getFeeds()
this.getBalance()
this.showWriteBox = false
},
onVerify(todoId) {
this.klaytn.verify(todoId, (receipt) => {
this.snackbarMsg = `Complete.. blocknumber: #${receipt.blockNumber} , ${receipt.transactionHash}`
this.snackbar = true
this.getFeeds()
this.getBalance()
})
},
getFeeds() {
this.klaytn.getFeeds((feed) => {
this.setTodos(feed)
})
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

After

Width:  |  Height:  |  Size: 539 B

View File

@@ -0,0 +1,108 @@
<template>
<div>
<v-card class="cardContent mx-auto mb-4" max-width="400" max-height="400"
v-for="item in allTodos" :key="item.index">
<v-list-item>
<v-list-item-avatar color="grey"><img :src="getProfile(item.owner)" /></v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{item.title}}</v-list-item-title>
<v-list-item-subtitle>{{getCreatedAt(item.timestamp)}}</v-list-item-subtitle>
</v-list-item-content>
<div class="flex-grow-1"></div>
<a :href="getScopeUrl(item.verifier)" target="_blank">{{getVerified(item.isVerified)}}</a>
</v-list-item>
<v-img
:src="getImage(item.photo)"
height="194"
></v-img>
<v-card-actions>
<v-btn
text
color="deep-purple accent-4"
v-if="!item.isVerified"
@click="verify(item.todoId)"
>
Verify
</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import moment from 'moment'
import {getIdenticon} from '@/util/identicon'
import { drawImageFromBytes } from '@/util/imageUtils'
export default {
data: () => ({
items: []
}),
computed: {
...mapGetters('todos', [
'allTodos'
])
},
methods: {
getProfile (user_name) {
return getIdenticon(user_name)
},
getCreatedAt (timestamp) {
return moment(timestamp * 1000).fromNow()
},
getImage (photo) {
if (photo) {
const imageUrl = drawImageFromBytes(photo)
return imageUrl
} else {
''
}
},
getVerified (isVerified) {
return (isVerified) ? "Verified" : "Not Verified"
},
getScopeUrl (address) {
if (address) {
return `https://baobab.scope.klaytn.com/account/${address}`
} else {
return ''
}
},
verify (todoId) {
this.$emit('verify', todoId)
}
}
}
</script>
<style scoped>
.cardContent {
text-align: left;
}
.v-list-item__title {
font-size: 14px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
color: #17191d;
}
.v-list-item__subtitle {
font-size: 12px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
color: #7f899a;
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<div>
<h2>Connect to Wallet</h2>
<v-card class="d-flex mx-auto mb-4 pa-2">
<template v-if="walletInstance">
<v-container>
<v-row>
<v-col cols="16">
<h2>Integrated</h2>
<div class="address">{{walletInstance.address}}</div>
<v-btn outlined class="btnSubmit mt-3" @click="this.handleRemoveWallet">REMOVE WALLEt</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<template v-else>
<v-container>
<v-row>
<v-col cols="16">
<input type="file" v-on:change="this.handleImport" />
<v-text-field
v-model="password"
label="Password"
type="password"
solo
required
></v-text-field>
<v-divider class="mt-3 mb-3"></v-divider>
<v-text-field
v-model="privateKey"
label="Private Key"
type="password"
solo
required
></v-text-field>
<v-btn outlined class="btnSubmit" @click="this.handleAddWallet">ADD WALLET</v-btn>
</v-col>
</v-row>
</v-container>
</template>
</v-card>
<v-snackbar
v-model="snackbar" top>
{{ errorTxt }}
<v-btn
color="pink"
text
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
data: () => ({
snackbar: false,
errorTxt: '',
// accessType: 'keystore',
keystore: null,
password: '',
privateKey: null,
walletInstance: null
}),
computed: {
...mapGetters('wallet', [
'klaytn'
])
},
methods: {
...mapMutations('wallet', [
'setIsConnectWallet',
'setMyAddress',
'setBalance'
]),
validate () {
},
handleImport (e) {
const keystore = e.target.files[0]
const fileReader = new FileReader()
fileReader.onload = (e) => {
try {
if (!this.checkValidKeystore(e.target.result)) {
// If key store file is invalid, show message "Invalid keystore file."
this.errorTxt = 'Invalid keystore file.'
this.snackbar = true
return
}
this.keystore = e.target.result
} catch (e) {
this.errorTxt = 'Invalid keystore file.'
this.snackbar = true
return
}
}
fileReader.readAsText(keystore)
},
async handleAddWallet () {
try {
// Access type2: access thorugh private key
if(this.privateKey) {
await this.klaytn.integrateWallet(this.privateKey)
} else {
// Access type1: access through keystore + password
await this.klaytn.loginWithKeystore(this.keystore, this.password)
}
this.getWalletInfo()
} catch (e) {
this.errorTxt = `Password or private key doesn't match.`
this.snackbar = true
}
},
async getWalletInfo () {
this.walletInstance = this.klaytn.getWallet()
const address = this.walletInstance.address
if(address) {
this.setMyAddress(address)
const balance = await this.klaytn.getBalance(address)
this.setBalance(balance)
this.setIsConnectWallet(true)
} else {
this.setIsConnectWallet(false)
}
},
checkValidKeystore (keystore) {
const parsedKeystore = JSON.parse(keystore)
const isValidKeystore = parsedKeystore.version &&
parsedKeystore.id &&
parsedKeystore.address &&
parsedKeystore.crypto
return isValidKeystore
},
async handleRemoveWallet () {
this.klaytn.removeWallet()
this.setIsConnectWallet(false)
this.walletInstance = null
}
},
mounted () {
if(this.klaytn) {
this.walletInstance = this.klaytn.getWallet()
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,113 @@
<template>
<div>
<v-card class="cardContent d-flex mx-auto mb-4 pa-2">
<v-container>
<v-row>
<v-col cols="16">
<v-form
ref="form"
class="mt-2"
lazy-validation
>
<v-text-field
v-model="title"
label="Title"
:rules="titleRules"
solo
required
></v-text-field>
<input type="file" accept="image/*" v-on:change="this.handleImportFile" required />
<v-btn v-if="!isLoading" outlined @click="validate">POST</v-btn>
<v-progress-circular indeterminate v-else></v-progress-circular>
</v-form>
</v-col>
</v-row>
</v-container>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import imageCompression from '@/util/imageCompression'
export default {
data: () => ({
title: '',
titleRules: [
v => !!v || 'Title is required',
v => (v && v.length <= 255) || 'Title must be less than 255 characters',
],
isLoading: false
}),
computed: {
...mapGetters('wallet', [
'klaytn'
])
},
methods: {
validate () {
if (!this.$refs.form.validate()) {
const snackbarMsg = 'form data are incorrect. please check again'
this.$emit('error-validate', snackbarMsg)
return false
}
this.submit()
},
async compressImage (imageFile) {
try {
const MAX_IMAGE_SIZE_MB = 0.03 // 30KB
const compressedFile = await imageCompression(imageFile, MAX_IMAGE_SIZE_MB)
return compressedFile
} catch (error) {
console.error('* Fail to compress image')
return imageFile
}
},
async handleImportFile (e) {
const MAX_IMAGE_SIZE = 30000 // 30KB
const file = e.target.files[0]
if (file.size > MAX_IMAGE_SIZE) {
this.imgFile = await this.compressImage(file)
} else {
this.imgFile = file
}
},
async submit () {
try {
this.isLoading = true
await this.klaytn.writeTodo(this.title, this.imgFile, (receipt) => {
console.log(receipt)
const snackbarMsg = `Complete.. blocknumber: #${receipt.blockNumber} , ${receipt.transactionHash}`
this.$emit('success-write', snackbarMsg)
this.isLoading = false
}, (error) => {
this.$emit('error-validate', error)
this.isLoading = false
})
} catch (e) {
console.error(e)
this.isLoading = false
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,27 @@
/**
* caver-js library helps making connection with klaytn node.
* You can connect to specific klaytn node by setting 'rpcURL' value.
* default rpcURL is 'https://api.baobab.klaytn.net:8651'.
*/
import Caver from 'caver-js'
const deployedABI = require('./deployedABI.json')
const TEST_NET = 'https://api.baobab.klaytn.net:8651'
export const config = {
rpcURL: TEST_NET
}
const DEPLOYED_ADDRESS = '0xa47898be53fba0fecdb9b4e1dadea5bf0f3c77f7' // testnet
const cav = new Caver(config.rpcURL)
const getContractInstance = () => {
const contractInstance = deployedABI
&& DEPLOYED_ADDRESS
&& new cav.klay.Contract(deployedABI, DEPLOYED_ADDRESS)
return contractInstance
}
export {cav, getContractInstance}

View File

@@ -0,0 +1 @@
[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"todoList","outputs":[{"name":"todoId","type":"uint256"},{"name":"owner","type":"address"},{"name":"title","type":"string"},{"name":"photo","type":"bytes"},{"name":"timestamp","type":"uint256"},{"name":"isVerified","type":"bool"},{"name":"verifier","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"todoMap","outputs":[{"name":"todoId","type":"uint256"},{"name":"owner","type":"address"},{"name":"title","type":"string"},{"name":"photo","type":"bytes"},{"name":"timestamp","type":"uint256"},{"name":"isVerified","type":"bool"},{"name":"verifier","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"todoId","type":"uint256"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"title","type":"string"},{"indexed":false,"name":"photo","type":"bytes"},{"indexed":false,"name":"timestamp","type":"uint256"}],"name":"TodoCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"todoId","type":"uint256"},{"indexed":false,"name":"verifier","type":"address"}],"name":"TodoVerified","type":"event"},{"constant":false,"inputs":[{"name":"title","type":"string"},{"name":"photo","type":"bytes"}],"name":"writeTodo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"todoId","type":"uint256"}],"name":"verifyTodo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalTodoCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"todoId","type":"uint256"}],"name":"getTodo","outputs":[{"name":"","type":"uint256"},{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"bytes"},{"name":"","type":"uint256"},{"name":"","type":"bool"},{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]

View File

@@ -0,0 +1 @@
0xa47898be53fba0fecdb9b4e1dadea5bf0f3c77f7

View File

@@ -0,0 +1,150 @@
import { cav, getContractInstance } from './caver'
export default class KlaytnService {
constructor() {}
async init () {
const walletFromSession = sessionStorage.getItem('walletInstance')
// If 'walletInstance' value exists, add it to caver's wallet
if (walletFromSession) {
try {
const address = JSON.parse(walletFromSession).address
cav.klay.accounts.wallet.add(JSON.parse(walletFromSession))
return address
} catch (e) {
// If value in sessionStorage is invalid wallet instance,
// remove it from sessionStorage.
sessionStorage.removeItem('walletInstance')
return false
}
}
}
async getBlockNumber () {
const blockNumber = await cav.klay.getBlockNumber()
return blockNumber
}
async getBalance (address) {
const balance = await cav.klay.getBalance(address)
return cav.utils.fromPeb(balance, "KLAY")
}
async loginWithKeystore (keystore, password) {
const { privateKey: privateKeyFromKeystore } = cav.klay.accounts.decrypt(keystore, password)
await this.integrateWallet(privateKeyFromKeystore)
return true
}
integrateWallet (privateKey) {
const walletInstance = cav.klay.accounts.privateKeyToAccount(privateKey)
cav.klay.accounts.wallet.add(walletInstance)
sessionStorage.setItem('walletInstance', JSON.stringify(walletInstance))
return true
}
removeWallet () {
cav.klay.accounts.wallet.clear()
sessionStorage.removeItem('walletInstance')
return true
}
getWallet () {
if (cav.klay.accounts.wallet.length) {
return cav.klay.accounts.wallet[0]
}
return null
}
writeTodo (title, file, dispatch, errorCb) {
const walletInstance = cav.klay.accounts.wallet && cav.klay.accounts.wallet[0]
if (!walletInstance) {
console.log('no walletInstance')
return
}
const address = walletInstance.address
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
const buffer = Buffer.from(reader.result)
const hexString = "0x" + buffer.toString('hex')
getContractInstance().methods.writeTodo(title, hexString).send({
from: address,
gas: '100000000',
})
.once('transactionHash', (txHash) => {
console.log(`
Sending a transaction...
txHash: ${txHash}
`
)
})
.once('receipt', (receipt) => {
console.log(`
Received receipt! (#${receipt.blockNumber} ,${receipt.transactionHash})
`, receipt)
dispatch(receipt)
})
.once('error', (error) => {
errorCb(error.message)
})
}
}
getFeeds (dispatch) {
getContractInstance().methods.getTotalTodoCount().call()
.then((totalTodoCount) => {
if (!totalTodoCount) return []
const feed = []
for (let i = totalTodoCount; i > 0; i--) {
const todo = getContractInstance().methods.getTodo(i).call()
feed.push(todo)
}
return Promise.all(feed)
})
.then((feed) => {
dispatch(feed)
})
}
verify (todoId, dispatch) {
const walletInstance = cav.klay.accounts.wallet && cav.klay.accounts.wallet[0]
if (!walletInstance) {
console.log('no walletInstance')
return
}
const address = walletInstance.address
getContractInstance().methods.verifyTodo(todoId).send({
from: address,
gas: '3000000'
})
.once('transactionHash', (txHash) => {
console.log(`
Sending a transaction...
txHash: ${txHash}
`
)
})
.once('receipt', (receipt) => {
console.log(`
Received receipt! (#${receipt.blockNumber} ,${receipt.transactionHash})
`, receipt)
dispatch(receipt)
})
.once('error', (error) => {
alert(error.message)
})
}
}

View File

@@ -0,0 +1,12 @@
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
new Vue({
vuetify,
store,
render: h => h(App)
}).$mount('#app')

View File

@@ -0,0 +1,7 @@
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
Vue.use(Vuetify);
export default new Vuetify({
});

View File

@@ -0,0 +1,14 @@
import Vue from 'vue'
import Vuex from 'vuex'
import wallet from '@/store/modules/wallet'
import todos from '@/store/modules/todos'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
wallet,
todos
}
})

View File

@@ -0,0 +1,42 @@
import klaytnService from '@/klaytn/klaytnService'
const state = {
items: []
}
const getters = {
allTodos: (state) => {
return state.items
}
}
const mutations = {
setTodos (state, todos) {
todos = todos.map(feed => {
const obj = {
todoId: parseInt(feed[0]),
owner: feed[1],
title: feed[2],
photo: feed[3],
timestamp: feed[4],
isVerified: feed[5],
verifier: feed[6]
}
return obj
})
state.items = todos
}
}
const actions = {
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -0,0 +1,41 @@
const state = {
klaytn: null,
isConnectWallet: false,
myaddress: '',
balance: 0
}
const getters = {
klaytn: (state) => state.klaytn,
isConnectWallet: (state) => state.isConnectWallet,
myaddress: (state) => state.myaddress,
balance: (state) => state.balance
}
const mutations = {
setKlaytn(state, klaytn) {
state.klaytn = klaytn
},
setIsConnectWallet(state, isConnected) {
state.isConnectWallet = isConnected
},
setMyAddress(state, address) {
state.myaddress = address
},
setBalance(state, balance) {
state.balance = balance
}
}
const actions = {
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -0,0 +1,14 @@
import sha256 from 'js-sha256'
import Identicon from 'identicon.js'
const getIdenticon = (user_name) => {
if(user_name){
const data = new Identicon(sha256(user_name)).toString()
return 'data:image/png;base64,'+ data
} else {
return '/img/profile-medium.png'
}
}
export {getIdenticon}

View File

@@ -0,0 +1,44 @@
import { drawImageInCanvas, getDataUrlFromFile, getFilefromDataUrl, loadImage } from './imageUtils'
async function imageCompression(file, maxSizeMB = Number.POSITIVE_INFINITY, maxWidthOrHeight) {
if (!(file instanceof Blob || file instanceof File)) {
throw new Error('The file given is not an instance of Blob or File')
} else if (!/^image/.test(file.type)) {
throw new Error('The file given is not an image')
}
const maxSizeByte = maxSizeMB * 1024 * 1024
const dataUrl = await getDataUrlFromFile(file)
const img = await loadImage(dataUrl)
const canvas = drawImageInCanvas(img, maxWidthOrHeight)
let quality = 0.9
let compressedFile = await getFilefromDataUrl(canvas.toDataURL(file.type, quality), file.name, file.lastModified)
if (file) {
while (compressedFile.size > maxSizeByte) {
canvas.width *= 0.9
canvas.height *= 0.9
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const compressedDataUrl = canvas.toDataURL(file.type, quality)
compressedFile = await getFilefromDataUrl(compressedDataUrl, file.name, file.lastModified)
}
} else {
while (compressedFile.size > maxSizeByte) {
quality *= 0.9
const compressedDataUrl = canvas.toDataURL(file.type, quality)
compressedFile = await getFilefromDataUrl(compressedDataUrl, file.name, file.lastModified)
}
}
return compressedFile
}
imageCompression.drawImageInCanvas = drawImageInCanvas
imageCompression.getDataUrlFromFile = getDataUrlFromFile
imageCompression.getFilefromDataUrl = getFilefromDataUrl
imageCompression.loadImage = loadImage
export default imageCompression

View File

@@ -0,0 +1,76 @@
export function getDataUrlFromFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
resolve(reader.result)
}
reader.onerror = reject
})
}
export function getFilefromDataUrl(dataurl, filename, lastModified = Date.now()) {
return new Promise((resolve) => {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?)/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
let file
try {
file = new File([u8arr], filename, { type: mime }) // Edge do not support File constructor
} catch (e) {
file = new Blob([u8arr], { type: mime })
file.name = filename
file.lastModified = lastModified
}
resolve(file)
})
}
export function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
resolve(img)
}
img.onerror = reject
img.src = src
})
}
export function drawImageInCanvas(img, maxWidthOrHeight) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (Number.isInteger(maxWidthOrHeight) && (img.width > maxWidthOrHeight || img.height > maxWidthOrHeight)) {
if (img.width > img.height) {
canvas.width = maxWidthOrHeight
canvas.height = (img.height / img.width) * maxWidthOrHeight
} else {
canvas.width = (img.width / img.height) * maxWidthOrHeight
canvas.height = maxWidthOrHeight
}
} else {
canvas.width = img.width
canvas.height = img.height
}
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
return canvas
}
export const drawImageFromBytes = (data) => {
/**
* data.slice(2)
* Remove prefix `0x` from hexString
*/
const hexString = data.slice(2)
const arrayBufferView = new Uint8Array(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)))
const blob = new Blob([arrayBufferView], { type: 'image/jpeg' })
const urlCreator = window.URL || window.webkitURL
const imageUrl = urlCreator.createObjectURL(blob)
return imageUrl
}

View File

@@ -0,0 +1,5 @@
module.exports = {
"transpileDependencies": [
"vuetify"
]
}

Submodule todofeed/frontend-todofeed deleted from 56352fc7d3