Browse Source

feat(gui): add icon change option for view

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4630/head
Pranav C 2 years ago
parent
commit
aed58b8bfd
  1. 26
      packages/nc-gui/components/general/ViewIcon.vue
  2. 20
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  3. 29
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  4. 7
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  5. 2
      packages/nocodb-sdk/src/lib/Api.ts
  6. 15
      packages/nocodb/src/lib/models/Model.ts
  7. 12
      packages/nocodb/src/lib/models/View.ts
  8. 33
      packages/nocodb/src/lib/utils/modelUtils.ts
  9. 4
      scripts/sdk/swagger.json

26
packages/nc-gui/components/general/ViewIcon.vue

@ -0,0 +1,26 @@
<script lang="ts" setup>
import { Icon as IcIcon } from '@iconify/vue'
import type { TableType } from 'nocodb-sdk'
import { viewIcons } from '#imports'
const { meta: viewMeta } = defineProps<{
meta: TableType
}>()
</script>
<template>
<IcIcon
v-if="viewMeta?.meta?.icon"
:data-testid="`nc-icon-${viewMeta?.meta?.icon}`"
class="text-[16px]"
:icon="viewMeta?.meta?.icon"
></IcIcon>
<component
:is="viewIcons[viewMeta.type]?.icon"
v-else
class="nc-view-icon group-hover"
:style="{ color: viewIcons[viewMeta.type]?.color }"
/>
</template>
<style scoped></style>

20
packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue

