After Width: | Height: | Size: 257 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 603 KiB |
After Width: | Height: | Size: 279 KiB |
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 652 B |
After Width: | Height: | Size: 682 B |
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts"> |
||||
const props = defineProps<{ |
||||
visible?: boolean |
||||
saving?: boolean |
||||
}>() |
||||
|
||||
const emit = defineEmits(['submit', 'cancel', 'update:visible']) |
||||
|
||||
const visible = useVModel(props, 'visible', emit) |
||||
</script> |
||||
|
||||
<template> |
||||
<GeneralModal v-model:visible="visible" size="small"> |
||||
<div class="flex flex-col p-6" @click.stop> |
||||
<div class="flex flex-row pb-2 mb-4 font-medium text-lg border-b-1 border-gray-50 text-gray-800">Field Type Change</div> |
||||
|
||||
<div class="mb-3 text-gray-800"> |
||||
<div class="flex item-center gap-2 mb-4"> |
||||
<component :is="iconMap.warning" id="nc-selected-item-icon" class="text-yellow-500 w-10 h-10" /> |
||||
This action cannot be undone. Converting data types may result in data loss. Proceed with caution! |
||||
</div> |
||||
</div> |
||||
|
||||
<slot name="entity-preview"></slot> |
||||
|
||||
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> |
||||
<NcButton type="secondary" @click="visible = false"> |
||||
{{ $t('general.cancel') }} |
||||
</NcButton> |
||||
|
||||
<NcButton |
||||
key="submit" |
||||
autofocus |
||||
type="primary" |
||||
html-type="submit" |
||||
:loading="saving" |
||||
data-testid="nc-delete-modal-delete-btn" |
||||
@click="emit('submit')" |
||||
> |
||||
Update |
||||
<template #loading> Saving... </template> |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
</GeneralModal> |
||||
</template> |
@ -0,0 +1,197 @@
|
||||
<script lang="ts" setup> |
||||
import tinycolor from 'tinycolor2' |
||||
import windiColors from 'windicss/colors' |
||||
import { themeV3Colors } from '../../utils/colorsUtils' |
||||
|
||||
interface Props { |
||||
modelValue?: string | any |
||||
isOpen?: boolean |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
isOpen: false, |
||||
}) |
||||
|
||||
const emit = defineEmits(['input', 'closeModal']) |
||||
|
||||
const { isOpen } = toRefs(props) |
||||
|
||||
const vModel = computed({ |
||||
get: () => props.modelValue, |
||||
set: (val) => { |
||||
emit('input', val || null) |
||||
}, |
||||
}) |
||||
|
||||
const showActiveColorTab = ref<boolean>(false) |
||||
|
||||
const picked = ref<string>(props.modelValue || enumColor.light[0]) |
||||
|
||||
const defaultColors = computed<string[][]>(() => { |
||||
const colors = [ |
||||
'gray', |
||||
'red', |
||||
'green', |
||||
'yellow', |
||||
'orange', |
||||
'pink', |
||||
'maroon', |
||||
'purple', |
||||
'blue', |
||||
] as (keyof typeof themeV3Colors)[] & (keyof typeof windiColors)[] |
||||
|
||||
const allColors = [] |
||||
|
||||
for (const color of colors) { |
||||
if (themeV3Colors[color]) { |
||||
allColors.push(color === 'gray' ? Object.values(themeV3Colors[color]).slice(1) : Object.values(themeV3Colors[color])) |
||||
} else if (windiColors[color]) { |
||||
allColors.push(Object.values(windiColors[color])) |
||||
} |
||||
} |
||||
return allColors |
||||
}) |
||||
|
||||
const localIsDefaultColorTab = ref<'true' | 'false'>('true') |
||||
|
||||
const isDefaultColorTab = computed({ |
||||
get: () => { |
||||
if (showActiveColorTab.value && vModel.value) { |
||||
for (const colorGrp of defaultColors.value) { |
||||
if (colorGrp.includes(vModel.value)) { |
||||
return 'true' |
||||
} |
||||
} |
||||
return 'false' |
||||
} |
||||
|
||||
return localIsDefaultColorTab.value |
||||
}, |
||||
set: (val: 'true' | 'false') => { |
||||
localIsDefaultColorTab.value = val |
||||
|
||||
if (showActiveColorTab.value) { |
||||
showActiveColorTab.value = false |
||||
} |
||||
}, |
||||
}) |
||||
|
||||
const selectColor = (color: string, closeModal = false) => { |
||||
picked.value = color |
||||
|
||||
if (closeModal) { |
||||
emit('closeModal') |
||||
} |
||||
} |
||||
|
||||
const compare = (colorA: string, colorB: string) => { |
||||
if (!colorA || !colorB) return false |
||||
|
||||
return colorA.toLowerCase() === colorB.toLowerCase() || colorA.toLowerCase() === tinycolor(colorB).toHex8String().toLowerCase() |
||||
} |
||||
|
||||
watch(picked, (n, _o) => { |
||||
vModel.value = n |
||||
}) |
||||
|
||||
watch( |
||||
isOpen, |
||||
(newValue) => { |
||||
if (newValue) { |
||||
showActiveColorTab.value = true |
||||
} |
||||
}, |
||||
{ |
||||
immediate: true, |
||||
}, |
||||
) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="nc-advance-color-picker w-[336px] pt-2" click.stop> |
||||
<NcTabs v-model:activeKey="isDefaultColorTab" class="nc-advance-color-picker-tab w-full"> |
||||
<a-tab-pane key="true"> |
||||
<template #tab> |
||||
<div class="tab" data-testid="nc-default-colors-tab">Default colors</div> |
||||
</template> |
||||
<div class="h-full p-2"> |
||||
<div class="flex flex-col gap-1"> |
||||
<div v-for="(colorGroup, i) of defaultColors" :key="i" class="flex"> |
||||
<div v-for="(color, j) of colorGroup" :key="`color-${i}-${j}`" class="p-1 rounded-md flex h-8 hover:bg-gray-200"> |
||||
<button |
||||
class="color-selector" |
||||
:class="{ selected: compare(picked, color) }" |
||||
:style="{ |
||||
backgroundColor: `${color}`, |
||||
}" |
||||
@click="selectColor(color, true)" |
||||
></button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</a-tab-pane> |
||||
<a-tab-pane key="false"> |
||||
<template #tab> |
||||
<div class="tab" data-testid="nc-custom-colors-tab"> |
||||
<div>Custom colours</div> |
||||
</div> |
||||
</template> |
||||
<div class="h-full p-2"> |
||||
<LazyGeneralChromeWrapper v-model="picked" class="!w-full !shadow-none" /> |
||||
</div> |
||||
</a-tab-pane> |
||||
</NcTabs> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.color-picker { |
||||
@apply flex flex-col items-center justify-center bg-white p-2.5; |
||||
} |
||||
.color-picker-row { |
||||
@apply flex flex-row space-x-1; |
||||
} |
||||
.color-selector { |
||||
@apply h-6 w-6 rounded; |
||||
-webkit-text-stroke-width: 1px; |
||||
-webkit-text-stroke-color: white; |
||||
} |
||||
.color-selector:hover { |
||||
filter: brightness(90%); |
||||
-webkit-filter: brightness(90%); |
||||
} |
||||
.color-selector:focus, |
||||
.color-selector.selected, |
||||
.nc-more-colors-trigger:focus { |
||||
outline: none; |
||||
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe; |
||||
} |
||||
|
||||
:deep(.vc-chrome-toggle-icon) { |
||||
@apply !ml-3; |
||||
} |
||||
|
||||
:deep(.ant-tabs) { |
||||
@apply !overflow-visible; |
||||
.ant-tabs-nav { |
||||
@apply px-1; |
||||
.ant-tabs-nav-list { |
||||
@apply w-[99%] mx-auto gap-6; |
||||
|
||||
.ant-tabs-tab { |
||||
@apply flex-1 flex items-center justify-center pt-2 pb-2 text-xs font-semibold; |
||||
|
||||
& + .ant-tabs-tab { |
||||
@apply !ml-0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.ant-tabs-content-holder { |
||||
.ant-tabs-content { |
||||
@apply h-full; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -1,17 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item"> |
||||
<div class="text-xs"> |
||||
<strong>{{ item.data?.user?.name }}</strong> {{ item.data?.action }} <strong>{{ item.data?.table?.name }}</strong> |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,17 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item"> |
||||
<div class="text-xs"> |
||||
<strong>{{ item.data?.user?.name }}</strong> {{ item.data?.action }} <strong>{{ item.data?.table?.name }}</strong> |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,38 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
import { AppEvents } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const { navigateToProject } = useGlobal() |
||||
|
||||
const action = computed(() => { |
||||
switch (item.value.type) { |
||||
case AppEvents.PROJECT_CREATE: |
||||
return 'created' |
||||
case AppEvents.PROJECT_UPDATE: |
||||
return 'updated' |
||||
case AppEvents.PROJECT_DELETE: |
||||
return 'deleted' |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (item.value.type === AppEvents.PROJECT_DELETE) return |
||||
navigateToProject({ baseId: item.value.body.id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item" @click="onClick"> |
||||
<div class="text-xs gap-2"> |
||||
Base |
||||
<GeneralProjectIcon style="vertical-align: middle" :type="item.body.type" /> <strong>{{ item.body.title }}</strong> |
||||
{{ action }} successfully |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,38 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
import { AppEvents } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const { navigateToProject } = useGlobal() |
||||
|
||||
const action = computed(() => { |
||||
switch (item.value.type) { |
||||
case AppEvents.VIEW_CREATE: |
||||
return 'created' |
||||
case AppEvents.VIEW_UPDATE: |
||||
return 'updated' |
||||
case AppEvents.VIEW_DELETE: |
||||
return 'deleted' |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (item.value.type === AppEvents.VIEW_DELETE) return |
||||
navigateToProject({ baseId: item.value.body.id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item" @click="onClick"> |
||||
<div class="text-xs gap-2"> |
||||
Shared view |
||||
<strong>{{ item.body.title }}</strong> |
||||
{{ action }} successfully |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,17 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item"> |
||||
<div class="text-xs"> |
||||
<strong>{{ item.data?.user?.name }}</strong> {{ item.data?.action }} <strong>{{ item.data?.table?.name }}</strong> |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,38 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { TableEventType } from 'nocodb-sdk' |
||||
import { AppEvents } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: TableEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const { navigateToProject } = useGlobal() |
||||
|
||||
const action = computed(() => { |
||||
switch (item.value.type) { |
||||
case AppEvents.TABLE_CREATE: |
||||
return 'created' |
||||
case AppEvents.TABLE_UPDATE: |
||||
return 'updated' |
||||
case AppEvents.TABLE_DELETE: |
||||
return 'deleted' |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (item.value.type === AppEvents.TABLE_DELETE) return |
||||
navigateToProject({ baseId: item.value.body.id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item" @click="onClick"> |
||||
<div class="text-xs gap-2"> |
||||
Table |
||||
<strong>{{ item.body.title }}</strong> |
||||
{{ action }} successfully |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,38 +0,0 @@
|
||||
<script setup lang="ts"> |
||||
import type { ProjectEventType } from 'nocodb-sdk' |
||||
import { AppEvents } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: ProjectEventType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const { navigateToProject } = useGlobal() |
||||
|
||||
const action = computed(() => { |
||||
switch (item.value.type) { |
||||
case AppEvents.VIEW_CREATE: |
||||
return 'created' |
||||
case AppEvents.VIEW_UPDATE: |
||||
return 'updated' |
||||
case AppEvents.VIEW_DELETE: |
||||
return 'deleted' |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (item.value.type === AppEvents.VIEW_DELETE) return |
||||
navigateToProject({ baseId: item.value.body.id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item" @click="onClick"> |
||||
<div class="text-xs gap-2"> |
||||
View |
||||
<strong>{{ item.body.title }}</strong> |
||||
{{ action }} successfully |
||||
</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
@ -1,26 +1,15 @@
|
||||
<script setup lang="ts"> |
||||
import type { WelcomeEventType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
item: any |
||||
item: WelcomeEventType |
||||
}>() |
||||
|
||||
const router = useRouter() |
||||
const route = router.currentRoute |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const navigateToHome = () => { |
||||
if (route.value.path !== '/') { |
||||
navigateTo(`/`) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NotificationItemWrapper :item="item" @click="navigateToHome"> |
||||
<template #avatar> |
||||
<img src="~/assets/img/icons/64x64.png" class="w-6" /> |
||||
</template> |
||||
|
||||
<div class="text-xs">Welcome to <strong>NocoHUB!</strong> We’re excited to have you onboard.</div> |
||||
<NotificationItemWrapper :item="item"> |
||||
<div>Welcome to <span class="font-semibold">NocoDB!</span> We’re excited to have you onboard.</div> |
||||
</NotificationItemWrapper> |
||||
</template> |
||||
|
@ -1,94 +1,73 @@
|
||||
<script setup lang="ts"> |
||||
import { timeAgo } from 'nocodb-sdk' |
||||
import type { NotificationType } from 'nocodb-sdk' |
||||
import { timeAgo } from '~/utils/datetimeUtils' |
||||
|
||||
const props = defineProps<{ |
||||
item: { |
||||
created_at: any |
||||
} |
||||
item: NotificationType |
||||
}>() |
||||
|
||||
const item = toRef(props, 'item') |
||||
|
||||
const { isMobileMode } = useGlobal() |
||||
|
||||
const notificationStore = useNotification() |
||||
|
||||
const { markAsRead } = notificationStore |
||||
const { toggleRead, deleteNotification } = notificationStore |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex items-center gap-1 cursor-pointer nc-notification-item-wrapper" |
||||
:class="{ |
||||
active: !item.is_read, |
||||
}" |
||||
> |
||||
<div class="nc-notification-dot" :class="{ active: !item.is_read }"></div> |
||||
<div class="nc-avatar-wrapper"> |
||||
<div class="flex pl-6 pr-4 w-full overflow-x-hidden group py-4 hover:bg-gray-50 gap-3 relative cursor-pointer"> |
||||
<div class="w-9.625"> |
||||
<slot name="avatar"> |
||||
<div class="nc-notification-avatar"></div> |
||||
<img src="~assets/img/brand/nocodb-logo.svg" alt="NocoDB" class="w-8" /> |
||||
</slot> |
||||
</div> |
||||
<div class="flex-grow ml-3"> |
||||
<div class="flex items-center"> |
||||
<slot /> |
||||
</div> |
||||
<div |
||||
v-if="item" |
||||
class="text-xs text-gray-500 mt-1" |
||||
|
||||
<div class="text-[13px] min-h-12 w-full leading-5"> |
||||
<slot /> |
||||
</div> |
||||
<div v-if="item" class="text-xs whitespace-nowrap absolute right-4.1 bottom-5 text-gray-600"> |
||||
{{ timeAgo(item.created_at) }} |
||||
</div> |
||||
<div class="flex items-start"> |
||||
<NcTooltip v-if="!item.is_read"> |
||||
<template #title> |
||||
<span>Mark as read</span> |
||||
</template> |
||||
|
||||
<NcButton |
||||
:class="{ |
||||
'!opacity-100': isMobileMode, |
||||
}" |
||||
type="secondary" |
||||
class="!border-0 transition-all duration-100 opacity-0 !group-hover:opacity-100" |
||||
size="xsmall" |
||||
@click.stop="() => toggleRead(item)" |
||||
> |
||||
<GeneralIcon icon="check" class="text-gray-700" /> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
<NcDropdown |
||||
v-else |
||||
:class="{ |
||||
'text-primary': !item.is_read, |
||||
'!opacity-100': isMobileMode, |
||||
}" |
||||
class="transition-all duration-100 opacity-0 !group-hover:opacity-100" |
||||
> |
||||
{{ timeAgo(item.created_at) }} |
||||
</div> |
||||
</div> |
||||
<div @click.stop> |
||||
<a-dropdown> |
||||
<GeneralIcon v-if="!item.is_read" icon="threeDotVertical" class="nc-notification-menu-icon" /> |
||||
<NcButton size="xsmall" type="secondary" @click.stop> |
||||
<GeneralIcon icon="threeDotVertical" /> |
||||
</NcButton> |
||||
|
||||
<template #overlay> |
||||
<a-menu> |
||||
<a-menu-item @click="markAsRead(item)"> |
||||
<div class="p-2 text-xs">Mark as read</div> |
||||
</a-menu-item> |
||||
</a-menu> |
||||
<NcMenu> |
||||
<NcMenuItem @click.stop="() => toggleRead(item)"> Mark as unread </NcMenuItem> |
||||
<NcDivider /> |
||||
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="deleteNotification(item)"> Delete </NcMenuItem> |
||||
</NcMenu> |
||||
</template> |
||||
</a-dropdown> |
||||
</NcDropdown> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.nc-avatar-wrapper { |
||||
@apply min-w-6 h-6 flex items-center justify-center; |
||||
} |
||||
|
||||
.nc-notification-avatar { |
||||
@apply w-6 h-6 rounded-full text-white font-weight-bold uppercase bg-gray-100; |
||||
font-size: 0.7rem; |
||||
} |
||||
|
||||
.nc-notification-dot { |
||||
@apply min-w-2 min-h-2 mr-1 rounded-full; |
||||
|
||||
&.active { |
||||
@apply bg-accent bg-opacity-100; |
||||
} |
||||
} |
||||
|
||||
.nc-notification-item-wrapper { |
||||
.nc-notification-menu-icon { |
||||
@apply !text-12px text-gray-500 opacity-0 transition-opacity duration-200 cursor-pointer; |
||||
} |
||||
|
||||
&:hover { |
||||
.nc-notification-menu-icon { |
||||
@apply opacity-100; |
||||
} |
||||
} |
||||
|
||||
&.active { |
||||
@apply bg-primary bg-opacity-4; |
||||
} |
||||
|
||||
@apply py-3 px-3; |
||||
} |
||||
</style> |
||||
<style scoped lang="scss"></style> |
||||
|