Added counts to each category

Counts update on certain events and show or hide various elements
Fixed various little ui element issues

fixes #6
This commit is contained in:
Max G 2020-02-11 21:11:14 +00:00
parent c903bcbcd1
commit 771b739c37
14 changed files with 169 additions and 39 deletions

View File

@ -13,6 +13,7 @@
<script> <script>
// import io from 'socket.io-client' // import io from 'socket.io-client'
import axios from 'axios'
export default { export default {
components: { components: {
@ -50,7 +51,6 @@ export default {
}, },
mounted: function(){ mounted: function(){
}, },
computed: { computed: {
loggedIn () { loggedIn () {

View File

@ -28,7 +28,7 @@
} }
html { html {
scrollbar-width: none; /*scrollbar-width: none;*/
} }
div.ui.basic.segment.no-fluf-segment { div.ui.basic.segment.no-fluf-segment {

View File

@ -192,6 +192,7 @@
this.unfolded = false this.unfolded = false
setTimeout( () => { setTimeout( () => {
this.visible = false this.visible = false
this.$store.dispatch('fetchAndUpdateUserTotals')
}, 600) }, 600)
} }
}) })

View File

@ -68,6 +68,7 @@
const imageCode = `<img alt="image" src="/api/static/thumb_${location}">` const imageCode = `<img alt="image" src="/api/static/thumb_${location}">`
this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode}) this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode})
this.$store.dispatch('fetchAndUpdateUserTotals')
} }
}) })
.catch(results => { .catch(results => {

View File

@ -154,6 +154,7 @@
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<router-link exact-active-class="active" class="menu-item menu-button" to="/notes" v-on:click.native="emitReloadEvent()"> <router-link exact-active-class="active" class="menu-item menu-button" to="/notes" v-on:click.native="emitReloadEvent()">
<i class="file outline icon"></i>Notes <i class="file outline icon"></i>Notes
<!-- <span v-if="$store.getters.totals">{{ $store.getters.totals['totalNotes'] }}</span> -->
</router-link> </router-link>
<div> <div>
<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> --> <!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> -->
@ -163,21 +164,10 @@
</div> </div>
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn && $store.getters.totals && $store.getters.totals['totalFiles']">
<div v-on:click="updateFastFilters(3)" class="menu-item menu-button">
<i class="mail outline icon"></i>Inbox
</div>
</div>
<div class="menu-section" v-if="loggedIn">
<div v-on:click="updateFastFilters(2)" class="menu-item menu-button">
<i class="hdd outline icon"></i>Archived
</div>
</div>
<div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/attachments"> <router-link class="menu-item menu-button" exact-active-class="active" to="/attachments">
<i class="folder open outline icon"></i>Files <i class="folder open outline icon"></i>Files
<!-- <span>{{ $store.getters.totals['totalFiles'] }}</span> -->
</router-link> </router-link>
</div> </div>
@ -242,6 +232,8 @@
this.mobile = this.$store.getters.getIsUserOnMobile this.mobile = this.$store.getters.getIsUserOnMobile
this.collapsed = this.$store.getters.getIsUserOnMobile this.collapsed = this.$store.getters.getIsUserOnMobile
// {{ totals['totalNotes'] }}
if(this.mobile){ if(this.mobile){
this.menuOpen = false this.menuOpen = false
} }

View File

@ -30,6 +30,7 @@
axios.post('/api/note/delete', {'noteId':this.noteId}).then(response => { axios.post('/api/note/delete', {'noteId':this.noteId}).then(response => {
if(response.data == true){ if(response.data == true){
this.$bus.$emit('note_deleted', this.noteId) this.$bus.$emit('note_deleted', this.noteId)
this.$store.dispatch('fetchAndUpdateUserTotals')
} }
}) })
}, },

View File

@ -202,6 +202,7 @@
loading: true, loading: true,
loadingMessage: 'Loading Note', loadingMessage: 'Loading Note',
currentNoteId: 0, currentNoteId: 0,
modified: false,
noteText: '', noteText: '',
rawTextId: 0, rawTextId: 0,
shareUsername: null, shareUsername: null,
@ -873,21 +874,16 @@
'archived':this.archived, 'archived':this.archived,
} }
let vm = this this.statusText = 'Saving'
//Debounce save to prevent spamming axios.post('/api/note/update', postData).then( response => {
// clearTimeout(this.saveDebounce) this.statusText = 'Saved'
// this.saveDebounce = setTimeout(() => { this.updated = Math.round((+new Date)/1000)
//Only notify user if saving - may help with debugging in the future this.modified = true
vm.statusText = 'Saving'
axios.post('/api/note/update', postData).then( response => {
vm.statusText = 'Saved'
vm.updated = Math.round((+new Date)/1000)
//Update last saved note hash //Update last saved note hash
vm.lastNoteHash = vm.hashString( currentNoteText ) this.lastNoteHash = this.hashString( currentNoteText )
return resolve(true) return resolve(true)
}) })
// }, 300)
}) })
}, },
checkForUpdatedNote(){ checkForUpdatedNote(){
@ -949,7 +945,9 @@
this.sizeDown = true this.sizeDown = true
//This timeout allows animation to play before closing //This timeout allows animation to play before closing
setTimeout(() => { setTimeout(() => {
this.$bus.$emit('close_active_note', {position: this.position, noteId: this.noteid}) this.$bus.$emit('close_active_note', {
position: this.position, noteId: this.noteid, modified: this.modified
})
return return
}, 300) }, 300)
}) })