@ -140,7 +140,7 @@ const initSortable = (el: HTMLElement) => {
if (sortable) sortable.destroy()
sortable = new Sortable(el, {
handle: '.nc-drag-icon',
// handle: '.nc-drag-icon',
ghostClass: 'ghost',
onStart: onSortStart,
onEnd: onSortEnd,
@ -213,6 +213,23 @@ function openDeleteDialog(view: ViewType) {
close(1000)
}
}
const setIcon = async (icon: string, view: ViewType) => {
try {
view.meta = {
...(view.meta || {}),
icon,
}
api.dbView.update(view.id as string, {
meta: view.meta,
})
$e('a:view:icon:sidebar', { icon })
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
</script>
<template>
@ -234,6 +251,7 @@ function openDeleteDialog(view: ViewType) {
@open-modal="$emit('openModal', $event)"
@delete="openDeleteDialog"
@rename="onRename"
@select-icon="setIcon($event, view)"
/>
</a-menu>
</template>

29
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -3,7 +3,6 @@ import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity'
import {
IsLockedInj,
computed,
inject,
message,
onKeyStroke,
@ -11,7 +10,6 @@ import {
useNuxtApp,
useUIPermission,
useVModel,
viewIcons,
} from '#imports'
interface Props {
@ -21,9 +19,15 @@ interface Props {
interface Emits {
(event: 'update:view', data: Record<string, any>): void
(event: 'selectIcon', icon: string): void
(event: 'changeView', view: Record<string, any>): void
(event: 'rename', view: ViewType): void
(event: 'delete', view: ViewType): void
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string; groupingFieldColumnId?: string }): void
}
@ -48,8 +52,6 @@ let isStopped = $ref(false)
/** Original view title when editing the view name */
let originalTitle = $ref<string | undefined>()
const viewType = computed(() => vModel.value.type as number)
/** Debounce click handler, so we can potentially enable editing view name {@see onDblClick} */
const onClick = useDebounceFn(() => {
if (isEditing || isStopped) return
@ -172,17 +174,14 @@ function onStopEdit() {
@click.stop="onClick"
>
<div v-e="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2" data-testid="view-item">
<div class="flex w-auto" :data-testid="`view-sidebar-drag-handle-${vModel.alias || vModel.title}`">
<MdiDrag
class="nc-drag-icon hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 !cursor-move"
@click.stop.prevent
/>
<component
:is="viewIcons[viewType].icon"
class="nc-view-icon group-hover:hidden"
:style="{ color: viewIcons[viewType].color }"
/>
<div class="flex w-auto min-w-5" :data-testid="`view-sidebar-drag-handle-${vModel.alias || vModel.title}`">
<a-dropdown :trigger="['click']" @click.stop>
<GeneralViewIcon :meta="props.view"></GeneralViewIcon>
<template v-if="isUIAllowed('viewIconCustomisation')" #overlay>
<GeneralEmojiIcons class="shadow bg-white p-2" @select-icon="emits('selectIcon', $event)" />
</template>
</a-dropdown>
</div>
<a-input v-if="isEditing" :ref="focusInput" v-model:value="vModel.title" @blur="onCancel" @keydown="onKeyDown($event)" />

7
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -4,7 +4,6 @@ import {
IsLockedInj,
IsPublicInj,
extractSdkResponseErrorMsg,
getViewIcon,
inject,
message,
ref,
@ -93,11 +92,7 @@ useMenuCloseOnEsc(open)
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center">
<component
:is="getViewIcon(selectedView?.type)?.icon"
class="nc-view-icon group-hover:hidden"
:style="{ color: getViewIcon(selectedView?.type)?.color }"
/>
<GeneralViewIcon :meta="selectedView"></GeneralViewIcon>
<span class="!text-sm font-weight-normal">
<GeneralTruncateText>{{ selectedView?.title }}</GeneralTruncateText>

2
packages/nocodb-sdk/src/lib/Api.ts

@ -135,6 +135,7 @@ export interface ViewType {
fk_model_id?: string;
slug?: string;
uuid?: string;
meta?: any;
show_system_fields?: boolean;
lock_type?: 'collaborative' | 'locked' | 'personal';
type?: number;
@ -2316,6 +2317,7 @@ export class Api<
viewId: string,
data: {
order?: number;
meta?: any;
title?: string;
show_system_fields?: boolean;
lock_type?: 'collaborative' | 'locked' | 'personal';

15
packages/nocodb/src/lib/models/Model.ts

@ -1,4 +1,5 @@
import Noco from '../Noco';
import { parseMetaProp } from '../utils/modelUtils'
import Column from './Column';
import NocoCache from '../cache/NocoCache';
import { XKnex } from '../db/sql-data-mapper';
@ -22,21 +23,7 @@ import { NcError } from '../meta/helpers/catchError';
import Audit from './Audit';
import { sanitize } from '../db/sql-data-mapper/lib/sql/helpers/sanitize';
function parseMetaProp(modelOrModelList: Model[] | Model) {
if (!modelOrModelList) return;
// parse meta property
for (const model of Array.isArray(modelOrModelList)
? modelOrModelList
: [modelOrModelList]) {
try {
model.meta =
typeof model.meta === 'string' ? JSON.parse(model.meta) : model.meta;
} catch {
model.meta = {};
}
}
}
export default class Model implements TableType {
copy_enabled: boolean;

12
packages/nocodb/src/lib/models/View.ts

@ -5,6 +5,7 @@ import {
CacheScope,
MetaTable,
} from '../utils/globals';
import { parseMetaProp, stringifyMetaProp } from '../utils/modelUtils'
import Model from './Model';
import FormView from './FormView';
import GridView from './GridView';
@ -118,6 +119,7 @@ export default class View implements ViewType {
));
if (!view) {
view = await ncMeta.metaGet2(null, null, MetaTable.VIEWS, viewId);
parseMetaProp(view);
await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view);
}
@ -156,6 +158,7 @@ export default class View implements ViewType {
],
}
);
parseMetaProp(view);
// todo: cache - titleOrId can be viewId so we need a different scope here
await NocoCache.set(
`${CacheScope.VIEW}:${fk_model_id}:${titleOrId}`,
@ -188,6 +191,7 @@ export default class View implements ViewType {
},
null
);
parseMetaProp(view);
await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:default`, view);
}
return view && new View(view);
@ -204,6 +208,7 @@ export default class View implements ViewType {
order: 'asc',
},
});
parseMetaProp(viewsList);
await NocoCache.setList(CacheScope.VIEW, [modelId], viewsList);
}
viewsList.sort(
@ -254,8 +259,11 @@ export default class View implements ViewType {
base_id: view.base_id,
created_at: view.created_at,
updated_at: view.updated_at,
meta: view.meta ?? {},
};
stringifyMetaProp(insertObj);
// get project and base id if missing
if (!(view.project_id && view.base_id)) {
const model = await Model.getByIdOrName({ id: view.fk_model_id }, ncMeta);
@ -838,7 +846,9 @@ export default class View implements ViewType {
'meta',
'uuid',
]);
updateObj.meta = JSON.stringify(updateObj.meta);
// if meta data defined then stringify it
stringifyMetaProp(updateObj);
// get existing cache
const key = `${CacheScope.VIEW}:${viewId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);

33
packages/nocodb/src/lib/utils/modelUtils.ts

@ -0,0 +1,33 @@
export function parseMetaProp(modelOrModelList: { meta: any } | { meta: any }[]) {
if (!modelOrModelList) return
// parse meta property
for (const model of Array.isArray(modelOrModelList)
? modelOrModelList
: [modelOrModelList]) {
try {
model.meta =
typeof model.meta === 'string' ? JSON.parse(model.meta) : model.meta
} catch {
model.meta = {}
}
}
}
export function stringifyMetaProp(
modelOrModelList: { meta?: any } | { meta?: any }[],
) {
if (!modelOrModelList) return
// parse meta property
for (const model of Array.isArray(modelOrModelList)
? modelOrModelList
: [modelOrModelList]) {
try {
model.meta =
typeof model.meta !== 'string' ? model.meta : JSON.parse(model.meta)
} catch (e) {
model.meta = '{}'
}
}
}

4
scripts/sdk/swagger.json

@ -2194,6 +2194,8 @@
"order": {
"type": "number"
},
"meta": {
},
"title": {
"type": "string"
},
@ -7586,6 +7588,8 @@
"uuid": {
"type": "string"
},
"meta": {
},
"show_system_fields": {
"type": "boolean"
},

Loading…
Cancel
Save