Browse Source

Merge pull request #3481 from nocodb/fix/3459-theme-palette

fix: theme palette
pull/3495/head
Raju Udava 2 years ago committed by GitHub
parent
commit
ca3e53d4f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      packages/nc-gui-v2/components/general/ChromeWrapper.vue
  2. 22
      packages/nc-gui-v2/components/general/ColorPicker.vue
  3. 16
      packages/nc-gui-v2/composables/useProject.ts
  4. 71
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  5. 80
      packages/nc-gui-v2/pages/index/index/index.vue

30
packages/nc-gui-v2/components/general/ChromeWrapper.vue

@ -0,0 +1,30 @@
<script lang="ts" setup>
import { Chrome } from '@ckpack/vue-color'
import { computed } from '#imports'
interface Props {
modelValue?: any
mode?: 'hex' | 'hex8' | 'hsl' | 'hsv' | 'rgba'
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => '#00FFFFFF',
mode: () => 'hex8',
})
const emit = defineEmits(['update:modelValue', 'input'])
const picked = computed({
get: () => props.modelValue || '#00FFFFFF',
set: (val) => {
emit('update:modelValue', val[props.mode] || null)
emit('input', val[props.mode] || null)
},
})
</script>
<template>
<Chrome v-model="picked" />
</template>
<style scoped></style>

22
packages/nc-gui-v2/components/general/ColorPicker.vue

