380 lines
10 KiB
Vue
380 lines
10 KiB
Vue
<!--
|
|
- @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
|
|
-
|
|
- @author John Molakvoæ <skjnldsv@protonmail.com>
|
|
-
|
|
- @license GNU AGPL version 3 or any later version
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as
|
|
- published by the Free Software Foundation, either version 3 of the
|
|
- License, or (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
-
|
|
-->
|
|
|
|
<template>
|
|
<AppNavigation>
|
|
<slot />
|
|
|
|
<!-- groups list -->
|
|
<template v-if="!loading" #list>
|
|
<!-- All contacts group -->
|
|
<AppNavigationItem id="everyone"
|
|
:title="GROUP_ALL_CONTACTS"
|
|
:to="{
|
|
name: 'group',
|
|
params: { selectedGroup: GROUP_ALL_CONTACTS },
|
|
}"
|
|
icon="icon-contacts-dark">
|
|
<AppNavigationCounter v-if="sortedContacts.length" slot="counter">
|
|
{{ sortedContacts.length }}
|
|
</AppNavigationCounter>
|
|
</AppNavigationItem>
|
|
|
|
<!-- Not grouped group -->
|
|
<AppNavigationItem
|
|
v-if="ungroupedContacts.length > 0"
|
|
id="notgrouped"
|
|
:title="GROUP_NO_GROUP_CONTACTS"
|
|
:to="{
|
|
name: 'group',
|
|
params: { selectedGroup: GROUP_NO_GROUP_CONTACTS },
|
|
}"
|
|
icon="icon-user">
|
|
<AppNavigationCounter v-if="ungroupedContacts.length" slot="counter">
|
|
{{ ungroupedContacts.length }}
|
|
</AppNavigationCounter>
|
|
</AppNavigationItem>
|
|
|
|
<!-- Recently contacted group -->
|
|
<AppNavigationItem
|
|
v-if="isContactsInteractionEnabled && recentlyContactedContacts && recentlyContactedContacts.contacts.length > 0"
|
|
id="recentlycontacted"
|
|
:title="GROUP_RECENTLY_CONTACTED"
|
|
:to="{
|
|
name: 'group',
|
|
params: { selectedGroup: GROUP_RECENTLY_CONTACTED },
|
|
}"
|
|
icon="icon-recent-actors">
|
|
<AppNavigationCounter v-if="recentlyContactedContacts.contacts.length" slot="counter">
|
|
{{ recentlyContactedContacts.contacts.length }}
|
|
</AppNavigationCounter>
|
|
</AppNavigationItem>
|
|
|
|
<AppNavigationItem
|
|
id="newgroup"
|
|
:force-menu="true"
|
|
:menu-open.sync="isNewGroupMenuOpen"
|
|
:title="t('contacts', 'Groups')"
|
|
menu-icon="icon-add"
|
|
@click.prevent.stop="toggleNewGroupMenu">
|
|
<template slot="actions">
|
|
<ActionText :icon="createGroupError ? 'icon-error' : 'icon-contacts-dark'">
|
|
{{ createGroupError ? createGroupError : t('contacts', 'Create a new group') }}
|
|
</ActionText>
|
|
<ActionInput
|
|
icon=""
|
|
:placeholder="t('contacts','Group name')"
|
|
@submit.prevent.stop="createNewGroup" />
|
|
</template>
|
|
</AppNavigationItem>
|
|
|
|
<!-- Custom groups -->
|
|
<GroupNavigationItem
|
|
v-for="group in ellipsisGroupsMenu"
|
|
:key="group.key"
|
|
:group="group" />
|
|
|
|
<!-- Toggle groups ellipsis -->
|
|
<AppNavigationItem
|
|
v-if="groupsMenu.length > ELLIPSIS_COUNT"
|
|
:title="collapseGroupsTitle"
|
|
class="app-navigation__collapse"
|
|
icon=""
|
|
@click="onToggleGroups" />
|
|
|
|
<AppNavigationItem
|
|
id="newcircle"
|
|
:force-menu="true"
|
|
:menu-open.sync="isNewCircleMenuOpen"
|
|
:title="t('contacts', 'Circles')"
|
|
menu-icon="icon-add"
|
|
@click.prevent.stop="toggleNewCircleMenu">
|
|
<template slot="actions">
|
|
<ActionText :icon="createCircleError ? 'icon-error' : 'icon-contacts-dark'">
|
|
{{ createCircleError ? createCircleError : t('contacts', 'Create a new circle') }}
|
|
</ActionText>
|
|
<ActionInput
|
|
icon=""
|
|
:placeholder="t('contacts','Circle name')"
|
|
@submit.prevent.stop="createNewCircle" />
|
|
</template>
|
|
</AppNavigationItem>
|
|
|
|
<!-- Circles -->
|
|
<CircleNavigationItem
|
|
v-for="circle in ellipsisCirclesMenu"
|
|
:key="circle.key"
|
|
:circle="circle" />
|
|
|
|
<!-- Toggle circles ellipsis -->
|
|
<AppNavigationItem
|
|
v-if="circlesMenu.length > ELLIPSIS_COUNT"
|
|
:title="collapseCirclesTitle"
|
|
class="app-navigation__collapse"
|
|
icon=""
|
|
@click="onToggleCircles" />
|
|
</template>
|
|
|
|
<!-- settings -->
|
|
<template #footer>
|
|
<AppNavigationSettings v-if="!loading">
|
|
<SettingsSection />
|
|
</AppNavigationSettings>
|
|
</template>
|
|
</AppNavigation>
|
|
</template>
|
|
|
|
<script>
|
|
import { GROUP_ALL_CONTACTS, GROUP_NO_GROUP_CONTACTS, GROUP_RECENTLY_CONTACTED, ELLIPSIS_COUNT } from '../../models/constants.ts'
|
|
|
|
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
|
|
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
|
|
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
|
import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter'
|
|
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
|
|
import AppNavigationSettings from '@nextcloud/vue/dist/Components/AppNavigationSettings'
|
|
|
|
import naturalCompare from 'string-natural-compare'
|
|
|
|
import CircleNavigationItem from './CircleNavigationItem'
|
|
import GroupNavigationItem from './GroupNavigationItem'
|
|
import SettingsSection from './SettingsSection'
|
|
import isContactsInteractionEnabled from '../../services/isContactsInteractionEnabled'
|
|
import RouterMixin from '../../mixins/RouterMixin'
|
|
|
|
export default {
|
|
name: 'RootNavigation',
|
|
|
|
components: {
|
|
ActionInput,
|
|
ActionText,
|
|
AppNavigation,
|
|
AppNavigationCounter,
|
|
AppNavigationItem,
|
|
AppNavigationSettings,
|
|
CircleNavigationItem,
|
|
GroupNavigationItem,
|
|
SettingsSection,
|
|
},
|
|
|
|
mixins: [RouterMixin],
|
|
|
|
props: {
|
|
loading: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
|
|
contactsList: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
ELLIPSIS_COUNT,
|
|
GROUP_ALL_CONTACTS,
|
|
GROUP_NO_GROUP_CONTACTS,
|
|
GROUP_RECENTLY_CONTACTED,
|
|
|
|
// create group
|
|
isNewGroupMenuOpen: false,
|
|
createGroupError: null,
|
|
|
|
// create circle
|
|
isNewCircleMenuOpen: false,
|
|
createCircleError: null,
|
|
|
|
isContactsInteractionEnabled,
|
|
|
|
collapsedGroups: true,
|
|
collapsedCircles: true,
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
// store variables
|
|
circles() {
|
|
return this.$store.getters.getCircles
|
|
},
|
|
contacts() {
|
|
return this.$store.getters.getContacts
|
|
},
|
|
groups() {
|
|
return this.$store.getters.getGroups
|
|
},
|
|
sortedContacts() {
|
|
return this.$store.getters.getSortedContacts
|
|
},
|
|
|
|
// list all the contacts that doesn't have a group
|
|
ungroupedContacts() {
|
|
return this.sortedContacts.filter(contact => this.contacts[contact.key].groups && this.contacts[contact.key].groups.length === 0)
|
|
},
|
|
|
|
// generate groups menu from the groups store
|
|
groupsMenu() {
|
|
const menu = this.groups.map(group => {
|
|
return Object.assign({}, group, {
|
|
id: group.name.replace(' ', '_'),
|
|
key: group.name.replace(' ', '_'),
|
|
router: {
|
|
name: 'group',
|
|
params: { selectedGroup: group.name },
|
|
},
|
|
toString: () => group.name,
|
|
})
|
|
})
|
|
menu.sort((a, b) => naturalCompare(a.toString(), b.toString(), { caseInsensitive: true }))
|
|
|
|
// Find the Recently Contacted group, delete it from array
|
|
const recentlyIndex = menu.findIndex(group => group.name === GROUP_RECENTLY_CONTACTED)
|
|
if (recentlyIndex >= 0) {
|
|
menu.splice(recentlyIndex, 1)
|
|
}
|
|
|
|
return menu
|
|
},
|
|
ellipsisGroupsMenu() {
|
|
if (this.collapsedGroups) {
|
|
return this.groupsMenu.slice(0, ELLIPSIS_COUNT)
|
|
}
|
|
return this.groupsMenu
|
|
},
|
|
|
|
// generate circles menu from the circles store
|
|
circlesMenu() {
|
|
const menu = this.circles
|
|
menu.sort((a, b) => naturalCompare(a.toString(), b.toString(), { caseInsensitive: true }))
|
|
|
|
return menu
|
|
},
|
|
ellipsisCirclesMenu() {
|
|
if (this.collapsedCircles) {
|
|
return this.circlesMenu.slice(0, ELLIPSIS_COUNT)
|
|
}
|
|
return this.circlesMenu
|
|
},
|
|
|
|
// Recently contacted data
|
|
recentlyContactedContacts() {
|
|
return this.groups.find(group => group.name === GROUP_RECENTLY_CONTACTED)
|
|
},
|
|
|
|
// Titles for the ellipsis toggle buttons
|
|
collapseGroupsTitle() {
|
|
return this.collapsedGroups
|
|
? t('contacts', 'Show all groups')
|
|
: t('contacts', 'Collapse groups')
|
|
},
|
|
collapseCirclesTitle() {
|
|
return this.collapsedCircles
|
|
? t('contacts', 'Show all circles')
|
|
: t('contacts', 'Collapse circles')
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
toggleNewGroupMenu() {
|
|
this.isNewGroupMenuOpen = !this.isNewGroupMenuOpen
|
|
},
|
|
createNewGroup(e) {
|
|
const input = e.target.querySelector('input[type=text]')
|
|
const groupName = input.value.trim()
|
|
console.debug('Creating new group', groupName)
|
|
|
|
// Check if already exists
|
|
if (this.groups.find(group => group.name === groupName)) {
|
|
this.createGroupError = t('contacts', 'This group already exists')
|
|
return
|
|
}
|
|
|
|
this.createGroupError = null
|
|
|
|
console.debug('Created new local group', groupName)
|
|
this.$store.dispatch('addGroup', groupName)
|
|
this.isNewGroupMenuOpen = false
|
|
|
|
// Select group
|
|
this.$router.push({
|
|
name: 'group',
|
|
params: {
|
|
selectedGroup: groupName,
|
|
},
|
|
})
|
|
},
|
|
|
|
// Ellipsis item toggles
|
|
onToggleGroups() {
|
|
this.collapsedGroups = !this.collapsedGroups
|
|
},
|
|
onToggleCircles() {
|
|
this.collapsedCircles = !this.collapsedCircles
|
|
},
|
|
|
|
toggleNewCircleMenu() {
|
|
this.isNewCircleMenuOpen = !this.isNewCircleMenuOpen
|
|
},
|
|
async createNewCircle(e) {
|
|
const input = e.target.querySelector('input[type=text]')
|
|
const circleName = input.value.trim()
|
|
console.debug('Creating new circle', circleName)
|
|
|
|
// Check if already exists
|
|
if (this.circles.find(circle => circle.name === circleName)) {
|
|
this.createGroupError = t('contacts', 'This circle already exists')
|
|
return
|
|
}
|
|
|
|
this.createCircleError = null
|
|
|
|
const circle = await this.$store.dispatch('createCircle', circleName)
|
|
this.isNewCircleMenuOpen = false
|
|
|
|
// Select group
|
|
this.$router.push({
|
|
name: 'circle',
|
|
params: {
|
|
selectedCircle: circle.id,
|
|
},
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
#newgroup,
|
|
#newcircle {
|
|
margin-top: 22px;
|
|
|
|
::v-deep a {
|
|
color: var(--color-text-maxcontrast)
|
|
}
|
|
}
|
|
|
|
.app-navigation__collapse ::v-deep a {
|
|
color: var(--color-text-maxcontrast)
|
|
}
|
|
</style>
|