View File

@ -1,6 +1,9 @@
<template> <template>
<div class="ui form"> <div class="ui form">
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes and Files" /> <div class="ui left icon fluid input">
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes and Files" />
<i class="search icon"></i>
</div>
</div> </div>
</template> </template>

View File

@ -128,7 +128,6 @@
if(this.styleObject && this.styleObject.noteBackground){ if(this.styleObject && this.styleObject.noteBackground){
this.bgColor = this.styleObject.noteBackground this.bgColor = this.styleObject.noteBackground
console.log(this.bgColor)
} }

View File

@ -3,21 +3,39 @@
<div class="ui grid" :class="{ 'mush-it-up':showOneColumn() }" ref="content"> <div class="ui grid" :class="{ 'mush-it-up':showOneColumn() }" ref="content">
<div v-if="!$store.getters.getIsUserOnMobile" <div v-if="!$store.getters.getIsUserOnMobile" class="sixteen wide column">
:class="{ 'sixteen wide column':showOneColumn(), 'twelve wide column':!showOneColumn() }"> <!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" -->
<div class="ui grid"> <div class="ui grid">
<div class="eight wide column"> <div class="eight wide column">
<search-input></search-input> <search-input></search-input>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui basic button" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && $store.getters.totals['sharedToNotes'] > 0" style="position: relative;">
<i class="green mail icon"></i>Inbox
<span class="floating ui green label" v-if="$store.getters.totals['unreadNotes'] > 0">
{{ $store.getters.totals['unreadNotes'] }}
</span>
</div>
<div class="ui basic button" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0">
<i class="green archive icon"></i>Archived
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
</div>
</div>
<div class="eight wide column" v-if="showClear">
<!-- <fast-filters /> --> <!-- <fast-filters /> -->
<span class="ui fluid green button" <span class="ui fluid green button"
v-if="showClear"
@click="reset"> @click="reset">
<i class="arrow circle left icon"></i>Back to All Notes <i class="arrow circle left icon"></i>Back to All Notes
</span> </span>
</div> </div>
</div> </div>
</div> </div>
@ -64,7 +82,7 @@
<!-- pinned notes --> <!-- pinned notes -->
<div v-if="containsPinnednotes > 0" class="note-card-section"> <div v-if="containsPinnednotes > 0" class="note-card-section">
<!-- ({{containsPinnednotes}}) --> <!-- ({{containsPinnednotes}}) -->
<h4><i class="green pin icon"></i>Pinned <span v-if="!working"></span></h4> <h4><i class="green pin icon"></i>Pinned</h4>
<div class="note-card-display-area"> <div class="note-card-display-area">
<note-title-display-card <note-title-display-card
v-for="note in notes" v-for="note in notes"
@ -80,7 +98,7 @@
<!-- normal notes --> <!-- normal notes -->
<div v-if="containsNormalNotes > 0" class="note-card-section"> <div v-if="containsNormalNotes > 0" class="note-card-section">
<!-- ({{containsNormalNotes}}) --> <!-- ({{containsNormalNotes}}) -->
<h4><i class="green file icon"></i>Notes <span v-if="!working"></span></h4> <h4><i class="green file icon"></i>Notes</h4>
<div class="note-card-display-area"> <div class="note-card-display-area">
<note-title-display-card <note-title-display-card
v-for="note in notes" v-for="note in notes"
@ -194,9 +212,16 @@
this.$parent.loginGateway() this.$parent.loginGateway()
this.$bus.$on('close_active_note', ({position, noteId}) => { //Update totals for app
this.$store.dispatch('fetchAndUpdateUserTotals')
this.$bus.$on('close_active_note', ({position, noteId, modified}) => {
this.closeNote(position) this.closeNote(position)
this.updateSingleNote(noteId) this.$store.dispatch('fetchAndUpdateUserTotals')
if(modified){
this.updateSingleNote(noteId)
}
}) })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
//Remove deleted note from set, its deleted //Remove deleted note from set, its deleted
@ -265,6 +290,7 @@
// this.$bus.$off() // this.$bus.$off()
}, },
mounted() { mounted() {
//Loads initial batch and tags //Loads initial batch and tags
this.reset() this.reset()
}, },
@ -634,6 +660,31 @@
resolve(data) resolve(data)
}) })
}) })
},
updateFastFilters(index){
//clear out tags
this.searchTags = []
//A little hacky, brings user to notes page then filters on click
if(this.$route.name != 'NotesPage'){
this.$router.push('/notes')
setTimeout( () => {
this.updateFastFilters(index)
}, 500 )
}
const options = [
'withLinks', // 'Only Show Notes with Links'
'withTags', // 'Only Show Notes with Tags'
'onlyArchived', //'Only Show Archived Notes'
'onlyShowSharedNotes', //Only show shared notes
]
let filter = {}
filter[options[index]] = 1
this.$bus.$emit('update_fast_filters', filter)
} }
} }
} }

