mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-29 06:49:55 +00:00
New left sidebar
This commit is contained in:
parent
00ae305039
commit
04e1a69c03
55 changed files with 1651 additions and 1751 deletions
changelog/entries/unreleased/feature
enterprise/web-frontend/modules/baserow_enterprise/components
premium/web-frontend
web-frontend
locales
modules
builder
core
adminTypes.jsapplicationTypes.js
assets
icons
scss
components
Modal.vue
jobTypes.jssidebar
Sidebar.vueSidebarAdmin.vueSidebarApplication.vueSidebarApplicationPendingJob.vueSidebarFoot.vueSidebarMenu.vueSidebarUserContext.vueSidebarWithWorkspace.vueSidebarWithoutWorkspace.vue
template
workspace
layouts
locales
plugin.jsplugins.jsstore
database
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "feature",
|
||||||
|
"message": "Redesigned the left sidebar.",
|
||||||
|
"issue_number": null,
|
||||||
|
"bullet_points": [],
|
||||||
|
"created_at": "2024-07-28"
|
||||||
|
}
|
|
@ -1,11 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<li
|
<nuxt-link
|
||||||
v-if="hasPermission"
|
v-if="hasPermission"
|
||||||
|
v-slot="{ href, navigate, isExactActive }"
|
||||||
|
:to="{
|
||||||
|
name: 'workspace-audit-log',
|
||||||
|
params: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<li
|
||||||
class="tree__item"
|
class="tree__item"
|
||||||
:class="{
|
:class="{
|
||||||
'tree__item--loading': loading,
|
'tree__item--loading': loading,
|
||||||
'tree__action--deactivated': deactivated,
|
'tree__action--deactivated': deactivated,
|
||||||
active: $route.matched.some(({ name }) => name === 'workspace-audit-log'),
|
active: isExactActive,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="tree__action">
|
<div class="tree__action">
|
||||||
|
@ -15,25 +24,17 @@
|
||||||
class="tree__link"
|
class="tree__link"
|
||||||
@click.prevent="$refs.enterpriseModal.show()"
|
@click.prevent="$refs.enterpriseModal.show()"
|
||||||
>
|
>
|
||||||
<i class="tree__icon tree__icon--type iconoir-lock"></i>
|
<i class="tree__icon iconoir-lock"></i>
|
||||||
<span class="tree__link-text">{{
|
<span class="tree__link-text">{{
|
||||||
$t('auditLogSidebarWorkspace.title')
|
$t('auditLogSidebarWorkspace.title')
|
||||||
}}</span>
|
}}</span>
|
||||||
</a>
|
</a>
|
||||||
<nuxt-link
|
<a v-else :href="href" class="tree__link" @click="navigate">
|
||||||
v-else
|
<i class="tree__icon baserow-icon-history"></i>
|
||||||
:event="!hasPermission ? null : 'click'"
|
|
||||||
class="tree__link"
|
|
||||||
:to="{
|
|
||||||
name: 'workspace-audit-log',
|
|
||||||
params: { workspaceId: workspace.id },
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<i class="tree__icon tree__icon--type baserow-icon-history"></i>
|
|
||||||
<span class="tree__link-text">{{
|
<span class="tree__link-text">{{
|
||||||
$t('auditLogSidebarWorkspace.title')
|
$t('auditLogSidebarWorkspace.title')
|
||||||
}}</span>
|
}}</span>
|
||||||
</nuxt-link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<EnterpriseModal
|
<EnterpriseModal
|
||||||
ref="enterpriseModal"
|
ref="enterpriseModal"
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
:name="$t('auditLogSidebarWorkspace.title')"
|
:name="$t('auditLogSidebarWorkspace.title')"
|
||||||
></EnterpriseModal>
|
></EnterpriseModal>
|
||||||
</li>
|
</li>
|
||||||
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
class="tree__link"
|
class="tree__link"
|
||||||
@click.prevent="$refs.enterpriseModal.show()"
|
@click.prevent="$refs.enterpriseModal.show()"
|
||||||
>
|
>
|
||||||
<i class="tree__icon tree__icon--type iconoir-lock"></i>
|
<i class="tree__icon iconoir-lock"></i>
|
||||||
<span class="tree__link-text">{{
|
<span class="tree__link-text">{{
|
||||||
$t('chatwootSupportSidebarWorkspace.directSupport')
|
$t('chatwootSupportSidebarWorkspace.directSupport')
|
||||||
}}</span>
|
}}</span>
|
||||||
</a>
|
</a>
|
||||||
<a v-else class="tree__link" @click="open">
|
<a v-else class="tree__link" @click="open">
|
||||||
<i class="tree__icon tree__icon--type iconoir-chat-bubble-question"></i>
|
<i class="tree__icon iconoir-chat-bubble-question"></i>
|
||||||
<span class="tree__link-text">{{
|
<span class="tree__link-text">{{
|
||||||
$t('chatwootSupportSidebarWorkspace.directSupport')
|
$t('chatwootSupportSidebarWorkspace.directSupport')
|
||||||
}}</span>
|
}}</span>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class DashboardType extends PremiumAdminType {
|
||||||
}
|
}
|
||||||
|
|
||||||
getIconClass() {
|
getIconClass() {
|
||||||
return 'iconoir-candlestick-chart'
|
return 'iconoir-home-simple'
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
@ -49,6 +49,11 @@ export class UsersAdminType extends PremiumAdminType {
|
||||||
return i18n.t('premium.adminType.users')
|
return i18n.t('premium.adminType.users')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCategory() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('sidebar.people')
|
||||||
|
}
|
||||||
|
|
||||||
getRouteName() {
|
getRouteName() {
|
||||||
return 'admin-users'
|
return 'admin-users'
|
||||||
}
|
}
|
||||||
|
@ -64,7 +69,7 @@ export class WorkspacesAdminType extends PremiumAdminType {
|
||||||
}
|
}
|
||||||
|
|
||||||
getIconClass() {
|
getIconClass() {
|
||||||
return 'iconoir-book-stack'
|
return 'baserow-icon-groups'
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
@ -72,6 +77,11 @@ export class WorkspacesAdminType extends PremiumAdminType {
|
||||||
return i18n.t('premium.adminType.workspaces')
|
return i18n.t('premium.adminType.workspaces')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCategory() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('sidebar.people')
|
||||||
|
}
|
||||||
|
|
||||||
getRouteName() {
|
getRouteName() {
|
||||||
return 'admin-workspaces'
|
return 'admin-workspaces'
|
||||||
}
|
}
|
||||||
|
@ -95,6 +105,11 @@ export class LicensesAdminType extends AdminType {
|
||||||
return i18n.t('premium.adminType.licenses')
|
return i18n.t('premium.adminType.licenses')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCategory() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('sidebar.licenses')
|
||||||
|
}
|
||||||
|
|
||||||
getRouteName() {
|
getRouteName() {
|
||||||
return 'admin-licenses'
|
return 'admin-licenses'
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
@import 'impersonate_warning';
|
@import 'impersonate_warning';
|
||||||
@import 'views/conditional_color_value_provider_form';
|
@import 'views/conditional_color_value_provider_form';
|
||||||
@import 'redirect-modal';
|
@import 'redirect-modal';
|
||||||
@import 'top_sidebar';
|
|
||||||
@import 'instance_wide_license';
|
|
||||||
@import 'form_view_survey';
|
@import 'form_view_survey';
|
||||||
@import 'views/calendar/all';
|
@import 'views/calendar/all';
|
||||||
@import 'active_users';
|
@import 'active_users';
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
.instance-wide-license {
|
|
||||||
@include absolute(4px, 4px, auto, auto);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
.premium-top-sidebar {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.layout--collapsed & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@
|
||||||
ref="license"
|
ref="license"
|
||||||
v-model="values.license"
|
v-model="values.license"
|
||||||
:error="fieldHasErrors('license')"
|
:error="fieldHasErrors('license')"
|
||||||
rows="6"
|
:rows="6"
|
||||||
@blur="$v.values.license.$touch()"
|
@blur="$v.values.license.$touch()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<Badge
|
||||||
|
v-if="highestLicenseType && highestLicenseType.showInTopSidebarWhenActive()"
|
||||||
|
v-tooltip="highestLicenseType.getTopSidebarTooltip()"
|
||||||
|
:color="highestLicenseType.getLicenseBadgeColor()"
|
||||||
|
bold
|
||||||
|
>
|
||||||
|
{{ highestLicenseType.getName() }}</Badge
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'HighestLicenseTypeBadge',
|
||||||
|
computed: {
|
||||||
|
highestLicenseType() {
|
||||||
|
return this.$highestLicenseType()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="premium-top-sidebar">
|
<div class="sidebar__impersonate">
|
||||||
<div v-if="impersonating" class="impersonate-warning">
|
<div v-if="impersonating" class="impersonate-warning">
|
||||||
{{ $t('premiumTopSidebar.impersonateDescription') }}
|
{{ $t('premiumTopSidebar.impersonateDescription') }}
|
||||||
<div>
|
<div>
|
||||||
|
@ -16,18 +16,6 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Badge
|
|
||||||
v-if="
|
|
||||||
highestLicenseType && highestLicenseType.showInTopSidebarWhenActive()
|
|
||||||
"
|
|
||||||
v-tooltip="highestLicenseType.getTopSidebarTooltip()"
|
|
||||||
:color="highestLicenseType.getLicenseBadgeColor()"
|
|
||||||
class="instance-wide-license"
|
|
||||||
bold
|
|
||||||
>
|
|
||||||
{{ highestLicenseType.getName() }}</Badge
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -35,7 +23,7 @@
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PremiumTopSidebar',
|
name: 'Impersonate',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -45,9 +33,6 @@ export default {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
impersonating: 'impersonating/getImpersonating',
|
impersonating: 'impersonating/getImpersonating',
|
||||||
}),
|
}),
|
||||||
highestLicenseType() {
|
|
||||||
return this.$highestLicenseType()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolveAdminUsersHref() {
|
resolveAdminUsersHref() {
|
|
@ -10,7 +10,7 @@
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
"workspaces": "Workspaces",
|
"workspaces": "Workspaces",
|
||||||
"licenses": "Licenses"
|
"licenses": "Manage licenses"
|
||||||
},
|
},
|
||||||
"viewType": {
|
"viewType": {
|
||||||
"kanban": "Kanban",
|
"kanban": "Kanban",
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
"creating": "creating",
|
"creating": "creating",
|
||||||
"updating": "updating",
|
"updating": "updating",
|
||||||
"deleting": "deleting",
|
"deleting": "deleting",
|
||||||
"created" : "created",
|
"created": "created",
|
||||||
"edited": "edited",
|
"edited": "edited",
|
||||||
"commentTrashed": "This comment has been deleted.",
|
"commentTrashed": "This comment has been deleted.",
|
||||||
"errorUserNotCommentAuthorTitle": "Cannot update or delete.",
|
"errorUserNotCommentAuthorTitle": "Cannot update or delete.",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { BaserowPlugin } from '@baserow/modules/core/plugins'
|
import { BaserowPlugin } from '@baserow/modules/core/plugins'
|
||||||
import PremiumTopSidebar from '@baserow_premium/components/sidebar/PremiumTopSidebar'
|
import Impersonate from '@baserow_premium/components/sidebar/Impersonate'
|
||||||
|
import HighestLicenseTypeBadge from '@baserow_premium/components/sidebar/HighestLicenseTypeBadge'
|
||||||
import BaserowLogoShareLinkOption from '@baserow_premium/components/views/BaserowLogoShareLinkOption'
|
import BaserowLogoShareLinkOption from '@baserow_premium/components/views/BaserowLogoShareLinkOption'
|
||||||
|
|
||||||
export class PremiumPlugin extends BaserowPlugin {
|
export class PremiumPlugin extends BaserowPlugin {
|
||||||
|
@ -7,8 +8,12 @@ export class PremiumPlugin extends BaserowPlugin {
|
||||||
return 'premium'
|
return 'premium'
|
||||||
}
|
}
|
||||||
|
|
||||||
getSidebarTopComponent() {
|
getImpersonateComponent() {
|
||||||
return PremiumTopSidebar
|
return Impersonate
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighestLicenseTypeBadge() {
|
||||||
|
return HighestLicenseTypeBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdditionalShareLinkOptions() {
|
getAdditionalShareLinkOptions() {
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
/**
|
|
||||||
* Various helper functions which interact with premium baserow components.
|
|
||||||
*/
|
|
||||||
export class PremiumUIHelpers {
|
|
||||||
static sidebarShowsPremiumEnabled(sidebarComponent) {
|
|
||||||
return sidebarComponent.find('.instance-wide-license').exists()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,192 +0,0 @@
|
||||||
import Sidebar from '@baserow/modules/core/components/sidebar/Sidebar'
|
|
||||||
import { PremiumTestApp } from '@baserow_premium_test/helpers/premiumTestApp'
|
|
||||||
import { PremiumUIHelpers } from '@baserow_premium_test/helpers/premiumUIHelpers'
|
|
||||||
import { UIHelpers } from '@baserow/test/helpers/testApp'
|
|
||||||
|
|
||||||
describe('Sidebar Premium Features Snapshot tests', () => {
|
|
||||||
let testApp = null
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
testApp = new PremiumTestApp()
|
|
||||||
testApp.createTestUserInAuthStore()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
testApp.afterEach()
|
|
||||||
})
|
|
||||||
|
|
||||||
test(
|
|
||||||
'When user does not have global premium enabled the sidebar does not show a' +
|
|
||||||
' premium badge',
|
|
||||||
async () => {
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(
|
|
||||||
PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)
|
|
||||||
).toBe(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
|
||||||
'When user does have global premium enabled the sidebar does show a premium' +
|
|
||||||
' badge',
|
|
||||||
async () => {
|
|
||||||
testApp.giveCurrentUserGlobalPremiumFeatures()
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(
|
|
||||||
PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)
|
|
||||||
).toBe(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test('A realtime update to global premium is reflected in the badge', async () => {
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)).toBe(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
testApp.giveCurrentUserGlobalPremiumFeatures()
|
|
||||||
await sidebarComponent.vm.$nextTick()
|
|
||||||
expect(PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)).toBe(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('When user is staff without global premium they dont see a premium badge', async () => {
|
|
||||||
testApp.updateCurrentUserToBecomeStaffMember()
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)).toBe(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('When user is staff with global premium they see a premium badge', async () => {
|
|
||||||
testApp.giveCurrentUserGlobalPremiumFeatures()
|
|
||||||
testApp.updateCurrentUserToBecomeStaffMember()
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(PremiumUIHelpers.sidebarShowsPremiumEnabled(sidebarComponent)).toBe(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('A non staff user cannot see admin settings links', async () => {
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(UIHelpers.getSidebarItemNames(sidebarComponent)).not.toContain(
|
|
||||||
'sidebar.admin'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('A non staff user with workspace premium cannot see admin settings links', async () => {
|
|
||||||
testApp.giveCurrentUserPremiumFeatureForSpecificWorkspaceOnly(1)
|
|
||||||
testApp
|
|
||||||
.getStore()
|
|
||||||
.dispatch('workspace/forceCreate', { id: 1, name: 'testWorkspace' })
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(UIHelpers.getSidebarItemNames(sidebarComponent)).not.toContain(
|
|
||||||
'sidebar.admin'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('A staff user can see admin settings links', async () => {
|
|
||||||
testApp.updateCurrentUserToBecomeStaffMember()
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(UIHelpers.getSidebarItemNames(sidebarComponent)).toContain(
|
|
||||||
'sidebar.admin'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('A staff user without global prem sees premium admin options locked', async () => {
|
|
||||||
testApp.updateCurrentUserToBecomeStaffMember()
|
|
||||||
testApp.setRouteToBe('admin-dashboard')
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await UIHelpers.selectSidebarItem(sidebarComponent, 'sidebar.admin')
|
|
||||||
expect(UIHelpers.getDisabledSidebarItemNames(sidebarComponent)).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
'premium.adminType.dashboard',
|
|
||||||
'premium.adminType.users',
|
|
||||||
'premium.adminType.workspaces',
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('A staff user with global prem sees premium admin options available', async () => {
|
|
||||||
const openedPage = 'sidebar.admin'
|
|
||||||
testApp.updateCurrentUserToBecomeStaffMember()
|
|
||||||
testApp.giveCurrentUserGlobalPremiumFeatures()
|
|
||||||
testApp.setRouteToBe('admin-dashboard')
|
|
||||||
|
|
||||||
const sidebarComponent = await testApp.mount(Sidebar, {
|
|
||||||
propsData: {
|
|
||||||
applications: [],
|
|
||||||
workspaces: [],
|
|
||||||
selectedWorkspace: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await UIHelpers.selectSidebarItem(sidebarComponent, openedPage)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
UIHelpers.getDisabledSidebarItemNames(sidebarComponent)
|
|
||||||
).toStrictEqual([])
|
|
||||||
const sidebarItemNames = UIHelpers.getSidebarItemNames(sidebarComponent)
|
|
||||||
expect(sidebarItemNames).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
openedPage,
|
|
||||||
'premium.adminType.dashboard',
|
|
||||||
'premium.adminType.users',
|
|
||||||
'premium.adminType.workspaces',
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -19,7 +19,7 @@
|
||||||
"signIn": "Sign in",
|
"signIn": "Sign in",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"createNew": "Create new",
|
"createNew": "Add new...",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"change": "Change",
|
"change": "Change",
|
||||||
|
@ -48,9 +48,11 @@
|
||||||
},
|
},
|
||||||
"applicationType": {
|
"applicationType": {
|
||||||
"database": "Database",
|
"database": "Database",
|
||||||
|
"databases": "Databases",
|
||||||
"databaseDefaultName": "Untitled Database",
|
"databaseDefaultName": "Untitled Database",
|
||||||
"databaseDesc": "Create an organized collection of structured data.",
|
"databaseDesc": "Create an organized collection of structured data.",
|
||||||
"builder": "Application",
|
"builder": "Application",
|
||||||
|
"builders": "Applications",
|
||||||
"builderDefaultName": "Untitled Application",
|
"builderDefaultName": "Untitled Application",
|
||||||
"builderDesc": "Easily build websites, web apps and portals without code.",
|
"builderDesc": "Easily build websites, web apps and portals without code.",
|
||||||
"cantSelectTableTitle": "Couldn't select the database.",
|
"cantSelectTableTitle": "Couldn't select the database.",
|
||||||
|
|
|
@ -12,7 +12,7 @@ export class BuilderApplicationType extends ApplicationType {
|
||||||
}
|
}
|
||||||
|
|
||||||
getIconClass() {
|
getIconClass() {
|
||||||
return 'iconoir-apple-imac-2021'
|
return 'baserow-icon-application'
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
@ -20,6 +20,11 @@ export class BuilderApplicationType extends ApplicationType {
|
||||||
return i18n.t('applicationType.builder')
|
return i18n.t('applicationType.builder')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNamePlural() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('applicationType.builders')
|
||||||
|
}
|
||||||
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
const { i18n } = this.app
|
const { i18n } = this.app
|
||||||
return i18n.t('applicationType.builderDesc')
|
return i18n.t('applicationType.builderDesc')
|
||||||
|
|
|
@ -8,10 +8,7 @@
|
||||||
>
|
>
|
||||||
<div class="tree__action">
|
<div class="tree__action">
|
||||||
<a class="tree__link" @click="$emit('selected', application)">
|
<a class="tree__link" @click="$emit('selected', application)">
|
||||||
<i
|
<i class="tree__icon" :class="application._.type.iconClass"></i>
|
||||||
class="tree__icon tree__icon--type"
|
|
||||||
:class="application._.type.iconClass"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">{{ application.name }}</span>
|
<span class="tree__link-text">{{ application.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
v-sortable="{
|
v-sortable="{
|
||||||
id: page.id,
|
id: page.id,
|
||||||
update: orderPages,
|
update: orderPages,
|
||||||
marginLeft: 34,
|
|
||||||
marginRight: 10,
|
|
||||||
marginTop: -1.5,
|
marginTop: -1.5,
|
||||||
enabled: $hasPermission(
|
enabled: $hasPermission(
|
||||||
'builder.order_pages',
|
'builder.order_pages',
|
||||||
|
@ -27,15 +25,6 @@
|
||||||
:page="page"
|
:page="page"
|
||||||
></SidebarItemBuilder>
|
></SidebarItemBuilder>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="pendingJobs.length" class="tree__subs">
|
|
||||||
<component
|
|
||||||
:is="getPendingJobComponent(job)"
|
|
||||||
v-for="job in pendingJobs"
|
|
||||||
:key="job.id"
|
|
||||||
:job="job"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</ul>
|
|
||||||
<a
|
<a
|
||||||
v-if="
|
v-if="
|
||||||
$hasPermission(
|
$hasPermission(
|
||||||
|
@ -47,7 +36,7 @@
|
||||||
class="tree__sub-add"
|
class="tree__sub-add"
|
||||||
@click="$refs.createPageModal.show()"
|
@click="$refs.createPageModal.show()"
|
||||||
>
|
>
|
||||||
<i class="iconoir-plus"></i>
|
<i class="tree__sub-add-icon iconoir-plus"></i>
|
||||||
{{ $t('sidebarComponentBuilder.createPage') }}
|
{{ $t('sidebarComponentBuilder.createPage') }}
|
||||||
</a>
|
</a>
|
||||||
<CreatePageModal
|
<CreatePageModal
|
||||||
|
@ -62,7 +51,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
||||||
import BuilderSettingsModal from '@baserow/modules/builder/components/settings/BuilderSettingsModal'
|
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
import SidebarItemBuilder from '@baserow/modules/builder/components/sidebar/SidebarItemBuilder'
|
import SidebarItemBuilder from '@baserow/modules/builder/components/sidebar/SidebarItemBuilder'
|
||||||
|
@ -73,7 +61,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
CreatePageModal,
|
CreatePageModal,
|
||||||
SidebarItemBuilder,
|
SidebarItemBuilder,
|
||||||
BuilderSettingsModal,
|
|
||||||
SidebarApplication,
|
SidebarApplication,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -89,20 +76,12 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
isAppSelected: 'application/isSelected',
|
isAppSelected: 'application/isSelected',
|
||||||
allJobs: 'job/getAll',
|
|
||||||
}),
|
}),
|
||||||
orderedPages() {
|
orderedPages() {
|
||||||
return this.application.pages
|
return this.application.pages
|
||||||
.map((page) => page)
|
.map((page) => page)
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
},
|
},
|
||||||
pendingJobs() {
|
|
||||||
return this.allJobs.filter((job) =>
|
|
||||||
this.$registry
|
|
||||||
.get('job', job.type)
|
|
||||||
.isJobPartOfApplication(job, this.application)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selected(application) {
|
selected(application) {
|
||||||
|
@ -123,9 +102,6 @@ export default {
|
||||||
notifyIf(error, 'page')
|
notifyIf(error, 'page')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPendingJobComponent(job) {
|
|
||||||
return this.$registry.get('job', job.type).getSidebarComponent()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
},
|
},
|
||||||
"sidebarComponentBuilder": {
|
"sidebarComponentBuilder": {
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"createPage": "Create page"
|
"createPage": "New page"
|
||||||
},
|
},
|
||||||
"builderSettingsModal": {
|
"builderSettingsModal": {
|
||||||
"title": "Application"
|
"title": "Application"
|
||||||
|
|
|
@ -24,6 +24,15 @@ export class AdminType extends Registerable {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A human readable name of the admin type category. This admin type is grouped by
|
||||||
|
* the category in the left sidebar.
|
||||||
|
*/
|
||||||
|
getCategory() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('sidebar.general')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The order value used to sort admin types in the sidebar menu.
|
* The order value used to sort admin types in the sidebar menu.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,6 +23,10 @@ export class ApplicationType extends Registerable {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNamePlural() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Small description of the application type, shown in the create new application
|
* Small description of the application type, shown in the create new application
|
||||||
* context menu.
|
* context menu.
|
||||||
|
|
4
web-frontend/modules/core/assets/icons/application.svg
Normal file
4
web-frontend/modules/core/assets/icons/application.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.5 17.5V6.5C3.5 5.39543 4.39543 4.5 5.5 4.5H18.5C19.6046 4.5 20.5 5.39543 20.5 6.5V17.5C20.5 18.6046 19.6046 19.5 18.5 19.5H5.5C4.39543 19.5 3.5 18.6046 3.5 17.5Z" stroke="black" stroke-width="1.4"/>
|
||||||
|
<path d="M3.5 8.5L20.5 8.5" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After (image error) Size: 425 B |
5
web-frontend/modules/core/assets/icons/groups.svg
Normal file
5
web-frontend/modules/core/assets/icons/groups.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 10.35C13.8075 10.35 15.2727 8.87254 15.2727 7.05C15.2727 5.22746 13.8075 3.75 12 3.75C10.1925 3.75 8.72729 5.22746 8.72729 7.05C8.72729 8.87254 10.1925 10.35 12 10.35Z" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.27273 20.25C8.0802 20.25 9.54545 18.7725 9.54545 16.95C9.54545 15.1275 8.0802 13.65 6.27273 13.65C4.46525 13.65 3 15.1275 3 16.95C3 18.7725 4.46525 20.25 6.27273 20.25Z" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.7273 20.25C19.5348 20.25 21 18.7725 21 16.95C21 15.1275 19.5348 13.65 17.7273 13.65C15.9198 13.65 14.4546 15.1275 14.4546 16.95C14.4546 18.7725 15.9198 20.25 17.7273 20.25Z" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After (image error) Size: 903 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.5 8.75L12 5.25L8.5 8.75" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M15.5 15.25L12 18.75L8.5 15.25" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After (image error) Size: 348 B |
|
@ -1,5 +1,5 @@
|
||||||
%badge-cyan {
|
%badge-cyan {
|
||||||
background: $palette-cyan-50 !important;
|
background: $palette-cyan-50;
|
||||||
color: $palette-cyan-800;
|
color: $palette-cyan-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-green {
|
%badge-green {
|
||||||
background: $palette-green-50 !important;
|
background: $palette-green-50;
|
||||||
color: $palette-green-800;
|
color: $palette-green-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-purple {
|
%badge-purple {
|
||||||
background: $palette-purple-50 !important;
|
background: $palette-purple-50;
|
||||||
color: $palette-purple-800;
|
color: $palette-purple-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-red {
|
%badge-red {
|
||||||
background: $palette-red-50 !important;
|
background: $palette-red-50;
|
||||||
color: $palette-red-800;
|
color: $palette-red-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-magenta {
|
%badge-magenta {
|
||||||
background: $palette-magenta-50 !important;
|
background: $palette-magenta-50;
|
||||||
color: $palette-magenta-800;
|
color: $palette-magenta-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-yellow {
|
%badge-yellow {
|
||||||
background: $palette-yellow-50 !important;
|
background: $palette-yellow-50;
|
||||||
color: $palette-yellow-800;
|
color: $palette-yellow-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%badge-neutral {
|
%badge-neutral {
|
||||||
background: $palette-neutral-50 !important;
|
background: $palette-neutral-50;
|
||||||
color: $palette-neutral-800;
|
color: $palette-neutral-800;
|
||||||
|
|
||||||
.badge__indicator {
|
.badge__indicator {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.badge {
|
.badge {
|
||||||
background: $palette-blue-500;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@include rounded($rounded);
|
@include rounded($rounded-md);
|
||||||
@include flex-align-items(6px);
|
@include flex-align-items(6px);
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba($palette-neutral-1300, 0.04);
|
background-color: $palette-neutral-100;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__collapse {
|
.modal__collapse {
|
||||||
@extend %modal-close;
|
@extend %modal-close;
|
||||||
|
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|
|
@ -1,161 +1,170 @@
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@include absolute(0);
|
@include absolute(0);
|
||||||
|
|
||||||
overflow-y: auto;
|
background-color: $color-neutral-50;
|
||||||
background-color: $color-neutral-10;
|
|
||||||
border-right: solid 1px $color-neutral-200;
|
border-right: solid 1px $color-neutral-200;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.layout--collapsed & {
|
.sidebar__section {
|
||||||
overflow: visible;
|
padding: 8px;
|
||||||
|
flex: 0 0;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: solid 1px $palette-neutral-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sidebar__section--scrollable {
|
||||||
|
flex: 1 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sidebar__section--bottom {
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__inner {
|
.sidebar__section-scrollable {
|
||||||
position: relative;
|
overflow-y: auto;
|
||||||
min-height: 100%;
|
min-height: 0;
|
||||||
padding-bottom: 46px;
|
}
|
||||||
|
|
||||||
.layout--collapsed & {
|
.sidebar__section-scrollable-inner {
|
||||||
padding-bottom: 56px;
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__workspaces-selector {
|
||||||
|
@include flex-align-items(10px);
|
||||||
|
|
||||||
|
border-bottom: 1px solid $palette-neutral-200;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 51px;
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar__workspaces-selector-selected-workspace {
|
||||||
|
@extend %ellipsis;
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__workspaces-selector-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
color: $palette-neutral-600;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar__user {
|
.sidebar__user {
|
||||||
display: flex;
|
border-top: solid 1px $palette-neutral-200;
|
||||||
align-items: center;
|
background: $palette-neutral-25;
|
||||||
width: 100%;
|
padding-bottom: 8px;
|
||||||
padding: 16px;
|
border-bottom-left-radius: $rounded-md;
|
||||||
margin-bottom: 4px;
|
border-bottom-right-radius: $rounded-md;
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $color-neutral-100;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout--collapsed & {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__user-info {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
.layout--collapsed & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__user-info-top {
|
|
||||||
@include flex-align-items(4px);
|
|
||||||
|
|
||||||
justify-items: center;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__user-name {
|
|
||||||
@extend %ellipsis;
|
|
||||||
|
|
||||||
min-width: 0;
|
|
||||||
line-height: 22px;
|
|
||||||
color: $color-neutral-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__user-icon {
|
|
||||||
color: $color-neutral-900;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__user-email {
|
.sidebar__user-email {
|
||||||
@extend %ellipsis;
|
font-weight: 500;
|
||||||
|
font-size: 11px;
|
||||||
font-size: 12px;
|
color: $palette-neutral-900;
|
||||||
line-height: 1.25;
|
padding-left: 8px;
|
||||||
color: $color-neutral-600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__nav {
|
.sidebar__user-info {
|
||||||
padding: 0 10px;
|
@include flex-align-items;
|
||||||
|
|
||||||
.layout--collapsed & {
|
padding: 8px 8px 0;
|
||||||
padding: 0 8px;
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__user-license {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
&.badge.badge--neutral {
|
||||||
|
background-color: $palette-neutral-200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__new-wrapper {
|
.sidebar__new-wrapper {
|
||||||
margin-top: 12px;
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__new-wrapper--separator {
|
||||||
|
border-top: solid 1px $palette-neutral-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__new {
|
.sidebar__new {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: $color-neutral-400;
|
font-weight: 500;
|
||||||
padding-left: 6px;
|
line-height: 17px;
|
||||||
|
color: $palette-neutral-900;
|
||||||
|
|
||||||
@include flex-align-items(4px);
|
@include flex-align-items(4px);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $color-neutral-500;
|
color: $palette-neutral-1200;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__new-icon {
|
.sidebar__new-icon {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
|
color: $palette-neutral-600;
|
||||||
|
|
||||||
|
.sidebar__new:hover & {
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__unread-notifications-icon {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: $color-primary-500;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__impersonate {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__foot {
|
.sidebar__foot {
|
||||||
@include absolute(auto, 0, 0, 0);
|
height: 31px;
|
||||||
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
|
||||||
padding: 0 16px 16px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.layout--collapsed & {
|
&.sidebar__foot--collapsed {
|
||||||
flex-direction: column;
|
padding-left: 0;
|
||||||
height: 56px;
|
padding-right: 0;
|
||||||
padding: 0 8px 8px;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
// Is needed temporarily while the undo redo functionality is behind the
|
|
||||||
// `undo` feature flag.
|
|
||||||
&.sidebar__foot--with-undo-redo {
|
|
||||||
.layout--collapsed & {
|
|
||||||
height: 114px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__foot-links {
|
.sidebar__foot-links {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
.layout--collapsed & {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__foot-link {
|
.sidebar__foot-link {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: $color-neutral-700;
|
color: $palette-neutral-700;
|
||||||
|
|
||||||
@include center-text(20px, 12px);
|
@include center-text(16px, 16px);
|
||||||
@include rounded($rounded);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: $color-neutral-100;
|
color: $palette-neutral-1200;
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-left: 6px;
|
|
||||||
|
|
||||||
.layout--collapsed & {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sidebar__foot-link--loading {
|
&.sidebar__foot-link--loading {
|
||||||
|
@ -182,54 +191,89 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout--collapsed {
|
.sidebar__workspace-active-icon {
|
||||||
// Some minor changes regarding the tree items within the collapsed sidebar.
|
|
||||||
.tree .sidebar__tree {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__action {
|
|
||||||
.tree__link {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
color: $palette-neutral-1200;
|
||||||
|
|
||||||
.tree__icon {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__item-name {
|
|
||||||
background-color: $color-neutral-900;
|
|
||||||
color: $white;
|
|
||||||
padding: 0 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 400;
|
|
||||||
display: none;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
@include absolute(6px, auto, auto, 36px);
|
|
||||||
@include center-text(auto, 11px, 21px);
|
|
||||||
@include rounded($rounded);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .sidebar__item-name {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__logo {
|
|
||||||
display: inline-block;
|
|
||||||
order: 2;
|
|
||||||
width: 18px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__unread-notifications-icon {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 50%;
|
||||||
right: 32px;
|
right: 5px;
|
||||||
width: 8px;
|
font-size: 14px;
|
||||||
height: 8px;
|
transform: translateY(-50%);
|
||||||
border-radius: 100%;
|
}
|
||||||
background-color: $color-primary-500;
|
|
||||||
|
.dashboard__user-workspaces.select__items {
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
.select__item {
|
||||||
|
&.active:not(.select__item--loading) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard__user-workspace-avatar {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__back-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $palette-neutral-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__head {
|
||||||
|
@include flex-align-items(15px);
|
||||||
|
|
||||||
|
border-bottom: 1px solid $palette-neutral-200;
|
||||||
|
padding: 0 21px;
|
||||||
|
height: 51px;
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__back {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
|
||||||
|
@include rounded($rounded-md);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($palette-neutral-1300, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__logo {
|
||||||
|
.sidebar__foot--collapsed & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__collapse-link {
|
||||||
|
font-size: 14px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
|
||||||
|
@include rounded($rounded-md);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($palette-neutral-1300, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__item-count {
|
||||||
|
margin-left: auto;
|
||||||
|
color: $palette-neutral-700;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 12px;
|
margin: 0;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.tree__item & {
|
.tree__item & {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
|
@ -10,27 +14,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree:not(:last-child) {
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree__item {
|
.tree__item {
|
||||||
@extend %first-last-no-margin;
|
@extend %first-last-no-margin;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 4px 0;
|
margin: 2px 0;
|
||||||
|
|
||||||
@include rounded($rounded);
|
@include rounded($rounded-md);
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: $color-primary-100;
|
background-color: rgba($palette-neutral-1300, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.tree__item--loading::after {
|
&.tree__item--loading::after {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
|
|
||||||
@include loading(14px);
|
@include loading(14px);
|
||||||
@include absolute(9px, 9px, auto, auto);
|
@include absolute(8px, 9px, auto, auto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
.tree__action {
|
.tree__action {
|
||||||
@extend %tree-size;
|
@extend %tree-size;
|
||||||
|
|
||||||
padding: 0 6px;
|
padding: 0 8px;
|
||||||
|
|
||||||
@include rounded($rounded);
|
@include rounded($rounded);
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.tree__action--disabled):hover {
|
&:not(.tree__action--disabled):hover {
|
||||||
background-color: $color-neutral-100;
|
background-color: rgba($palette-neutral-1300, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree__item.active &:hover {
|
.tree__item.active &:hover {
|
||||||
|
@ -74,22 +74,17 @@
|
||||||
&.tree__action--has-right-icon {
|
&.tree__action--has-right-icon {
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.tree__action--has-notification {
|
|
||||||
padding-right: 48px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree__link {
|
.tree__link {
|
||||||
@extend %tree-size;
|
@extend %tree-size;
|
||||||
|
|
||||||
color: $color-neutral-900;
|
color: $palette-neutral-900;
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
@include flex-align-items(4px);
|
@include flex-align-items(8px);
|
||||||
|
|
||||||
.tree__action--deactivated & {
|
.tree__action--deactivated & {
|
||||||
color: $color-neutral-500;
|
color: $palette-neutral-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -119,6 +114,12 @@
|
||||||
@extend %ellipsis;
|
@extend %ellipsis;
|
||||||
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
color: $palette-neutral-900;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.active & {
|
||||||
|
color: $palette-neutral-1200;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree__progress-percentage {
|
.tree__progress-percentage {
|
||||||
|
@ -132,16 +133,17 @@
|
||||||
@extend %tree-size;
|
@extend %tree-size;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $color-neutral-900;
|
color: $palette-neutral-700;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
&.tree__icon--type {
|
.active & {
|
||||||
color: $color-neutral-500;
|
color: $palette-neutral-1200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
%tree-sub-size {
|
%tree-sub-size {
|
||||||
line-height: 28px;
|
line-height: 32px;
|
||||||
height: 28px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree__subs {
|
.tree__subs {
|
||||||
|
@ -155,29 +157,33 @@
|
||||||
@extend %tree-sub-size;
|
@extend %tree-sub-size;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 34px;
|
margin: 2px 0 0 21px;
|
||||||
|
|
||||||
|
@include rounded($rounded-md);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background-color: rgba($palette-neutral-1300, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12px;
|
left: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
top: 0;
|
height: 32px;
|
||||||
height: 28px;
|
border-right: 1px solid $palette-neutral-200;
|
||||||
border-left: 1px solid $color-neutral-200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:not(:last-child) {
|
||||||
top: 14px;
|
margin-bottom: 2px;
|
||||||
width: 12px;
|
|
||||||
border-bottom: 1px solid $color-neutral-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child::before {
|
&::before {
|
||||||
height: 15px;
|
height: 34px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,18 +199,18 @@
|
||||||
@extend %tree-sub-size;
|
@extend %tree-sub-size;
|
||||||
@extend %ellipsis;
|
@extend %ellipsis;
|
||||||
|
|
||||||
color: $color-neutral-900;
|
color: $palette-neutral-900;
|
||||||
display: block;
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0 32px 0 16px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $color-primary-500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.active > & {
|
.active > & {
|
||||||
font-weight: 600;
|
color: $palette-neutral-1200;
|
||||||
color: $color-primary-600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--empty::before {
|
&--empty::before {
|
||||||
|
@ -237,13 +243,24 @@
|
||||||
|
|
||||||
.tree__sub-add {
|
.tree__sub-add {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0 10px 10px;
|
margin: 10px 0 10px 6px;
|
||||||
|
line-height: 17px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $color-neutral-400;
|
font-weight: 500;
|
||||||
|
color: $palette-neutral-900;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $color-neutral-500;
|
color: $palette-neutral-1200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree__sub-add-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: $palette-neutral-600;
|
||||||
|
|
||||||
|
.tree__sub-add:hover & {
|
||||||
|
color: $palette-neutral-1200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,3 +304,10 @@
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree__heading {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $palette-neutral-900;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
|
@ -76,4 +76,5 @@ $baserow-icons: 'circle-empty', 'circle-checked', 'check-square', 'formula',
|
||||||
'file-audio', 'file-video', 'file-code', 'tablet', 'form', 'file-excel',
|
'file-audio', 'file-video', 'file-code', 'tablet', 'form', 'file-excel',
|
||||||
'kanban', 'file-word', 'file-archive', 'gallery', 'file-powerpoint',
|
'kanban', 'file-word', 'file-archive', 'gallery', 'file-powerpoint',
|
||||||
'calendar', 'smile', 'smartphone', 'plus', 'heading-1', 'heading-2',
|
'calendar', 'smile', 'smartphone', 'plus', 'heading-1', 'heading-2',
|
||||||
'heading-3', 'paragraph', 'ordered-list', 'enlarge', 'share', 'settings';
|
'heading-3', 'paragraph', 'ordered-list', 'enlarge', 'share', 'settings',
|
||||||
|
'up-down-arrows', 'application', 'groups';
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<a
|
<a
|
||||||
v-if="collapsibleRightSidebar"
|
v-if="collapsibleRightSidebar"
|
||||||
class="sidebar__collapse"
|
class="modal__collapse"
|
||||||
@click="collapseSidebar"
|
@click="collapseSidebar"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
|
|
|
@ -1,456 +1,99 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="sidebar__inner">
|
|
||||||
<component
|
<component
|
||||||
:is="component"
|
:is="component"
|
||||||
v-for="(component, index) in sidebarTopComponents"
|
v-for="(component, index) in impersonateComponent"
|
||||||
:key="index"
|
:key="index"
|
||||||
></component>
|
></component>
|
||||||
|
<template v-if="showAdmin">
|
||||||
|
<div class="sidebar__head">
|
||||||
|
<a href="#" class="sidebar__back" @click="setShowAdmin(false)">
|
||||||
|
<i class="sidebar__back-icon iconoir-nav-arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<div class="sidebar__title">
|
||||||
|
{{ $t('sidebar.adminSettings') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SidebarAdmin></SidebarAdmin>
|
||||||
|
</template>
|
||||||
|
<template v-if="!showAdmin">
|
||||||
<a
|
<a
|
||||||
ref="userContextAnchor"
|
ref="workspaceContextAnchor"
|
||||||
class="sidebar__user"
|
class="sidebar__workspaces-selector"
|
||||||
@click="
|
@click="
|
||||||
$refs.userContext.toggle(
|
$refs.workspacesContext.toggle(
|
||||||
$refs.userContextAnchor,
|
$refs.workspaceContextAnchor,
|
||||||
'bottom',
|
'bottom',
|
||||||
'left',
|
'left',
|
||||||
isCollapsed ? -4 : -10,
|
8,
|
||||||
isCollapsed ? 8 : 16
|
16
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
rounded
|
:initials="selectedWorkspace.name || name | nameAbbreviation"
|
||||||
:initials="name | nameAbbreviation"
|
|
||||||
:size="avatarSize"
|
|
||||||
></Avatar>
|
></Avatar>
|
||||||
<div class="sidebar__user-info">
|
<span class="sidebar__workspaces-selector-selected-workspace">{{
|
||||||
<div class="sidebar__user-info-top">
|
selectedWorkspace.name || name
|
||||||
<div class="sidebar__user-name">{{ name }}</div>
|
|
||||||
<i class="sidebar__user-icon iconoir-nav-arrow-down"></i>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar__user-email">{{ email }}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<Context
|
|
||||||
ref="userContext"
|
|
||||||
:overflow-scroll="true"
|
|
||||||
:max-height-if-outside-viewport="true"
|
|
||||||
>
|
|
||||||
<div class="context__menu-title">{{ name }}</div>
|
|
||||||
<ul class="context__menu">
|
|
||||||
<li class="context__menu-item">
|
|
||||||
<a
|
|
||||||
class="context__menu-item-link"
|
|
||||||
@click=";[$refs.settingsModal.show(), $refs.userContext.hide()]"
|
|
||||||
>
|
|
||||||
<i class="context__menu-item-icon iconoir-settings"></i>
|
|
||||||
{{ $t('sidebar.settings') }}
|
|
||||||
</a>
|
|
||||||
<SettingsModal ref="settingsModal"></SettingsModal>
|
|
||||||
</li>
|
|
||||||
<li class="context__menu-item">
|
|
||||||
<a
|
|
||||||
class="context__menu-item-link"
|
|
||||||
:class="{ 'context__menu-item-link--loading': logoffLoading }"
|
|
||||||
@click="logoff()"
|
|
||||||
>
|
|
||||||
<i class="context__menu-item-icon iconoir-log-out"></i>
|
|
||||||
{{ $t('sidebar.logoff') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Context>
|
|
||||||
<div class="sidebar__nav">
|
|
||||||
<ul class="tree">
|
|
||||||
<component
|
|
||||||
:is="component"
|
|
||||||
v-for="(component, index) in sidebarMainMenuComponents"
|
|
||||||
:key="index"
|
|
||||||
></component>
|
|
||||||
<li class="tree__item">
|
|
||||||
<div class="tree__action sidebar__action">
|
|
||||||
<a class="tree__link" @click="$refs.trashModal.show()">
|
|
||||||
<i class="tree__icon iconoir-bin"></i>
|
|
||||||
<span class="tree__link-text">
|
|
||||||
<span class="sidebar__item-name">{{
|
|
||||||
$t('sidebar.trash')
|
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<TrashModal ref="trashModal"></TrashModal>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li v-if="isStaff" class="tree__item">
|
|
||||||
<div
|
|
||||||
class="tree__action sidebar__action"
|
|
||||||
:class="{ 'tree__action--disabled': isAdminPage }"
|
|
||||||
>
|
|
||||||
<a class="tree__link" @click.prevent="admin()">
|
|
||||||
<i class="tree__icon iconoir-settings"></i>
|
|
||||||
<span class="tree__link-text">
|
|
||||||
<span class="sidebar__item-name">{{
|
|
||||||
$t('sidebar.admin')
|
|
||||||
}}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ul v-show="isAdminPage" class="tree sidebar__tree">
|
|
||||||
<SidebarAdminItem
|
|
||||||
v-for="adminType in sortedAdminTypes"
|
|
||||||
:key="adminType.type"
|
|
||||||
:admin-type="adminType"
|
|
||||||
>
|
|
||||||
</SidebarAdminItem>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<template v-if="hasSelectedWorkspace && !isCollapsed">
|
|
||||||
<li class="tree__item margin-top-2">
|
|
||||||
<div
|
|
||||||
:title="selectedWorkspace.name"
|
|
||||||
class="tree__action tree__action--has-options"
|
|
||||||
:class="{
|
|
||||||
'tree__action--has-notification':
|
|
||||||
unreadNotificationsInOtherWorkspaces,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
ref="workspaceSelectToggle"
|
|
||||||
class="tree__link tree__link--group"
|
|
||||||
@click="
|
|
||||||
$refs.workspaceSelect.toggle(
|
|
||||||
$refs.workspaceSelectToggle,
|
|
||||||
'bottom',
|
|
||||||
'left',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="tree__link-text">
|
|
||||||
<Editable
|
|
||||||
ref="rename"
|
|
||||||
:value="selectedWorkspace.name"
|
|
||||||
@change="renameWorkspace(selectedWorkspace, $event)"
|
|
||||||
></Editable>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<span
|
<span
|
||||||
v-if="unreadNotificationsInOtherWorkspaces"
|
v-if="unreadNotificationsInOtherWorkspaces"
|
||||||
class="sidebar__unread-notifications-icon"
|
class="sidebar__unread-notifications-icon"
|
||||||
></span>
|
></span>
|
||||||
|
<i
|
||||||
|
class="sidebar__workspaces-selector-icon baserow-icon-up-down-arrows"
|
||||||
|
></i>
|
||||||
|
</a>
|
||||||
|
<SidebarUserContext
|
||||||
|
ref="workspacesContext"
|
||||||
|
:workspaces="workspaces"
|
||||||
|
:selected-workspace="selectedWorkspace"
|
||||||
|
@selected-workspace="$emit('selected-workspace', $event)"
|
||||||
|
@toggle-admin="setShowAdmin($event)"
|
||||||
|
></SidebarUserContext>
|
||||||
|
|
||||||
<a
|
<SidebarMenu
|
||||||
ref="contextLink"
|
v-if="hasSelectedWorkspace"
|
||||||
class="tree__options"
|
:selected-workspace="selectedWorkspace"
|
||||||
@click="
|
></SidebarMenu>
|
||||||
$refs.context.toggle(
|
|
||||||
$refs.contextLink,
|
|
||||||
'bottom',
|
|
||||||
'right',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="baserow-icon-more-vertical"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<WorkspacesContext ref="workspaceSelect"></WorkspacesContext>
|
<SidebarWithWorkspace
|
||||||
<WorkspaceContext
|
v-if="hasSelectedWorkspace"
|
||||||
ref="context"
|
:applications="applications"
|
||||||
:workspace="selectedWorkspace"
|
:selected-workspace="selectedWorkspace"
|
||||||
@rename="enableRename()"
|
></SidebarWithWorkspace>
|
||||||
></WorkspaceContext>
|
|
||||||
</div>
|
<SidebarWithoutWorkspace
|
||||||
</li>
|
v-if="!hasSelectedWorkspace"
|
||||||
<nuxt-link
|
:workspaces="workspaces"
|
||||||
v-slot="{ href, navigate, isExactActive }"
|
@selected-workspace="$emit('selected-workspace', $event)"
|
||||||
:to="{
|
></SidebarWithoutWorkspace>
|
||||||
name: 'workspace',
|
|
||||||
params: {
|
|
||||||
workspaceId: selectedWorkspace.id,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
data-highlight="members"
|
|
||||||
class="tree__item"
|
|
||||||
:class="{ active: isExactActive }"
|
|
||||||
>
|
|
||||||
<div class="tree__action">
|
|
||||||
<a :href="href" class="tree__link" @click="navigate">
|
|
||||||
<i
|
|
||||||
class="tree__icon tree__icon--type iconoir-home-alt-slim-horiz"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">{{
|
|
||||||
$t('sidebar.home')
|
|
||||||
}}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</nuxt-link>
|
|
||||||
<li class="tree__item">
|
|
||||||
<div class="tree__action tree__action--has-counter">
|
|
||||||
<a
|
|
||||||
class="tree__link"
|
|
||||||
@click="$refs.notificationPanel.toggle($event.currentTarget)"
|
|
||||||
>
|
|
||||||
<i class="tree__icon tree__icon--type iconoir-bell"></i>
|
|
||||||
<span class="tree__link-text">{{
|
|
||||||
$t('sidebar.notifications')
|
|
||||||
}}</span>
|
|
||||||
</a>
|
|
||||||
<BadgeCounter
|
|
||||||
v-show="unreadNotificationCount"
|
|
||||||
class="tree__counter"
|
|
||||||
:count="unreadNotificationCount"
|
|
||||||
:limit="10"
|
|
||||||
>
|
|
||||||
</BadgeCounter>
|
|
||||||
</div>
|
|
||||||
<NotificationPanel ref="notificationPanel" />
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="
|
|
||||||
$hasPermission(
|
|
||||||
'workspace.create_invitation',
|
|
||||||
selectedWorkspace,
|
|
||||||
selectedWorkspace.id
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="tree__item"
|
|
||||||
>
|
|
||||||
<div class="tree__action">
|
|
||||||
<a class="tree__link" @click="$refs.inviteModal.show()">
|
|
||||||
<i class="tree__icon tree__icon--type iconoir-add-user"></i>
|
|
||||||
<span class="tree__link-text">{{
|
|
||||||
$t('sidebar.inviteOthers')
|
|
||||||
}}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<WorkspaceMemberInviteModal
|
|
||||||
ref="inviteModal"
|
|
||||||
:workspace="selectedWorkspace"
|
|
||||||
@invite-submitted="handleInvite"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<nuxt-link
|
|
||||||
v-if="
|
|
||||||
$hasPermission(
|
|
||||||
'workspace.list_workspace_users',
|
|
||||||
selectedWorkspace,
|
|
||||||
selectedWorkspace.id
|
|
||||||
)
|
|
||||||
"
|
|
||||||
v-slot="{ href, navigate, isExactActive }"
|
|
||||||
:to="{
|
|
||||||
name: 'settings-members',
|
|
||||||
params: {
|
|
||||||
workspaceId: selectedWorkspace.id,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
data-highlight="members"
|
|
||||||
class="tree__item"
|
|
||||||
:class="{ active: isExactActive }"
|
|
||||||
>
|
|
||||||
<div class="tree__action">
|
|
||||||
<a :href="href" class="tree__link" @click="navigate">
|
|
||||||
<i
|
|
||||||
class="tree__icon tree__icon--type iconoir-community"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">{{
|
|
||||||
$t('sidebar.members')
|
|
||||||
}}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</nuxt-link>
|
|
||||||
<component
|
|
||||||
:is="component"
|
|
||||||
v-for="(component, index) in sidebarWorkspaceComponents"
|
|
||||||
:key="'sidebarWorkspaceComponents' + index"
|
|
||||||
:workspace="selectedWorkspace"
|
|
||||||
></component>
|
|
||||||
<ul class="tree" data-highlight="applications">
|
|
||||||
<component
|
|
||||||
:is="getApplicationComponent(application)"
|
|
||||||
v-for="application in orderedApplicationsInSelectedWorkspace"
|
|
||||||
:key="application.id"
|
|
||||||
v-sortable="{
|
|
||||||
id: application.id,
|
|
||||||
update: orderApplications,
|
|
||||||
handle: '[data-sortable-handle]',
|
|
||||||
marginTop: -1.5,
|
|
||||||
enabled: $hasPermission(
|
|
||||||
'workspace.order_applications',
|
|
||||||
selectedWorkspace,
|
|
||||||
selectedWorkspace.id
|
|
||||||
),
|
|
||||||
}"
|
|
||||||
:application="application"
|
|
||||||
:workspace="selectedWorkspace"
|
|
||||||
></component>
|
|
||||||
</ul>
|
|
||||||
<ul v-if="pendingJobs.length" class="tree">
|
|
||||||
<component
|
|
||||||
:is="getPendingJobComponent(job)"
|
|
||||||
v-for="job in pendingJobs"
|
|
||||||
:key="job.id"
|
|
||||||
:job="job"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</ul>
|
|
||||||
<li class="sidebar__new-wrapper">
|
|
||||||
<a
|
|
||||||
v-if="
|
|
||||||
$hasPermission(
|
|
||||||
'workspace.create_application',
|
|
||||||
selectedWorkspace,
|
|
||||||
selectedWorkspace.id
|
|
||||||
)
|
|
||||||
"
|
|
||||||
ref="createApplicationContextLink"
|
|
||||||
class="sidebar__new"
|
|
||||||
@click="
|
|
||||||
$refs.createApplicationContext.toggle(
|
|
||||||
$refs.createApplicationContextLink
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="sidebar__new-icon iconoir-plus"></i>
|
|
||||||
{{ $t('action.createNew') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<CreateApplicationContext
|
|
||||||
ref="createApplicationContext"
|
|
||||||
:workspace="selectedWorkspace"
|
|
||||||
></CreateApplicationContext>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!hasSelectedWorkspace && !isCollapsed">
|
<SidebarFoot></SidebarFoot>
|
||||||
<li v-if="workspaces.length === 0" class="tree_item margin-top-2">
|
|
||||||
<p>{{ $t('sidebar.errorNoWorkspace') }}</p>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-for="(workspace, index) in workspaces"
|
|
||||||
:key="workspace.id"
|
|
||||||
class="tree__item"
|
|
||||||
:class="{
|
|
||||||
'margin-top-2': index === 0,
|
|
||||||
'tree__item--loading': workspace._.additionalLoading,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="tree__action tree__action--has-right-icon tree__action--has-notification"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="tree__link tree__link--group"
|
|
||||||
@click="$emit('selected-workspace', workspace)"
|
|
||||||
><span class="tree__link-text">{{ workspace.name }}</span></a
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="hasUnreadNotifications(workspace.id)"
|
|
||||||
class="sidebar__unread-notifications-icon"
|
|
||||||
></span>
|
|
||||||
<span class="tree__right-icon">
|
|
||||||
<i class="iconoir-arrow-right"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar__new-wrapper">
|
|
||||||
<a
|
|
||||||
v-if="$hasPermission('create_workspace')"
|
|
||||||
class="sidebar__new"
|
|
||||||
@click="$refs.createWorkspaceModal.show()"
|
|
||||||
>
|
|
||||||
<i class="iconoir-plus"></i>
|
|
||||||
{{ $t('sidebar.createWorkspace') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<CreateWorkspaceModal
|
|
||||||
ref="createWorkspaceModal"
|
|
||||||
></CreateWorkspaceModal>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar__foot sidebar__foot--with-undo-redo">
|
|
||||||
<div class="sidebar__logo">
|
|
||||||
<ExternalLinkBaserowLogo />
|
|
||||||
</div>
|
|
||||||
<div class="sidebar__foot-links">
|
|
||||||
<a
|
|
||||||
class="sidebar__foot-link"
|
|
||||||
:class="{
|
|
||||||
'sidebar__foot-link--loading': undoLoading,
|
|
||||||
}"
|
|
||||||
@click="undo(false)"
|
|
||||||
>
|
|
||||||
<i class="sidebar__foot-link-icon iconoir-undo"></i>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="sidebar__foot-link"
|
|
||||||
:class="{
|
|
||||||
'sidebar__foot-link--loading': redoLoading,
|
|
||||||
}"
|
|
||||||
@click="redo(false)"
|
|
||||||
>
|
|
||||||
<i class="sidebar__foot-link-icon iconoir-redo"></i>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="sidebar__foot-link"
|
|
||||||
@click="$store.dispatch('sidebar/toggleCollapsed')"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="sidebar__foot-link-icon"
|
|
||||||
:class="{
|
|
||||||
'iconoir-fast-arrow-right': isCollapsed,
|
|
||||||
'iconoir-fast-arrow-left': !isCollapsed,
|
|
||||||
}"
|
|
||||||
></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
import SidebarUserContext from '@baserow/modules/core/components/sidebar/SidebarUserContext'
|
||||||
import SettingsModal from '@baserow/modules/core/components/settings/SettingsModal'
|
import SidebarWithWorkspace from '@baserow/modules/core/components/sidebar/SidebarWithWorkspace'
|
||||||
import SidebarAdminItem from '@baserow/modules/core/components/sidebar/SidebarAdminItem'
|
import SidebarWithoutWorkspace from '@baserow/modules/core/components/sidebar/SidebarWithoutWorkspace'
|
||||||
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
import SidebarAdmin from '@baserow/modules/core/components/sidebar/SidebarAdmin'
|
||||||
import CreateApplicationContext from '@baserow/modules/core/components/application/CreateApplicationContext'
|
import SidebarFoot from '@baserow/modules/core/components/sidebar/SidebarFoot'
|
||||||
import WorkspacesContext from '@baserow/modules/core/components/workspace/WorkspacesContext'
|
import SidebarMenu from '@baserow/modules/core/components/sidebar/SidebarMenu'
|
||||||
import WorkspaceContext from '@baserow/modules/core/components/workspace/WorkspaceContext'
|
import SidebarAdminItem from './SidebarAdminItem.vue'
|
||||||
import CreateWorkspaceModal from '@baserow/modules/core/components/workspace/CreateWorkspaceModal'
|
|
||||||
import TrashModal from '@baserow/modules/core/components/trash/TrashModal'
|
|
||||||
import editWorkspace from '@baserow/modules/core/mixins/editWorkspace'
|
|
||||||
import undoRedo from '@baserow/modules/core/mixins/undoRedo'
|
|
||||||
import ExternalLinkBaserowLogo from '@baserow/modules/core/components/ExternalLinkBaserowLogo'
|
|
||||||
import WorkspaceMemberInviteModal from '@baserow/modules/core/components/workspace/WorkspaceMemberInviteModal'
|
|
||||||
import { logoutAndRedirectToLogin } from '@baserow/modules/core/utils/auth'
|
|
||||||
import NotificationPanel from '@baserow/modules/core/components/NotificationPanel'
|
|
||||||
import BadgeCounter from '@baserow/modules/core/components/BadgeCounter'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Sidebar',
|
name: 'Sidebar',
|
||||||
components: {
|
components: {
|
||||||
ExternalLinkBaserowLogo,
|
SidebarAdmin,
|
||||||
SettingsModal,
|
SidebarWithoutWorkspace,
|
||||||
CreateApplicationContext,
|
SidebarWithWorkspace,
|
||||||
SidebarAdminItem,
|
SidebarUserContext,
|
||||||
SidebarApplication,
|
SidebarMenu,
|
||||||
WorkspacesContext,
|
SidebarFoot,
|
||||||
WorkspaceContext,
|
|
||||||
CreateWorkspaceModal,
|
|
||||||
TrashModal,
|
|
||||||
WorkspaceMemberInviteModal,
|
|
||||||
NotificationPanel,
|
|
||||||
BadgeCounter,
|
|
||||||
},
|
},
|
||||||
mixins: [editWorkspace, undoRedo],
|
|
||||||
props: {
|
props: {
|
||||||
applications: {
|
applications: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -467,144 +110,43 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
logoffLoading: false,
|
showAdmin: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
/**
|
SidebarAdminItem() {
|
||||||
* Because all the applications that belong to the user are in the store we will
|
return SidebarAdminItem
|
||||||
* filter on the selected workspace here.
|
|
||||||
*/
|
|
||||||
orderedApplicationsInSelectedWorkspace() {
|
|
||||||
return this.applications
|
|
||||||
.filter(
|
|
||||||
(application) =>
|
|
||||||
application.workspace.id === this.selectedWorkspace.id
|
|
||||||
)
|
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
},
|
},
|
||||||
adminTypes() {
|
impersonateComponent() {
|
||||||
return this.$registry.getAll('admin')
|
|
||||||
},
|
|
||||||
sortedAdminTypes() {
|
|
||||||
return Object.values(this.adminTypes)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.getOrder() - b.getOrder())
|
|
||||||
},
|
|
||||||
sidebarTopComponents() {
|
|
||||||
return Object.values(this.$registry.getAll('plugin'))
|
return Object.values(this.$registry.getAll('plugin'))
|
||||||
.map((plugin) => plugin.getSidebarTopComponent())
|
.map((plugin) => plugin.getImpersonateComponent())
|
||||||
.filter((component) => component !== null)
|
.filter((component) => component !== null)
|
||||||
},
|
},
|
||||||
sidebarMainMenuComponents() {
|
|
||||||
return Object.values(this.$registry.getAll('plugin'))
|
|
||||||
.map((plugin) => plugin.getSidebarMainMenuComponent())
|
|
||||||
.filter((component) => component !== null)
|
|
||||||
},
|
|
||||||
sidebarWorkspaceComponents() {
|
|
||||||
return Object.values(this.$registry.getAll('plugin'))
|
|
||||||
.flatMap((plugin) =>
|
|
||||||
plugin.getSidebarWorkspaceComponents(this.selectedWorkspace)
|
|
||||||
)
|
|
||||||
.filter((component) => component !== null)
|
|
||||||
},
|
|
||||||
pendingJobs() {
|
|
||||||
return this.$store.getters['job/getAll'].filter((job) =>
|
|
||||||
this.$registry
|
|
||||||
.get('job', job.type)
|
|
||||||
.isJobPartOfWorkspace(job, this.selectedWorkspace)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Indicates whether the current user is visiting an admin page.
|
|
||||||
*/
|
|
||||||
isAdminPage() {
|
|
||||||
return Object.values(this.adminTypes).some((adminType) => {
|
|
||||||
return this.$route.matched.some(
|
|
||||||
({ name }) => name === adminType.routeName
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
avatarSize() {
|
|
||||||
return this.isCollapsed ? 'large' : 'x-large'
|
|
||||||
},
|
|
||||||
hasSelectedWorkspace() {
|
hasSelectedWorkspace() {
|
||||||
return Object.prototype.hasOwnProperty.call(this.selectedWorkspace, 'id')
|
return Object.prototype.hasOwnProperty.call(this.selectedWorkspace, 'id')
|
||||||
},
|
},
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
isStaff: 'auth/isStaff',
|
|
||||||
name: 'auth/getName',
|
name: 'auth/getName',
|
||||||
email: 'auth/getUsername',
|
|
||||||
isCollapsed: 'sidebar/isCollapsed',
|
|
||||||
unreadNotificationCount: 'notification/getUnreadCount',
|
|
||||||
unreadNotificationsInOtherWorkspaces:
|
unreadNotificationsInOtherWorkspaces:
|
||||||
'notification/anyOtherWorkspaceWithUnread',
|
'notification/anyOtherWorkspaceWithUnread',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
// Checks whether the rendered page is an admin page. If so, switch the left sidebar
|
||||||
|
// navigation to the admin.
|
||||||
|
// this.showAdmin = Object.values(this.$registry.getAll('admin')).some(
|
||||||
|
// (adminType) => {
|
||||||
|
// return this.$route.matched.some(
|
||||||
|
// ({ name }) => name === adminType.routeName
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasUnreadNotifications(workspaceId) {
|
setShowAdmin(value) {
|
||||||
return this.$store.getters['notification/workspaceHasUnread'](workspaceId)
|
this.showAdmin = value
|
||||||
},
|
this.$forceUpdate()
|
||||||
getApplicationComponent(application) {
|
|
||||||
return this.$registry
|
|
||||||
.get('application', application.type)
|
|
||||||
.getSidebarComponent()
|
|
||||||
},
|
|
||||||
getPendingJobComponent(job) {
|
|
||||||
return this.$registry.get('job', job.type).getSidebarComponent()
|
|
||||||
},
|
|
||||||
logoff() {
|
|
||||||
this.logoffLoading = true
|
|
||||||
logoutAndRedirectToLogin(
|
|
||||||
this.$nuxt.$router,
|
|
||||||
this.$store,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Called when the user clicks on the admin menu. Because there isn't an
|
|
||||||
* admin page it will navigate to the route of the first registered admin
|
|
||||||
* type.
|
|
||||||
*/
|
|
||||||
admin() {
|
|
||||||
// If the user is already on an admin page we don't have to do anything because
|
|
||||||
// the link is disabled.
|
|
||||||
if (this.isAdminPage) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only want to autoselect the first active admin type because the other ones
|
|
||||||
// can't be selected.
|
|
||||||
const activated = this.sortedAdminTypes.filter((adminType) => {
|
|
||||||
return !this.$registry.get('admin', adminType.type).isDeactivated()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (activated.length > 0) {
|
|
||||||
this.$nuxt.$router.push({ name: activated[0].routeName })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async orderApplications(order, oldOrder) {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('application/order', {
|
|
||||||
workspace: this.selectedWorkspace,
|
|
||||||
order,
|
|
||||||
oldOrder,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
notifyIf(error, 'application')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleInvite(event) {
|
|
||||||
if (this.$route.name !== 'settings-invites') {
|
|
||||||
this.$router.push({
|
|
||||||
name: 'settings-invites',
|
|
||||||
params: {
|
|
||||||
workspaceId: this.selectedWorkspace.id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div class="sidebar__section sidebar__section--scrollable">
|
||||||
|
<div class="sidebar__section-scrollable">
|
||||||
|
<div class="sidebar__section-scrollable-inner">
|
||||||
|
<ul class="tree">
|
||||||
|
<template v-for="(category, index) in groupedSortedAdminTypes">
|
||||||
|
<li
|
||||||
|
:key="category.name"
|
||||||
|
class="tree__heading"
|
||||||
|
:class="{ 'margin-top-2': index > 0 }"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
</li>
|
||||||
|
<SidebarAdminItem
|
||||||
|
v-for="adminType in category.items"
|
||||||
|
:key="adminType.type"
|
||||||
|
:admin-type="adminType"
|
||||||
|
>
|
||||||
|
</SidebarAdminItem>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SidebarAdminItem from '@baserow/modules/core/components/sidebar/SidebarAdminItem.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarAdmin',
|
||||||
|
components: { SidebarAdminItem },
|
||||||
|
computed: {
|
||||||
|
adminTypes() {
|
||||||
|
return this.$registry.getAll('admin')
|
||||||
|
},
|
||||||
|
sortedAdminTypes() {
|
||||||
|
return Object.values(this.adminTypes)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.getOrder() - b.getOrder())
|
||||||
|
},
|
||||||
|
groupedSortedAdminTypes() {
|
||||||
|
const categories = []
|
||||||
|
this.sortedAdminTypes.forEach((adminType) => {
|
||||||
|
const categoryName = adminType.getCategory()
|
||||||
|
let category = categories.find((c) => c.name === categoryName)
|
||||||
|
if (!category) {
|
||||||
|
category = { name: categoryName, items: [] }
|
||||||
|
categories.push(category)
|
||||||
|
}
|
||||||
|
category.items.push(adminType)
|
||||||
|
})
|
||||||
|
return categories
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,7 +2,6 @@
|
||||||
<li
|
<li
|
||||||
class="tree__item"
|
class="tree__item"
|
||||||
:class="{
|
:class="{
|
||||||
active: application._.selected,
|
|
||||||
'tree__item--loading': application._.loading,
|
'tree__item--loading': application._.loading,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
@ -13,10 +12,7 @@
|
||||||
:title="application.name"
|
:title="application.name"
|
||||||
@click="$emit('selected', application)"
|
@click="$emit('selected', application)"
|
||||||
>
|
>
|
||||||
<i
|
<i class="tree__icon" :class="application._.type.iconClass"></i>
|
||||||
class="tree__icon tree__icon--type"
|
|
||||||
:class="application._.type.iconClass"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">
|
<span class="tree__link-text">
|
||||||
<template v-if="application.name === ''"> </template>
|
<template v-if="application.name === ''"> </template>
|
||||||
<Editable
|
<Editable
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<li class="tree__item tree__item--loading">
|
<li class="tree__item tree__item--loading">
|
||||||
<div class="tree__action tree__action--disabled">
|
<div class="tree__action tree__action--disabled">
|
||||||
<a class="tree__link">
|
<a class="tree__link">
|
||||||
<i class="tree__icon tree__icon--type" :class="jobIconClass"></i>
|
<i class="tree__icon" :class="jobIconClass"></i>
|
||||||
<span class="tree__link-text">{{ jobSidebarText }}</span>
|
<span class="tree__link-text">{{ jobSidebarText }}</span>
|
||||||
<div class="tree__progress-percentage">
|
<div class="tree__progress-percentage">
|
||||||
{{ job.progress_percentage }} %
|
{{ job.progress_percentage }} %
|
||||||
|
|
40
web-frontend/modules/core/components/sidebar/SidebarFoot.vue
Normal file
40
web-frontend/modules/core/components/sidebar/SidebarFoot.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div class="sidebar__section sidebar__section--bottom">
|
||||||
|
<div class="sidebar__foot">
|
||||||
|
<div class="sidebar__logo">
|
||||||
|
<ExternalLinkBaserowLogo />
|
||||||
|
</div>
|
||||||
|
<div class="sidebar__foot-links">
|
||||||
|
<a
|
||||||
|
class="sidebar__foot-link"
|
||||||
|
:class="{
|
||||||
|
'sidebar__foot-link--loading': undoLoading,
|
||||||
|
}"
|
||||||
|
@click="undo(false)"
|
||||||
|
>
|
||||||
|
<i class="sidebar__foot-link-icon iconoir-undo"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="sidebar__foot-link"
|
||||||
|
:class="{
|
||||||
|
'sidebar__foot-link--loading': redoLoading,
|
||||||
|
}"
|
||||||
|
@click="redo(false)"
|
||||||
|
>
|
||||||
|
<i class="sidebar__foot-link-icon iconoir-redo"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import undoRedo from '@baserow/modules/core/mixins/undoRedo'
|
||||||
|
import ExternalLinkBaserowLogo from '@baserow/modules/core/components/ExternalLinkBaserowLogo'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarFoot',
|
||||||
|
components: { ExternalLinkBaserowLogo },
|
||||||
|
mixins: [undoRedo],
|
||||||
|
}
|
||||||
|
</script>
|
189
web-frontend/modules/core/components/sidebar/SidebarMenu.vue
Normal file
189
web-frontend/modules/core/components/sidebar/SidebarMenu.vue
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
<template>
|
||||||
|
<div class="sidebar__section">
|
||||||
|
<ul class="tree">
|
||||||
|
<nuxt-link
|
||||||
|
v-slot="{ href, navigate, isExactActive }"
|
||||||
|
:to="{
|
||||||
|
name: 'workspace',
|
||||||
|
params: {
|
||||||
|
workspaceId: selectedWorkspace.id,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="tree__item"
|
||||||
|
:class="{
|
||||||
|
active: isExactActive,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="tree__action sidebar__action">
|
||||||
|
<a :href="href" class="tree__link" @click="navigate">
|
||||||
|
<i class="tree__icon iconoir-home-simple"></i>
|
||||||
|
<span class="tree__link-text">
|
||||||
|
<span class="sidebar__item-name">{{ $t('sidebar.home') }}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</nuxt-link>
|
||||||
|
|
||||||
|
<li class="tree__item">
|
||||||
|
<div class="tree__action tree__action--has-counter">
|
||||||
|
<a
|
||||||
|
class="tree__link"
|
||||||
|
@click="$refs.notificationPanel.toggle($event.currentTarget)"
|
||||||
|
>
|
||||||
|
<i class="tree__icon tree__icon--type iconoir-bell"></i>
|
||||||
|
<span class="tree__link-text">{{
|
||||||
|
$t('sidebar.notifications')
|
||||||
|
}}</span>
|
||||||
|
</a>
|
||||||
|
<BadgeCounter
|
||||||
|
v-show="unreadNotificationCount"
|
||||||
|
class="tree__counter"
|
||||||
|
:count="unreadNotificationCount"
|
||||||
|
:limit="10"
|
||||||
|
>
|
||||||
|
</BadgeCounter>
|
||||||
|
</div>
|
||||||
|
<NotificationPanel ref="notificationPanel" />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<nuxt-link
|
||||||
|
v-if="
|
||||||
|
$hasPermission(
|
||||||
|
'workspace.list_workspace_users',
|
||||||
|
selectedWorkspace,
|
||||||
|
selectedWorkspace.id
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-slot="{ href, navigate, isExactActive }"
|
||||||
|
:to="{
|
||||||
|
name: 'settings-members',
|
||||||
|
params: {
|
||||||
|
workspaceId: selectedWorkspace.id,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="tree__item"
|
||||||
|
:class="{
|
||||||
|
active: isExactActive,
|
||||||
|
}"
|
||||||
|
data-highlight="members"
|
||||||
|
>
|
||||||
|
<div class="tree__action sidebar__action">
|
||||||
|
<a :href="href" class="tree__link" @click="navigate">
|
||||||
|
<i class="tree__icon iconoir-group"></i>
|
||||||
|
<span class="tree__link-text">
|
||||||
|
<span class="sidebar__item-name">{{
|
||||||
|
$t('sidebar.members')
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="selectedWorkspace.users.length"
|
||||||
|
class="sidebar__item-count"
|
||||||
|
>
|
||||||
|
{{ selectedWorkspace.users.length }}</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</nuxt-link>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-if="
|
||||||
|
$hasPermission(
|
||||||
|
'workspace.create_invitation',
|
||||||
|
selectedWorkspace,
|
||||||
|
selectedWorkspace.id
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="tree__item"
|
||||||
|
>
|
||||||
|
<div class="tree__action sidebar__action">
|
||||||
|
<a class="tree__link" @click="$refs.inviteModal.show()">
|
||||||
|
<i class="tree__icon iconoir-add-user"></i>
|
||||||
|
<span class="tree__link-text">
|
||||||
|
<span class="sidebar__item-name">{{
|
||||||
|
$t('sidebar.inviteOthers')
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<WorkspaceMemberInviteModal
|
||||||
|
ref="inviteModal"
|
||||||
|
:workspace="selectedWorkspace"
|
||||||
|
@invite-submitted="handleInvite"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
v-for="(component, index) in sidebarWorkspaceComponents"
|
||||||
|
:key="'sidebarWorkspaceComponents' + index"
|
||||||
|
:workspace="selectedWorkspace"
|
||||||
|
></component>
|
||||||
|
<li class="tree__item">
|
||||||
|
<div class="tree__action sidebar__action">
|
||||||
|
<a class="tree__link" @click="$refs.trashModal.show()">
|
||||||
|
<i class="tree__icon iconoir-bin"></i>
|
||||||
|
<span class="tree__link-text">
|
||||||
|
<span class="sidebar__item-name">{{ $t('sidebar.trash') }}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<TrashModal ref="trashModal"></TrashModal>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
import TrashModal from '@baserow/modules/core/components/trash/TrashModal'
|
||||||
|
import NotificationPanel from '@baserow/modules/core/components/NotificationPanel'
|
||||||
|
import WorkspaceMemberInviteModal from '@baserow/modules/core/components/workspace/WorkspaceMemberInviteModal'
|
||||||
|
import BadgeCounter from '@baserow/modules/core/components/BadgeCounter'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarMenu',
|
||||||
|
components: {
|
||||||
|
TrashModal,
|
||||||
|
NotificationPanel,
|
||||||
|
WorkspaceMemberInviteModal,
|
||||||
|
BadgeCounter,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
selectedWorkspace: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sidebarWorkspaceComponents() {
|
||||||
|
return Object.values(this.$registry.getAll('plugin'))
|
||||||
|
.flatMap((plugin) =>
|
||||||
|
plugin.getSidebarWorkspaceComponents(this.selectedWorkspace)
|
||||||
|
)
|
||||||
|
.filter((component) => component !== null)
|
||||||
|
},
|
||||||
|
...mapGetters({
|
||||||
|
unreadNotificationCount: 'notification/getUnreadCount',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleInvite(event) {
|
||||||
|
if (this.$route.name !== 'settings-invites') {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'settings-invites',
|
||||||
|
params: {
|
||||||
|
workspaceId: this.selectedWorkspace.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<Context :max-height-if-outside-viewport="true" class="select">
|
||||||
|
<ul
|
||||||
|
ref="dropdown"
|
||||||
|
v-auto-overflow-scroll
|
||||||
|
class="select__items select__items--no-max-height dashboard__user-workspaces"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="workspace in workspaces"
|
||||||
|
:key="workspace.id"
|
||||||
|
class="select__item"
|
||||||
|
:class="{
|
||||||
|
'select__item--loading': workspace._.loading,
|
||||||
|
active: workspace.id === selectedWorkspace.id,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
v-if="workspace.id === selectedWorkspace.id"
|
||||||
|
class="sidebar__workspace-active-icon iconoir-check"
|
||||||
|
></i>
|
||||||
|
<a
|
||||||
|
class="select__item-link"
|
||||||
|
@click=";[$emit('selected-workspace', workspace), hide()]"
|
||||||
|
>
|
||||||
|
<div class="select__item-name">
|
||||||
|
<Avatar
|
||||||
|
class="dashboard__user-workspace-avatar"
|
||||||
|
:initials="workspace.name | nameAbbreviation"
|
||||||
|
></Avatar>
|
||||||
|
{{ workspace.name }}
|
||||||
|
<span
|
||||||
|
v-if="hasUnreadNotifications(workspace.id)"
|
||||||
|
class="sidebar__unread-notifications-icon"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="select__item">
|
||||||
|
<a class="select__item-link" @click="$refs.createWorkspaceModal.show()">
|
||||||
|
<div class="select__item-name">
|
||||||
|
<ButtonIcon
|
||||||
|
tag="a"
|
||||||
|
size="small"
|
||||||
|
type="secondary"
|
||||||
|
icon="iconoir-plus"
|
||||||
|
style="pointer-events: none"
|
||||||
|
/>
|
||||||
|
<span>{{ $t('sidebar.addNewWorkspace') }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="sidebar__user">
|
||||||
|
<div class="sidebar__user-info">
|
||||||
|
<span class="sidebar__user-email">
|
||||||
|
{{ email }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
v-for="(component, index) in highestLicenceTypeBadge"
|
||||||
|
:key="index"
|
||||||
|
class="sidebar__user-license"
|
||||||
|
></component>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="context__menu margin-bottom-0">
|
||||||
|
<li v-if="isStaff" class="context__menu-item">
|
||||||
|
<a class="context__menu-item-link" @click="admin">
|
||||||
|
<i class="context__menu-item-icon iconoir-settings"></i>
|
||||||
|
{{ $t('sidebar.adminTools') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="context__menu-item">
|
||||||
|
<a
|
||||||
|
class="context__menu-item-link"
|
||||||
|
@click="$refs.settingsModal.show()"
|
||||||
|
>
|
||||||
|
<i class="context__menu-item-icon iconoir-user-circle"></i>
|
||||||
|
{{ $t('sidebar.settings') }}
|
||||||
|
</a>
|
||||||
|
<SettingsModal ref="settingsModal"></SettingsModal>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class="context__menu-item context__menu-item--with-separator margin-bottom-0"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="context__menu-item-link"
|
||||||
|
:class="{ 'context__menu-item-link--loading': logoffLoading }"
|
||||||
|
@click="logoff()"
|
||||||
|
>
|
||||||
|
<i class="context__menu-item-icon iconoir-log-out"></i>
|
||||||
|
{{ $t('sidebar.logoff') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<CreateWorkspaceModal ref="createWorkspaceModal"></CreateWorkspaceModal>
|
||||||
|
</Context>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
import { logoutAndRedirectToLogin } from '@baserow/modules/core/utils/auth'
|
||||||
|
import context from '@baserow/modules/core/mixins/context'
|
||||||
|
import SettingsModal from '@baserow/modules/core/components/settings/SettingsModal'
|
||||||
|
import CreateWorkspaceModal from '@baserow/modules/core/components/workspace/CreateWorkspaceModal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarUserContext',
|
||||||
|
components: { SettingsModal, CreateWorkspaceModal },
|
||||||
|
mixins: [context],
|
||||||
|
props: {
|
||||||
|
workspaces: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedWorkspace: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logoffLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
highestLicenceTypeBadge() {
|
||||||
|
return Object.values(this.$registry.getAll('plugin'))
|
||||||
|
.map((plugin) => plugin.getHighestLicenseTypeBadge())
|
||||||
|
.filter((component) => component !== null)
|
||||||
|
},
|
||||||
|
adminTypes() {
|
||||||
|
return this.$registry.getAll('admin')
|
||||||
|
},
|
||||||
|
sortedAdminTypes() {
|
||||||
|
return Object.values(this.adminTypes)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.getOrder() - b.getOrder())
|
||||||
|
},
|
||||||
|
...mapGetters({
|
||||||
|
isStaff: 'auth/isStaff',
|
||||||
|
name: 'auth/getName',
|
||||||
|
email: 'auth/getUsername',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
logoff() {
|
||||||
|
this.logoffLoading = true
|
||||||
|
logoutAndRedirectToLogin(
|
||||||
|
this.$nuxt.$router,
|
||||||
|
this.$store,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async admin() {
|
||||||
|
this.$emit('toggle-admin', true)
|
||||||
|
this.hide()
|
||||||
|
const activatedAdminTypes = this.sortedAdminTypes.filter(
|
||||||
|
(adminType) => !adminType.isDeactivated()
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
await this.$router.push({ name: activatedAdminTypes[0].routeName })
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
hasUnreadNotifications(workspaceId) {
|
||||||
|
return this.$store.getters['notification/workspaceHasUnread'](workspaceId)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,183 @@
|
||||||
|
<template>
|
||||||
|
<div class="sidebar__section sidebar__section--scrollable">
|
||||||
|
<div v-if="applicationsCount" class="sidebar__section-scrollable">
|
||||||
|
<div
|
||||||
|
class="sidebar__section-scrollable-inner"
|
||||||
|
data-highlight="applications"
|
||||||
|
>
|
||||||
|
<ul v-if="pendingJobs[null].length" class="tree">
|
||||||
|
<component
|
||||||
|
:is="getPendingJobComponent(job)"
|
||||||
|
v-for="job in pendingJobs[null]"
|
||||||
|
:key="job.id"
|
||||||
|
:job="job"
|
||||||
|
>
|
||||||
|
</component>
|
||||||
|
</ul>
|
||||||
|
<ul class="tree">
|
||||||
|
<div
|
||||||
|
v-for="applicationGroup in groupedApplicationsForSelectedWorkspace"
|
||||||
|
:key="applicationGroup.type"
|
||||||
|
>
|
||||||
|
<template v-if="applicationGroup.applications.length > 0">
|
||||||
|
<div class="tree__heading">
|
||||||
|
{{ applicationGroup.name }}
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
class="tree"
|
||||||
|
:class="{
|
||||||
|
'margin-bottom-0': pendingJobs[applicationGroup.type].length,
|
||||||
|
}"
|
||||||
|
data-highlight="applications"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="getApplicationComponent(application)"
|
||||||
|
v-for="application in applicationGroup.applications"
|
||||||
|
:key="application.id"
|
||||||
|
v-sortable="{
|
||||||
|
id: application.id,
|
||||||
|
update: orderApplications,
|
||||||
|
handle: '[data-sortable-handle]',
|
||||||
|
marginTop: -1.5,
|
||||||
|
enabled: $hasPermission(
|
||||||
|
'workspace.order_applications',
|
||||||
|
selectedWorkspace,
|
||||||
|
selectedWorkspace.id
|
||||||
|
),
|
||||||
|
}"
|
||||||
|
:application="application"
|
||||||
|
:pending-jobs="pendingJobs[application.type]"
|
||||||
|
:workspace="selectedWorkspace"
|
||||||
|
></component>
|
||||||
|
</ul>
|
||||||
|
<ul v-if="pendingJobs[applicationGroup.type].length" class="tree">
|
||||||
|
<component
|
||||||
|
:is="getPendingJobComponent(job)"
|
||||||
|
v-for="job in pendingJobs[applicationGroup.type]"
|
||||||
|
:key="job.id"
|
||||||
|
:job="job"
|
||||||
|
>
|
||||||
|
</component>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
$hasPermission(
|
||||||
|
'workspace.create_application',
|
||||||
|
selectedWorkspace,
|
||||||
|
selectedWorkspace.id
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="sidebar__new-wrapper"
|
||||||
|
:class="{ 'sidebar__new-wrapper--separator': applicationsCount > 0 }"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
ref="createApplicationContextLink"
|
||||||
|
class="sidebar__new"
|
||||||
|
@click="
|
||||||
|
$refs.createApplicationContext.toggle(
|
||||||
|
$refs.createApplicationContextLink
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i class="sidebar__new-icon iconoir-plus"></i>
|
||||||
|
{{ $t('action.createNew') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<CreateApplicationContext
|
||||||
|
ref="createApplicationContext"
|
||||||
|
:workspace="selectedWorkspace"
|
||||||
|
></CreateApplicationContext>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
|
import CreateApplicationContext from '@baserow/modules/core/components/application/CreateApplicationContext'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarWithWorkspace',
|
||||||
|
components: { CreateApplicationContext },
|
||||||
|
props: {
|
||||||
|
applications: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedWorkspace: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* Because all the applications that belong to the user are in the store we will
|
||||||
|
* filter on the selected workspace here.
|
||||||
|
*/
|
||||||
|
groupedApplicationsForSelectedWorkspace() {
|
||||||
|
const applicationTypes = Object.values(
|
||||||
|
this.$registry.getAll('application')
|
||||||
|
).map((applicationType) => {
|
||||||
|
return {
|
||||||
|
name: applicationType.getNamePlural(),
|
||||||
|
type: applicationType.getType(),
|
||||||
|
applications: this.applications
|
||||||
|
.filter((application) => {
|
||||||
|
return (
|
||||||
|
application.workspace.id === this.selectedWorkspace.id &&
|
||||||
|
application.type === applicationType.getType()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.order - b.order),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return applicationTypes
|
||||||
|
},
|
||||||
|
applicationsCount() {
|
||||||
|
return this.groupedApplicationsForSelectedWorkspace.reduce(
|
||||||
|
(acc, group) => acc + group.applications.length,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
pendingJobs() {
|
||||||
|
const grouped = { null: [] }
|
||||||
|
Object.values(this.$registry.getAll('application')).forEach(
|
||||||
|
(applicationType) => {
|
||||||
|
grouped[applicationType.getType()] = []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.$store.getters['job/getAll'].forEach((job) => {
|
||||||
|
const jobType = this.$registry.get('job', job.type)
|
||||||
|
if (jobType.isJobPartOfWorkspace(job, this.selectedWorkspace)) {
|
||||||
|
grouped[jobType.getSidebarApplicationTypeLocation(job)].push(job)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return grouped
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getApplicationComponent(application) {
|
||||||
|
return this.$registry
|
||||||
|
.get('application', application.type)
|
||||||
|
.getSidebarComponent()
|
||||||
|
},
|
||||||
|
getPendingJobComponent(job) {
|
||||||
|
return this.$registry.get('job', job.type).getSidebarComponent()
|
||||||
|
},
|
||||||
|
async orderApplications(order, oldOrder) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('application/order', {
|
||||||
|
workspace: this.selectedWorkspace,
|
||||||
|
order,
|
||||||
|
oldOrder,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notifyIf(error, 'application')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<div class="sidebar__section sidebar__section--scrollable">
|
||||||
|
<div class="sidebar__section-scrollable">
|
||||||
|
<div class="sidebar__section-scrollable-inner">
|
||||||
|
<p
|
||||||
|
v-if="workspaces.length === 0"
|
||||||
|
class="margin-left-1 margin-right-1 margin-top-1 margin-bottom-1"
|
||||||
|
>
|
||||||
|
{{ $t('sidebar.errorNoWorkspace') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar__new-wrapper sidebar__new-wrapper--separator">
|
||||||
|
<a
|
||||||
|
v-if="$hasPermission('create_workspace')"
|
||||||
|
class="sidebar__new"
|
||||||
|
@click="$refs.createWorkspaceModal.show()"
|
||||||
|
>
|
||||||
|
<i class="sidebar__new-icon iconoir-plus"></i>
|
||||||
|
{{ $t('sidebar.createWorkspace') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<CreateWorkspaceModal ref="createWorkspaceModal"></CreateWorkspaceModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CreateWorkspaceModal from '@baserow/modules/core/components/workspace/CreateWorkspaceModal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarWithoutWorkspace',
|
||||||
|
components: { CreateWorkspaceModal },
|
||||||
|
props: {
|
||||||
|
workspaces: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasUnreadNotifications(workspaceId) {
|
||||||
|
return this.$store.getters['notification/workspaceHasUnread'](workspaceId)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div v-show="!collapsed" class="sidebar__nav">
|
<div
|
||||||
|
v-show="!collapsed"
|
||||||
|
class="sidebar__section sidebar__section--scrollable"
|
||||||
|
>
|
||||||
|
<div class="sidebar__section-scrollable">
|
||||||
|
<div class="sidebar__section-scrollable-inner">
|
||||||
<ul class="tree">
|
<ul class="tree">
|
||||||
<li class="tree__item margin-top-2">
|
|
||||||
<div class="tree__link tree__link--group">
|
|
||||||
<span class="tree__link-text">{{ template.name }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<component
|
<component
|
||||||
:is="getApplicationComponent(application)"
|
:is="getApplicationComponent(application)"
|
||||||
v-for="application in sortedApplications"
|
v-for="application in sortedApplications"
|
||||||
|
@ -18,7 +18,13 @@
|
||||||
></component>
|
></component>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar__foot">
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar__section sidebar__section--bottom">
|
||||||
|
<div
|
||||||
|
class="sidebar__foot"
|
||||||
|
:class="{ 'sidebar__foot--collapsed': collapsed }"
|
||||||
|
>
|
||||||
<div class="sidebar__logo">
|
<div class="sidebar__logo">
|
||||||
<Logo height="14" alt="Baserow logo" />
|
<Logo height="14" alt="Baserow logo" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,6 +38,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -8,10 +8,7 @@
|
||||||
>
|
>
|
||||||
<div class="tree__action">
|
<div class="tree__action">
|
||||||
<a class="tree__link">
|
<a class="tree__link">
|
||||||
<i
|
<i class="tree__icon" :class="application._.type.iconClass"></i>
|
||||||
class="tree__icon tree__icon--type"
|
|
||||||
:class="application._.type.iconClass"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">{{ application.name }}</span>
|
<span class="tree__link-text">{{ application.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
<template>
|
|
||||||
<Context
|
|
||||||
ref="workspacesContext"
|
|
||||||
class="select"
|
|
||||||
:max-height-if-outside-viewport="true"
|
|
||||||
>
|
|
||||||
<div class="select__search">
|
|
||||||
<i class="select__search-icon iconoir-search"></i>
|
|
||||||
<input
|
|
||||||
v-model="query"
|
|
||||||
type="text"
|
|
||||||
class="select__search-input"
|
|
||||||
:placeholder="$t('workspacesContext.search')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="isLoading" class="context--loading">
|
|
||||||
<div class="loading"></div>
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
v-if="!isLoading && isLoaded && workspaces.length > 0"
|
|
||||||
v-auto-overflow-scroll
|
|
||||||
class="select__items select__items--no-max-height"
|
|
||||||
>
|
|
||||||
<WorkspacesContextItem
|
|
||||||
v-for="workspace in searchAndSort(workspaces)"
|
|
||||||
:key="workspace.id"
|
|
||||||
v-sortable="{ id: workspace.id, update: order, marginTop: -1.5 }"
|
|
||||||
:workspace="workspace"
|
|
||||||
@selected="hide"
|
|
||||||
></WorkspacesContextItem>
|
|
||||||
</ul>
|
|
||||||
<div
|
|
||||||
v-if="!isLoading && isLoaded && workspaces.length == 0"
|
|
||||||
class="context__description"
|
|
||||||
>
|
|
||||||
{{ $t('workspacesContext.noResults') }}
|
|
||||||
</div>
|
|
||||||
<div class="select__footer">
|
|
||||||
<a
|
|
||||||
v-if="$hasPermission('create_workspace')"
|
|
||||||
class="select__footer-button"
|
|
||||||
@click="$refs.createWorkspaceModal.show()"
|
|
||||||
>
|
|
||||||
<i class="iconoir-plus"></i>
|
|
||||||
{{ $t('workspacesContext.createWorkspace') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<CreateWorkspaceModal
|
|
||||||
ref="createWorkspaceModal"
|
|
||||||
@created="hide"
|
|
||||||
></CreateWorkspaceModal>
|
|
||||||
</Context>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters, mapState } from 'vuex'
|
|
||||||
|
|
||||||
import CreateWorkspaceModal from '@baserow/modules/core/components/workspace/CreateWorkspaceModal'
|
|
||||||
import WorkspacesContextItem from '@baserow/modules/core/components/workspace/WorkspacesContextItem'
|
|
||||||
import context from '@baserow/modules/core/mixins/context'
|
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
|
||||||
import { escapeRegExp } from '@baserow/modules/core/utils/string'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'WorkspacesContext',
|
|
||||||
components: {
|
|
||||||
CreateWorkspaceModal,
|
|
||||||
WorkspacesContextItem,
|
|
||||||
},
|
|
||||||
mixins: [context],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
query: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
workspaces: (state) => state.workspace.items,
|
|
||||||
}),
|
|
||||||
...mapGetters({
|
|
||||||
isLoading: 'workspace/isLoading',
|
|
||||||
isLoaded: 'workspace/isLoaded',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* When the workspaces context select is opened for the first time we must make
|
|
||||||
* sure that all the workspaces are already loaded or going to be loaded.
|
|
||||||
*/
|
|
||||||
toggle(...args) {
|
|
||||||
this.$store.dispatch('workspace/loadAll')
|
|
||||||
this.getRootContext().toggle(...args)
|
|
||||||
},
|
|
||||||
searchAndSort(workspaces) {
|
|
||||||
const query = this.query
|
|
||||||
|
|
||||||
return workspaces
|
|
||||||
.filter(function (workspace) {
|
|
||||||
const regex = new RegExp('(' + escapeRegExp(query) + ')', 'i')
|
|
||||||
return workspace.name.match(regex)
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.order - b.order
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async order(order, oldOrder) {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('workspace/order', {
|
|
||||||
order,
|
|
||||||
oldOrder,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
notifyIf(error, 'workspace')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,68 +0,0 @@
|
||||||
<template>
|
|
||||||
<li
|
|
||||||
class="select__item"
|
|
||||||
:class="{
|
|
||||||
active: workspace._.selected,
|
|
||||||
'select__item--loading':
|
|
||||||
workspace._.loading || workspace._.additionalLoading,
|
|
||||||
'select__item--has-notification': hasUnreadNotifications,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<a class="select__item-link" @click="selectWorkspace(workspace)">
|
|
||||||
<div :title="workspace.name" class="select__item-name">
|
|
||||||
<span class="select__item-name-text">
|
|
||||||
<Editable
|
|
||||||
ref="rename"
|
|
||||||
:value="workspace.name"
|
|
||||||
@change="renameWorkspace(workspace, $event)"
|
|
||||||
></Editable>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="hasUnreadNotifications"
|
|
||||||
class="sidebar__unread-notifications-icon"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<i
|
|
||||||
v-if="workspace._.selected"
|
|
||||||
class="select__item-active-icon iconoir-check"
|
|
||||||
></i>
|
|
||||||
<a
|
|
||||||
ref="contextLink"
|
|
||||||
class="select__item-options"
|
|
||||||
@click="$refs.context.toggle($refs.contextLink, 'bottom', 'right', 0)"
|
|
||||||
@mousedown.stop
|
|
||||||
>
|
|
||||||
<i class="baserow-icon-more-vertical"></i>
|
|
||||||
</a>
|
|
||||||
<WorkspaceContext
|
|
||||||
ref="context"
|
|
||||||
:workspace="workspace"
|
|
||||||
@rename="enableRename()"
|
|
||||||
></WorkspaceContext>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import WorkspaceContext from '@baserow/modules/core/components/workspace/WorkspaceContext'
|
|
||||||
import editWorkspace from '@baserow/modules/core/mixins/editWorkspace'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'WorkspacesContextItem',
|
|
||||||
components: { WorkspaceContext },
|
|
||||||
mixins: [editWorkspace],
|
|
||||||
props: {
|
|
||||||
workspace: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasUnreadNotifications() {
|
|
||||||
return this.$store.getters['notification/workspaceHasUnread'](
|
|
||||||
this.workspace.id
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -41,6 +41,16 @@ export class JobType extends Registerable {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The left sidebar groups the applications. This method can optionally return an
|
||||||
|
* application type. If that's the case, then the job animation will be put in
|
||||||
|
* that application group. If nothing is provided, then the job will be put outside
|
||||||
|
* of the applications.
|
||||||
|
*/
|
||||||
|
getSidebarApplicationTypeLocation(job) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args)
|
super(...args)
|
||||||
this.type = this.getType()
|
this.type = this.getType()
|
||||||
|
@ -125,6 +135,10 @@ export class DuplicateApplicationJobType extends JobType {
|
||||||
return SidebarApplicationPendingJob
|
return SidebarApplicationPendingJob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSidebarApplicationTypeLocation(job) {
|
||||||
|
return job.original_application.type
|
||||||
|
}
|
||||||
|
|
||||||
isJobPartOfWorkspace(job, workspace) {
|
isJobPartOfWorkspace(job, workspace) {
|
||||||
return job.original_application.workspace.id === workspace.id
|
return job.original_application.workspace.id === workspace.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Toasts></Toasts>
|
<Toasts></Toasts>
|
||||||
<div :class="{ 'layout--collapsed': isCollapsed }" class="layout">
|
<div class="layout">
|
||||||
<div class="layout__col-1">
|
<div class="layout__col-1">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
:workspaces="workspaces"
|
:workspaces="workspaces"
|
||||||
:selected-workspace="selectedWorkspace"
|
:selected-workspace="selectedWorkspace"
|
||||||
:applications="applications"
|
:applications="applications"
|
||||||
:user="user"
|
|
||||||
@selected-workspace="selectedWorkspaceEvent"
|
@selected-workspace="selectedWorkspaceEvent"
|
||||||
></Sidebar>
|
></Sidebar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,8 +58,6 @@ export default {
|
||||||
}),
|
}),
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
applications: 'application/getAll',
|
applications: 'application/getAll',
|
||||||
isCollapsed: 'sidebar/isCollapsed',
|
|
||||||
user: 'auth/getUserObject',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
|
@ -23,15 +23,21 @@
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"createWorkspace": "Create workspace",
|
"createWorkspace": "Create workspace",
|
||||||
|
"addNewWorkspace": "Add new workspace",
|
||||||
"inviteOthers": "Invite others",
|
"inviteOthers": "Invite others",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
"logoff": "Logoff",
|
"logoff": "Log out",
|
||||||
"errorNoWorkspace": "You don’t have any workspaces.",
|
"errorNoWorkspace": "You don’t have any workspaces.",
|
||||||
"admin": "Admin",
|
"adminTools": "Admin tools",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
"trash": "Trash",
|
"trash": "Trash",
|
||||||
"settings": "Settings",
|
"settings": "My settings",
|
||||||
"notifications": "Notifications"
|
"notifications": "Notifications",
|
||||||
|
"adminSettings": "Admin settings",
|
||||||
|
"general": "General",
|
||||||
|
"people": "People",
|
||||||
|
"licenses": "Licenses"
|
||||||
},
|
},
|
||||||
"accountForm": {
|
"accountForm": {
|
||||||
"nameLabel": "Your name",
|
"nameLabel": "Your name",
|
||||||
|
@ -44,7 +50,7 @@
|
||||||
"submitButton": "Update account"
|
"submitButton": "Update account"
|
||||||
},
|
},
|
||||||
"settingsModal": {
|
"settingsModal": {
|
||||||
"title": "Settings"
|
"title": "My settings"
|
||||||
},
|
},
|
||||||
"notificationPanel": {
|
"notificationPanel": {
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
|
@ -157,11 +163,6 @@
|
||||||
"errorTooLongMessage": "Messages are limited to {amount} characters.",
|
"errorTooLongMessage": "Messages are limited to {amount} characters.",
|
||||||
"additionalRoles": "Additional roles"
|
"additionalRoles": "Additional roles"
|
||||||
},
|
},
|
||||||
"workspacesContext": {
|
|
||||||
"search": "Search workspaces",
|
|
||||||
"noResults": "No results found",
|
|
||||||
"createWorkspace": "Create workspace"
|
|
||||||
},
|
|
||||||
"workspaceContext": {
|
"workspaceContext": {
|
||||||
"renameWorkspace": "Rename workspace",
|
"renameWorkspace": "Rename workspace",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
|
|
|
@ -60,7 +60,6 @@ import authStore from '@baserow/modules/core/store/auth'
|
||||||
import workspaceStore from '@baserow/modules/core/store/workspace'
|
import workspaceStore from '@baserow/modules/core/store/workspace'
|
||||||
import jobStore from '@baserow/modules/core/store/job'
|
import jobStore from '@baserow/modules/core/store/job'
|
||||||
import toastStore from '@baserow/modules/core/store/toast'
|
import toastStore from '@baserow/modules/core/store/toast'
|
||||||
import sidebarStore from '@baserow/modules/core/store/sidebar'
|
|
||||||
import undoRedoStore from '@baserow/modules/core/store/undoRedo'
|
import undoRedoStore from '@baserow/modules/core/store/undoRedo'
|
||||||
import integrationStore from '@baserow/modules/core/store/integration'
|
import integrationStore from '@baserow/modules/core/store/integration'
|
||||||
import userSourceStore from '@baserow/modules/core/store/userSource'
|
import userSourceStore from '@baserow/modules/core/store/userSource'
|
||||||
|
@ -179,7 +178,6 @@ export default (context, inject) => {
|
||||||
store.registerModule('job', jobStore)
|
store.registerModule('job', jobStore)
|
||||||
store.registerModule('workspace', workspaceStore)
|
store.registerModule('workspace', workspaceStore)
|
||||||
store.registerModule('toast', toastStore)
|
store.registerModule('toast', toastStore)
|
||||||
store.registerModule('sidebar', sidebarStore)
|
|
||||||
store.registerModule('undoRedo', undoRedoStore)
|
store.registerModule('undoRedo', undoRedoStore)
|
||||||
store.registerModule('integration', integrationStore)
|
store.registerModule('integration', integrationStore)
|
||||||
store.registerModule('userSource', userSourceStore)
|
store.registerModule('userSource', userSourceStore)
|
||||||
|
|
|
@ -21,14 +21,14 @@ export class BaserowPlugin extends Registerable {
|
||||||
* Every registered plugin can have a component that's rendered at the top of the
|
* Every registered plugin can have a component that's rendered at the top of the
|
||||||
* left sidebar.
|
* left sidebar.
|
||||||
*/
|
*/
|
||||||
getSidebarTopComponent() {
|
getImpersonateComponent() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Every registered plugin can display an item in the main sidebar menu.
|
* Every registered plugin can have a component displaying a badge with the highest license type
|
||||||
*/
|
*/
|
||||||
getSidebarMainMenuComponent() {
|
getHighestLicenseTypeBadge() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
export const state = () => ({
|
|
||||||
collapsed: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const mutations = {
|
|
||||||
SET_COLLAPSED(state, collapsed) {
|
|
||||||
state.collapsed = collapsed
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
toggleCollapsed({ commit, getters }, value) {
|
|
||||||
if (value === undefined) {
|
|
||||||
value = !getters.isCollapsed
|
|
||||||
}
|
|
||||||
commit('SET_COLLAPSED', value)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getters = {
|
|
||||||
isCollapsed(state) {
|
|
||||||
return !!state.collapsed
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations,
|
|
||||||
}
|
|
|
@ -30,6 +30,11 @@ export class DatabaseApplicationType extends ApplicationType {
|
||||||
return i18n.t('applicationType.database')
|
return i18n.t('applicationType.database')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNamePlural() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('applicationType.databases')
|
||||||
|
}
|
||||||
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
const { i18n } = this.app
|
const { i18n } = this.app
|
||||||
return i18n.t('applicationType.databaseDesc')
|
return i18n.t('applicationType.databaseDesc')
|
||||||
|
|
|
@ -28,8 +28,6 @@
|
||||||
v-sortable="{
|
v-sortable="{
|
||||||
id: table.id,
|
id: table.id,
|
||||||
update: orderTables,
|
update: orderTables,
|
||||||
marginLeft: 34,
|
|
||||||
marginRight: 10,
|
|
||||||
marginTop: -1.5,
|
marginTop: -1.5,
|
||||||
enabled: $hasPermission(
|
enabled: $hasPermission(
|
||||||
'database.order_tables',
|
'database.order_tables',
|
||||||
|
@ -41,15 +39,6 @@
|
||||||
:table="table"
|
:table="table"
|
||||||
></SidebarItem>
|
></SidebarItem>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="pendingJobs.length" class="tree__subs">
|
|
||||||
<component
|
|
||||||
:is="getPendingJobComponent(job)"
|
|
||||||
v-for="job in pendingJobs"
|
|
||||||
:key="job.id"
|
|
||||||
:job="job"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</ul>
|
|
||||||
<a
|
<a
|
||||||
v-if="
|
v-if="
|
||||||
$hasPermission(
|
$hasPermission(
|
||||||
|
@ -61,7 +50,7 @@
|
||||||
class="tree__sub-add"
|
class="tree__sub-add"
|
||||||
@click="$refs.importFileModal.show()"
|
@click="$refs.importFileModal.show()"
|
||||||
>
|
>
|
||||||
<i class="iconoir-plus"></i>
|
<i class="tree__sub-add-icon iconoir-plus"></i>
|
||||||
{{ $t('sidebar.createTable') }}
|
{{ $t('sidebar.createTable') }}
|
||||||
</a>
|
</a>
|
||||||
<ImportFileModal ref="importFileModal" :database="application" />
|
<ImportFileModal ref="importFileModal" :database="application" />
|
||||||
|
@ -73,7 +62,6 @@
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
import SidebarItem from '@baserow/modules/database/components/sidebar/SidebarItem'
|
import SidebarItem from '@baserow/modules/database/components/sidebar/SidebarItem'
|
||||||
import SidebarItemPendingJob from '@baserow/modules/core/components/sidebar/SidebarItemPendingJob'
|
|
||||||
import ImportFileModal from '@baserow/modules/database/components/table/ImportFileModal'
|
import ImportFileModal from '@baserow/modules/database/components/table/ImportFileModal'
|
||||||
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
||||||
|
|
||||||
|
@ -82,7 +70,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
SidebarApplication,
|
SidebarApplication,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
SidebarItemPendingJob,
|
|
||||||
ImportFileModal,
|
ImportFileModal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -101,13 +88,6 @@ export default {
|
||||||
.map((table) => table)
|
.map((table) => table)
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
},
|
},
|
||||||
pendingJobs() {
|
|
||||||
return this.$store.getters['job/getAll'].filter((job) =>
|
|
||||||
this.$registry
|
|
||||||
.get('job', job.type)
|
|
||||||
.isJobPartOfApplication(job, this.application)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
...mapGetters({ isAppSelected: 'application/isSelected' }),
|
...mapGetters({ isAppSelected: 'application/isSelected' }),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -129,9 +109,6 @@ export default {
|
||||||
notifyIf(error, 'table')
|
notifyIf(error, 'table')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPendingJobComponent(job) {
|
|
||||||
return this.$registry.get('job', job.type).getSidebarComponent()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,16 +2,12 @@
|
||||||
<li
|
<li
|
||||||
class="tree__item"
|
class="tree__item"
|
||||||
:class="{
|
:class="{
|
||||||
active: application._.selected,
|
|
||||||
'tree__item--loading': application._.loading,
|
'tree__item--loading': application._.loading,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="tree__action">
|
<div class="tree__action">
|
||||||
<a class="tree__link" @click="$emit('selected', application)">
|
<a class="tree__link" @click="$emit('selected', application)">
|
||||||
<i
|
<i class="tree__icon" :class="application._.type.iconClass"></i>
|
||||||
class="tree__icon tree__icon--type"
|
|
||||||
:class="application._.type.iconClass"
|
|
||||||
></i>
|
|
||||||
<span class="tree__link-text">{{ application.name }}</span>
|
<span class="tree__link-text">{{ application.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"viewAPI": "View API Docs",
|
"viewAPI": "View API Docs",
|
||||||
"createTable": "Create table"
|
"createTable": "New table"
|
||||||
},
|
},
|
||||||
"sidebarItem": {
|
"sidebarItem": {
|
||||||
"exportTable": "Export table",
|
"exportTable": "Export table",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue