-
Notifications
You must be signed in to change notification settings - Fork 274
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6311 from inverse-inc/feature/composition-prefere…
…nces UI User Preferences
- Loading branch information
Showing
11 changed files
with
974 additions
and
196 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
html/pfappserver/root/src/composables/usePreferences.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { ref, watch } from '@vue/composition-api' | ||
import { createDebouncer } from 'promised-debounce' | ||
import store from '@/store' // required for 'system/version' | ||
import apiCall from '@/utils/api' | ||
|
||
export const IDENTIFIER_PREFIX = 'pfappserver::' // transparently prefix all identifiers - avoid key collisions | ||
|
||
export const api = { | ||
allPreferences: () => { | ||
return apiCall.getQuiet('preferences').then(response => { | ||
return response.data.items | ||
}) | ||
}, | ||
getPreference: id => { | ||
return apiCall.getQuiet(`preference/${IDENTIFIER_PREFIX}${id}`).then(response => { | ||
return response.data.item | ||
}) | ||
}, | ||
setPreference: (id, data) => { | ||
if (data) { | ||
let body = { | ||
id: `${IDENTIFIER_PREFIX}${id}`, | ||
value: JSON.stringify({ | ||
data, | ||
meta: { | ||
created_at: (new Date()).getTime(), | ||
updated_at: (new Date()).getTime(), | ||
version: store.getters['system/version'] | ||
} | ||
}) | ||
} | ||
return apiCall.getQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => { // exists | ||
const { data: { item: { value = null } = {} } = {} } = response | ||
if (value) { | ||
const { meta: { created_at = null } = {} } = JSON.parse(value) | ||
if (created_at) { // retain `created_at` | ||
body = { | ||
id: `${IDENTIFIER_PREFIX}${id}`, | ||
value: JSON.stringify({ | ||
data, | ||
meta: { | ||
created_at: created_at, | ||
updated_at: (new Date()).getTime(), | ||
version: store.getters['system/version'] | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
return apiCall.putQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`], body).then(response => { | ||
return response.data | ||
}) | ||
}).catch(() => { // not exists | ||
return apiCall.putQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`], body).then(response => { | ||
return response.data | ||
}) | ||
}) | ||
} | ||
else { | ||
return apiCall.deleteQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => { | ||
return response | ||
}) | ||
} | ||
}, | ||
removePreference: id => { | ||
return apiCall.deleteQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => { | ||
return response | ||
}) | ||
} | ||
} | ||
|
||
export const usePreferences = () => { | ||
|
||
const preferences = ref(undefined) | ||
|
||
api.allPreferences() | ||
.then(response => { | ||
preferences.value = response.map(preference => { | ||
const { id, value } = preference || {} | ||
const _id = id.substr(IDENTIFIER_PREFIX.length) // strip IDENTIFIER_PREFIX | ||
const get = () => api.getPreference(_id) | ||
const set = data => api.setPreference(_id, data) | ||
const remove = () => api.removePreference(_id) | ||
return { | ||
id: _id, | ||
value: JSON.parse(value), // parse | ||
get, set, remove // methods | ||
} | ||
}) | ||
}) | ||
.catch(() => { | ||
preferences.value = [] | ||
}) | ||
|
||
return preferences | ||
} | ||
|
||
export const usePreference = (id, defaultValue) => { | ||
|
||
const preference = ref(defaultValue) | ||
|
||
api.getPreference(id) | ||
.then(response => { | ||
const { value } = response || {} | ||
const { data } = JSON.parse(value || '{}') | ||
preference.value = data | ||
}) | ||
.catch(() => { | ||
preference.value = defaultValue | ||
}) | ||
.finally(() => { | ||
// watch after initial mutation | ||
let debouncer | ||
watch(preference, () => { | ||
if (!debouncer) | ||
debouncer = createDebouncer() | ||
debouncer({ | ||
handler: () => { | ||
if (!preference.value) | ||
api.removePreference(id) | ||
else | ||
api.setPreference(id, preference.value) | ||
}, | ||
time: 1000 | ||
}) | ||
}, { deep: true }) | ||
}) | ||
|
||
return preference | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
html/pfappserver/root/src/views/Preferences/_components/TabPermissions.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<template> | ||
<b-tab :title="$t('Permissions')"> | ||
<b-card no-body class="mb-3"> | ||
<b-card-header> | ||
<h4 class="mb-0">{{ $t('Roles') }}</h4> | ||
</b-card-header> | ||
<b-card-body class="px-3 pt-3 pb-0"> | ||
<b-row> | ||
<b-col cols="3" v-for="(acls, role) in aclContextAssociated" :key="role"> | ||
<b-card class="mb-3" :title="roleToString(role)"> | ||
<b-badge v-for="acl in acls" :key="`${role}_${acl}`" | ||
:title="`${role}_${acl}`" v-b-tooltip.hover.top.d300 | ||
variant="secondary" class="mr-1">{{ acl }}</b-badge> | ||
</b-card> | ||
</b-col> | ||
</b-row> | ||
</b-card-body> | ||
</b-card> | ||
</b-tab> | ||
</template> | ||
<script> | ||
const components = {} | ||
import { computed } from '@vue/composition-api' | ||
const setup = (props, context) => { | ||
const { root: { $store } = {} } = context | ||
const aclContext = computed(() => $store.getters['session/aclContext']) | ||
const aclContextAssociated = computed(() => aclContext.value | ||
.sort((a, b) => a.localeCompare(b)) | ||
.reduce((associated, role) => { | ||
const [ suffix, ...prefixes ] = role.split('_').reverse() | ||
if (['CREATE', 'DELETE', 'READ', 'UPDATE', 'WRITE'].includes(suffix)) { // split by last '_' | ||
const prefix = prefixes.reverse().join('_') | ||
if (!(prefix in associated)) | ||
associated[prefix] = [] | ||
associated[prefix].push(suffix) | ||
} | ||
else { // split by first '_' | ||
const [ prefix, ...suffixes ] = role.split('_') | ||
const suffix = suffixes.join('_') | ||
if (!(prefix in associated)) | ||
associated[prefix] = [] | ||
associated[prefix].push(suffix) | ||
} | ||
return associated | ||
}, {}) | ||
) | ||
const roleToString = (role) => { | ||
return role | ||
.split('_') | ||
.map(role => `${role.charAt(0)}${role.slice(1).toLowerCase()}`) | ||
.join(' ') | ||
} | ||
return { | ||
aclContextAssociated, | ||
roleToString | ||
} | ||
} | ||
// @vue/component | ||
export default { | ||
name: 'tab-permissions', | ||
inheritAttrs: false, | ||
components, | ||
setup | ||
} | ||
</script> |
Oops, something went wrong.