@ -1,5 +1,4 @@
<script lang="ts" setup>
import { Chrome } from '@ckpack/vue-color'
import { computed, enumColor, ref, watch } from '#imports'
interface Props {
@ -18,27 +17,28 @@ const props = withDefaults(defineProps<Props>(), {
pickButton: false,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:modelValue', 'input'])
const vModel = computed({
get: () => props.modelValue,
set: (val) => {
emit('update:modelValue', val.hex8 ? val.hex8 : val || null)
emit('update:modelValue', val || null)
emit('input', val || null)
},
})
const picked = ref<string | Record<string, any>>(props.modelValue || enumColor.light[0])
const picked = ref<string>(props.modelValue || enumColor.light[0])
const selectColor = (color: string | Record<string, any>) => {
picked.value = typeof color === 'string' ? color : color.hex8 ? color.hex8 : color
vModel.value = typeof color === 'string' ? color : color.hex8 ? color.hex8 : color
const selectColor = (color: string) => {
picked.value = color
if (props.pickButton) vModel.value = color
}
const compare = (colorA: string, colorB: string) => colorA.toLowerCase() === colorB.toLowerCase()
watch(picked, (n, _o) => {
if (!props.pickButton) {
vModel.value = typeof n === 'string' ? n : n.hex8 ? n.hex8 : n
vModel.value = n
}
})
</script>
@ -50,11 +50,11 @@ watch(picked, (n, _o) => {
v-for="(color, i) of colors.slice((colId - 1) * rowSize, colId * rowSize)"
:key="`color-${colId}-${i}`"
class="color-selector"
:class="compare(typeof picked === 'string' ? picked : picked.hex8, color) ? 'selected' : ''"
:class="compare(picked, color) ? 'selected' : ''"
:style="{ 'background-color': `${color}` }"
@click="selectColor(color)"
>
{{ compare(typeof picked === 'string' ? picked : picked.hex8, color) ? '&#10003;' : '' }}
{{ compare(picked, color) ? '&#10003;' : '' }}
</button>
</div>
<a-card v-if="props.advanced" class="w-full mt-2" :body-style="{ padding: '0px' }" :bordered="false">
@ -64,7 +64,7 @@ watch(picked, (n, _o) => {
Pick Color
</a-button>
<div class="flex justify-center py-4">
<Chrome v-model="picked" class="!w-full !shadow-none" />
<GeneralChromeWrapper v-model="picked" class="!w-full !shadow-none" />
</div>
</a-collapse-panel>
</a-collapse>

16
packages/nc-gui-v2/composables/useProject.ts

@ -10,7 +10,7 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
const { $api } = useNuxtApp()
const route = useRoute()
const { includeM2M } = useGlobal()
const { setTheme } = useTheme()
const { setTheme, theme } = useTheme()
const projectId = computed(() => (_projectId ? unref(_projectId) : (route.params.projectId as string)))
const project = ref<ProjectType>({})
@ -99,15 +99,21 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
}
}
async function saveTheme(theme: Partial<ThemeConfig>) {
async function saveTheme(_theme: Partial<ThemeConfig>) {
const fullTheme = {
primaryColor: theme.value.primaryColor,
accentColor: theme.value.accentColor,
..._theme,
}
await updateProject({
color: theme.primaryColor,
color: fullTheme.primaryColor,
meta: {
...projectMeta.value,
theme,
theme: fullTheme,
},
})
setTheme(theme)
setTheme(fullTheme)
}
watch(

71
packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { Chrome } from '@ckpack/vue-color'
import tinycolor from 'tinycolor2'
import { useI18n } from 'vue-i18n'
import {
@ -18,9 +17,7 @@ import {
useProject,
useRoute,
useTabs,
useTheme,
useUIPermission,
watch,
} from '#imports'
import { TabType } from '~/composables'
@ -58,8 +55,6 @@ const sidebar = ref()
const email = computed(() => user.value?.email ?? '---')
const { theme } = useTheme()
const logout = () => {
signOut()
navigateTo('/signin')
@ -84,37 +79,41 @@ await loadProject()
await loadTables()
const themePrimaryColor = ref<any>(theme.value.primaryColor)
const themeAccentColor = ref<any>(theme.value.accentColor)
const { t } = useI18n()
// Chrome provides object so if custom picker used we only edit primary otherwise use complement as accent
watch(themePrimaryColor, async (nextColor) => {
const hexColor = nextColor.hex8 ? nextColor.hex8 : nextColor
const tcolor = tinycolor(hexColor)
if (tcolor) {
const complement = tcolor.complement()
await saveTheme({
primaryColor: hexColor,
accentColor: themeAccentColor.value,
})
themeAccentColor.value = nextColor.hex8 ? theme.value.accentColor : complement.toHex8String()
const handleThemeColor = async (mode: 'swatch' | 'primary' | 'accent', color: string) => {
switch (mode) {
case 'swatch': {
const tcolor = tinycolor(color)
if (tcolor.isValid()) {
const complement = tcolor.complement()
await saveTheme({
primaryColor: color,
accentColor: complement.toHex8String(),
})
}
break
}
case 'primary': {
const tcolor = tinycolor(color)
if (tcolor.isValid()) {
await saveTheme({
primaryColor: color,
})
}
break
}
case 'accent': {
const tcolor = tinycolor(color)
if (tcolor.isValid()) {
await saveTheme({
accentColor: color,
})
}
break
}
}
})
watch(themeAccentColor, (nextColor) => {
const hexColor = nextColor.hex8 ? nextColor.hex8 : nextColor
// skip if the color is same as saved
if (hexColor === theme.value.accentColor) return
saveTheme({
primaryColor: theme.value.primaryColor,
accentColor: hexColor,
})
})
}
if (!route.params.type && isUIAllowed('teamAndAuth')) {
addTab({ type: TabType.AUTH, title: t('title.teamAndAuth') })
@ -292,10 +291,10 @@ const copyAuthToken = async () => {
<template #expandIcon></template>
<GeneralColorPicker
v-model="themePrimaryColor"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="handleThemeColor('swatch', $event)"
/>
<!-- Custom Theme -->
@ -322,7 +321,7 @@ const copyAuthToken = async () => {
</div>
</template>
<template #expandIcon></template>
<Chrome v-model="themePrimaryColor" />
<GeneralChromeWrapper @input="handleThemeColor('primary', $event)" />
</a-sub-menu>
<!-- Accent Color -->
@ -334,7 +333,7 @@ const copyAuthToken = async () => {
</div>
</template>
<template #expandIcon></template>
<Chrome v-model="themeAccentColor" />
<GeneralChromeWrapper @input="handleThemeColor('accent', $event)" />
</a-sub-menu>
</a-sub-menu>
</a-sub-menu>

80
packages/nc-gui-v2/pages/index/index/index.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { Empty, Modal, message } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { Chrome } from '@ckpack/vue-color'
import tinycolor from 'tinycolor2'
import {
computed,
@ -68,50 +67,41 @@ const deleteProject = (project: ProjectType) => {
await loadProjects()
const themePrimaryColors = $ref(
(() => {
const colors: Record<string, any> = {}
for (const project of projects?.value || []) {
if (project?.id) {
try {
const projectMeta = typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta
colors[project.id] = tinycolor(projectMeta?.theme?.primaryColor).isValid()
? projectMeta?.theme?.primaryColor
: themeV2Colors['royal-blue'].DEFAULT
} catch (e) {
colors[project.id] = themeV2Colors['royal-blue'].DEFAULT
}
}
}
return colors
})(),
)
const oldPrimaryColors = ref({ ...themePrimaryColors })
watch(themePrimaryColors, async (nextColors) => {
for (const [projectId, nextColor] of Object.entries(nextColors)) {
if (oldPrimaryColors.value[projectId] === nextColor) continue
const hexColor = nextColor.hex8 ? nextColor.hex8 : nextColor
const tcolor = tinycolor(hexColor)
if (tcolor) {
const complement = tcolor.complement()
const project: ProjectType = await $api.project.read(projectId)
const meta = project?.meta && typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta || {}
await $api.project.update(projectId, {
color: hexColor,
meta: JSON.stringify({
...meta,
theme: {
primaryColor: hexColor,
accentColor: complement.toHex8String(),
},
}),
const handleProjectColor = async (projectId: string, color: string) => {
const tcolor = tinycolor(color)
if (tcolor.isValid()) {
const complement = tcolor.complement()
const project: ProjectType = await $api.project.read(projectId)
const meta = project?.meta && typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta || {}
await $api.project.update(projectId, {
color,
meta: JSON.stringify({
...meta,
theme: {
primaryColor: color,
accentColor: complement.toHex8String(),
},
}),
})
// Update local project
const localProject = projects.value?.find((p) => p.id === projectId)
if (localProject) {
localProject.color = color
localProject.meta = JSON.stringify({
...meta,
theme: {
primaryColor: color,
accentColor: complement.toHex8String(),
},
})
}
}
oldPrimaryColors.value = { ...themePrimaryColors }
})
}
const getProjectPrimary = (project: ProjectType) => {
const meta = project?.meta && typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta || {}
return meta?.theme?.primaryColor || themeV2Colors['royal-blue'].DEFAULT
}
</script>
<template>
@ -219,7 +209,7 @@ watch(themePrimaryColors, async (nextColors) => {
<div
class="color-selector"
:style="{
'background-color': themePrimaryColors[record.id].hex8 || themePrimaryColors[record.id],
'background-color': getProjectPrimary(record),
'width': '8px',
'height': '100%',
}"
@ -229,10 +219,10 @@ watch(themePrimaryColors, async (nextColors) => {
<template #expandIcon></template>
<GeneralColorPicker
v-model="themePrimaryColors[record.id]"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="handleProjectColor(record.id, $event)"
/>
<a-sub-menu key="pick-primary">
<template #title>
@ -242,7 +232,7 @@ watch(themePrimaryColors, async (nextColors) => {
</div>
</template>
<template #expandIcon></template>
<Chrome v-model="themePrimaryColors[record.id]" />
<GeneralChromeWrapper @input="handleProjectColor(record.id, $event)" />
</a-sub-menu>
</a-sub-menu>
</template>

Loading…
Cancel
Save