diff --git a/todofeed/front-todofeed/.gitignore b/todofeed/front-todofeed/.gitignore
new file mode 100644
index 0000000..a0dddc6
--- /dev/null
+++ b/todofeed/front-todofeed/.gitignore
@@ -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?
diff --git a/todofeed/front-todofeed/README.md b/todofeed/front-todofeed/README.md
new file mode 100644
index 0000000..b600a3d
--- /dev/null
+++ b/todofeed/front-todofeed/README.md
@@ -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/).
diff --git a/todofeed/front-todofeed/babel.config.js b/todofeed/front-todofeed/babel.config.js
new file mode 100644
index 0000000..ba17966
--- /dev/null
+++ b/todofeed/front-todofeed/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ '@vue/app'
+ ]
+}
diff --git a/todofeed/front-todofeed/package.json b/todofeed/front-todofeed/package.json
new file mode 100644
index 0000000..d30d869
--- /dev/null
+++ b/todofeed/front-todofeed/package.json
@@ -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"
+ ]
+}
diff --git a/todofeed/front-todofeed/public/favicon.ico b/todofeed/front-todofeed/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/todofeed/front-todofeed/public/favicon.ico differ
diff --git a/todofeed/front-todofeed/public/index.html b/todofeed/front-todofeed/public/index.html
new file mode 100644
index 0000000..532384a
--- /dev/null
+++ b/todofeed/front-todofeed/public/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ frontend-todofeed
+
+
+
+
+
+
+
+
+
diff --git a/todofeed/front-todofeed/src/App.vue b/todofeed/front-todofeed/src/App.vue
new file mode 100644
index 0000000..ee69307
--- /dev/null
+++ b/todofeed/front-todofeed/src/App.vue
@@ -0,0 +1,181 @@
+
+
+
+ TodoFeed
+
+
+ {{balance}} Klay
+ Remove Wallet
+
+
+ Connect to Wallet
+
+
+
+
+ New ToDo
+
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
+ New Todo
+
+
+
+
+
+
+ Close
+
+
+
+
+
+ {{ snackbarMsg }}
+
+ Close
+
+
+
+
+
+
+
+
diff --git a/todofeed/front-todofeed/src/assets/logo.png b/todofeed/front-todofeed/src/assets/logo.png
new file mode 100644
index 0000000..f3d2503
Binary files /dev/null and b/todofeed/front-todofeed/src/assets/logo.png differ
diff --git a/todofeed/front-todofeed/src/assets/logo.svg b/todofeed/front-todofeed/src/assets/logo.svg
new file mode 100644
index 0000000..145b6d1
--- /dev/null
+++ b/todofeed/front-todofeed/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/todofeed/front-todofeed/src/components/Feeds.vue b/todofeed/front-todofeed/src/components/Feeds.vue
new file mode 100644
index 0000000..1d2a53e
--- /dev/null
+++ b/todofeed/front-todofeed/src/components/Feeds.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/components/LoginBox.vue b/todofeed/front-todofeed/src/components/LoginBox.vue
new file mode 100644
index 0000000..30ed3c8
--- /dev/null
+++ b/todofeed/front-todofeed/src/components/LoginBox.vue
@@ -0,0 +1,171 @@
+
+
+
Connect to Wallet
+
+
+
+
+
+ Integrated
+ {{walletInstance.address}}
+ REMOVE WALLEt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ADD WALLET
+
+
+
+
+
+
+
+ {{ errorTxt }}
+
+ Close
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/components/WriteBox.vue b/todofeed/front-todofeed/src/components/WriteBox.vue
new file mode 100644
index 0000000..ee2f955
--- /dev/null
+++ b/todofeed/front-todofeed/src/components/WriteBox.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+ POST
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/klaytn/caver.js b/todofeed/front-todofeed/src/klaytn/caver.js
new file mode 100644
index 0000000..cc47621
--- /dev/null
+++ b/todofeed/front-todofeed/src/klaytn/caver.js
@@ -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}
diff --git a/todofeed/front-todofeed/src/klaytn/deployedABI.json b/todofeed/front-todofeed/src/klaytn/deployedABI.json
new file mode 100644
index 0000000..0981bae
--- /dev/null
+++ b/todofeed/front-todofeed/src/klaytn/deployedABI.json
@@ -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"}]
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/klaytn/deployedAddress b/todofeed/front-todofeed/src/klaytn/deployedAddress
new file mode 100644
index 0000000..d0b998f
--- /dev/null
+++ b/todofeed/front-todofeed/src/klaytn/deployedAddress
@@ -0,0 +1 @@
+0xa47898be53fba0fecdb9b4e1dadea5bf0f3c77f7
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/klaytn/klaytnService.js b/todofeed/front-todofeed/src/klaytn/klaytnService.js
new file mode 100644
index 0000000..a843194
--- /dev/null
+++ b/todofeed/front-todofeed/src/klaytn/klaytnService.js
@@ -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)
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/main.js b/todofeed/front-todofeed/src/main.js
new file mode 100644
index 0000000..7614f5d
--- /dev/null
+++ b/todofeed/front-todofeed/src/main.js
@@ -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')
diff --git a/todofeed/front-todofeed/src/plugins/vuetify.js b/todofeed/front-todofeed/src/plugins/vuetify.js
new file mode 100644
index 0000000..ec46adb
--- /dev/null
+++ b/todofeed/front-todofeed/src/plugins/vuetify.js
@@ -0,0 +1,7 @@
+import Vue from 'vue';
+import Vuetify from 'vuetify/lib';
+
+Vue.use(Vuetify);
+
+export default new Vuetify({
+});
diff --git a/todofeed/front-todofeed/src/store/index.js b/todofeed/front-todofeed/src/store/index.js
new file mode 100644
index 0000000..7588de2
--- /dev/null
+++ b/todofeed/front-todofeed/src/store/index.js
@@ -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
+ }
+})
diff --git a/todofeed/front-todofeed/src/store/modules/todos.js b/todofeed/front-todofeed/src/store/modules/todos.js
new file mode 100644
index 0000000..4e64e3a
--- /dev/null
+++ b/todofeed/front-todofeed/src/store/modules/todos.js
@@ -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
+}
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/store/modules/wallet.js b/todofeed/front-todofeed/src/store/modules/wallet.js
new file mode 100644
index 0000000..62f82e8
--- /dev/null
+++ b/todofeed/front-todofeed/src/store/modules/wallet.js
@@ -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
+}
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/util/identicon.js b/todofeed/front-todofeed/src/util/identicon.js
new file mode 100644
index 0000000..a3ff47a
--- /dev/null
+++ b/todofeed/front-todofeed/src/util/identicon.js
@@ -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}
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/util/imageCompression.js b/todofeed/front-todofeed/src/util/imageCompression.js
new file mode 100644
index 0000000..7672a71
--- /dev/null
+++ b/todofeed/front-todofeed/src/util/imageCompression.js
@@ -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
\ No newline at end of file
diff --git a/todofeed/front-todofeed/src/util/imageUtils.js b/todofeed/front-todofeed/src/util/imageUtils.js
new file mode 100644
index 0000000..7db5a21
--- /dev/null
+++ b/todofeed/front-todofeed/src/util/imageUtils.js
@@ -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
+}
diff --git a/todofeed/front-todofeed/vue.config.js b/todofeed/front-todofeed/vue.config.js
new file mode 100644
index 0000000..ef6e86b
--- /dev/null
+++ b/todofeed/front-todofeed/vue.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ "transpileDependencies": [
+ "vuetify"
+ ]
+}
\ No newline at end of file
diff --git a/todofeed/frontend-todofeed b/todofeed/frontend-todofeed
deleted file mode 160000
index 56352fc..0000000
--- a/todofeed/frontend-todofeed
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 56352fc7d38779d97d4fbb361a9dc336a2b8650c