View File

@ -12,6 +12,7 @@ export default new Vuex.Store({
isUserOnMobile: false, isUserOnMobile: false,
isNoteSettingsOpen: false, //Little note settings pane isNoteSettingsOpen: false, //Little note settings pane
socket: null, socket: null,
userTotals: null,
}, },
mutations: { mutations: {
setLoginToken(state, userData){ setLoginToken(state, userData){
@ -88,6 +89,14 @@ export default new Vuex.Store({
//Put socket id in axios headers //Put socket id in axios headers
axios.defaults.headers.common['socketId'] = socket axios.defaults.headers.common['socketId'] = socket
state.socket = socket state.socket = socket
},
setUserTotals(state, totalsObject){
//Save all the totals for the user
state.userTotals = totalsObject
// Object.keys(totalsObject).forEach( key => {
// console.log(key + ' -- ' + totalsObject[key])
// })
} }
}, },
@ -114,5 +123,16 @@ export default new Vuex.Store({
getSocket: state => { getSocket: state => {
return state.socket return state.socket
}, },
totals: state => {
return state.userTotals
},
},
actions: {
fetchAndUpdateUserTotals ({ commit }) {
axios.post('/api/user/totals')
.then( ({data}) => {
commit('setUserTotals', data)
})
}
} }
}) })

View File

@ -334,6 +334,10 @@ Note.get = (userId, noteId) => {
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId]) WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId])
.then((rows, fields) => { .then((rows, fields) => {
const created = Math.round((+new Date)/1000)
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [created, noteId])
//Return note data //Return note data
resolve(rows[0][0]) resolve(rows[0][0])

View File

@ -113,3 +113,56 @@ User.create = (username, password) => {
}) })
} }
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
User.getCounts = (userId) => {
return new Promise((resolve, reject) => {
let countTotals = {}
db.promise().query(
`SELECT
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes,
SUM(pinned = 0 && archived = 1 && share_user_id IS NULL) AS archivedNotes,
SUM(share_user_id IS NULL) AS totalNotes,
SUM(share_user_id != ?) AS sharedToNotes,
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && updated > opened) ) AS unreadNotes
FROM note
WHERE user_id = ?`, [userId, userId, userId, userId])
.then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
return db.promise().query(
`SELECT count(id) AS sharedFromNotes
FROM note WHERE share_user_id = ?`, [userId]
)
})
.then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
return db.promise().query(
`SELECT
SUM(attachment_type = 1) as linkFiles,
SUM(attachment_type != 1) as otherFiles,
COUNT(id) as totalFiles
FROM attachment WHERE visible = 1
AND user_id = ?
`, [userId]
)
}).then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
//Convert everything to an int or 0
Object.keys(countTotals).forEach( key => {
const count = parseInt(countTotals[key])
countTotals[key] = count ? count : 0
})
resolve(countTotals)
})
})
}

View File

@ -46,4 +46,11 @@ router.post('/login', function (req, res) {
}) })
}) })
// fetch counts of users notes
router.post('/totals', function (req, res) {
User.getCounts(req.headers.userId)
.then( countsObject => res.send( countsObject ))
})
module.exports = router module.exports = router