Fix OpenTable performances

This commit is contained in:
Julien Nahum 2024-01-28 19:53:49 +01:00
parent 6aec718ef6
commit 28e55574e6
5 changed files with 121 additions and 314 deletions

View File

@ -1,13 +1,13 @@
NUXT_LOG_LEVEL= NUXT_LOG_LEVEL=
NUXT_PUBLIC_APP_URL=http://localhost/ NUXT_PUBLIC_APP_URL=http://localhost/
NUXT_PUBLIC_API_BASE=http://localhost/api NUXT_PUBLIC_API_BASE=http://localhost/api
NUXT_PUBLIC_AI_ENABLED= NUXT_PUBLIC_AI_ENABLED=false
NUXT_PUBLIC_AMPLITUDE_CODE= NUXT_PUBLIC_AMPLITUDE_CODE=
NUXT_PUBLIC_CRISP_WEBSITE_ID= NUXT_PUBLIC_CRISP_WEBSITE_ID=
NUXT_PUBLIC_CUSTOM_DOMAINS_ENABLED= NUXT_PUBLIC_CUSTOM_DOMAINS_ENABLED=false
NUXT_PUBLIC_ENV=local NUXT_PUBLIC_ENV=local
NUXT_PUBLIC_GOOGLE_ANALYTICS_CODE= NUXT_PUBLIC_GOOGLE_ANALYTICS_CODE=
NUXT_PUBLIC_H_CAPTCHA_SITE_KEY= NUXT_PUBLIC_H_CAPTCHA_SITE_KEY=
NUXT_PUBLIC_PAID_PLANS_ENABLED= NUXT_PUBLIC_PAID_PLANS_ENABLED=false
NUXT_PUBLIC_S3_ENABLED= NUXT_PUBLIC_S3_ENABLED=false
NUXT_API_SECRET= NUXT_API_SECRET=

View File

@ -7,72 +7,70 @@
:class="{'absolute': data.length !== 0}" :class="{'absolute': data.length !== 0}"
style="will-change: transform; transform: translate3d(0px, 0px, 0px)" style="will-change: transform; transform: translate3d(0px, 0px, 0px)"
> >
<tr class="n-table-row overflow-x-hidden"> <tr class="n-table-row overflow-x-hidden">
<resizable-th v-for="col, index in columns" :id="'table-head-cell-' + col.id" :key="col.id" <resizable-th v-for="col, index in columns" :id="'table-head-cell-' + col.id" :key="col.id"
scope="col" :allow-resize="allowResize" :width="(col.cell_width ? col.cell_width + 'px':'auto')" scope="col" :allow-resize="allowResize" :width="(col.cell_width ? col.cell_width + 'px':'auto')"
class="n-table-cell p-0 relative" class="n-table-cell p-0 relative"
@resize-width="resizeCol(col, $event)" @resize-width="resizeCol(col, $event)"
>
<p class="bg-gray-50 border-r dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs"
> >
<p {{ col.name }}
:class="{'border-r': index < columns.length - 1 || hasActions}" </p>
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs" </resizable-th>
> <th class="n-table-cell p-0 relative" style="width: 100px">
{{ col.name }} <p
</p> class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs"
</resizable-th> >
<th v-if="hasActions" class="n-table-cell p-0 relative" style="width: 100px"> Actions
<p </p>
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs" </th>
> </tr>
Actions
</p>
</th>
</tr>
</thead> </thead>
<tbody v-if="data.length > 0" class="n-table-body bg-white dark:bg-notion-dark-light"> <tbody v-if="data.length > 0" class="n-table-body bg-white dark:bg-notion-dark-light">
<tr v-if="$slots.hasOwnProperty('actions')" <tr v-if="$slots.hasOwnProperty('actions')"
:id="'table-actions-'+tableHash" :id="'table-actions-'+tableHash"
ref="actions-row" ref="actions-row"
class="action-row absolute w-full" class="action-row absolute w-full"
style="will-change: transform; transform: translate3d(0px, 32px, 0px)" style="will-change: transform; transform: translate3d(0px, 32px, 0px)"
> >
<td :colspan="columns.length" class="p-1"> <td :colspan="columns.length" class="p-1">
<slot name="actions" /> <slot name="actions"/>
</td> </td>
</tr> </tr>
<tr v-for="row, index in data" :key="index" class="n-table-row" :class="{'first':index===0}"> <tr v-for="row, index in data" :key="row.id" class="n-table-row" :class="{'first':index===0}">
<td v-for="col, colIndex in columns" <td v-for="col, colIndex in columns"
:key="col.id" :key="col.id"
:style="{width: col.cell_width + 'px'}" :style="{width: col.cell_width + 'px'}"
class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 overflow-hidden" class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 overflow-hidden"
:class="[{'border-b': index !== data.length - 1, 'border-r': colIndex !== columns.length - 1 || hasActions}, :class="[{'border-b': index !== data.length - 1, 'border-r': colIndex !== columns.length - 1 || hasActions},
colClasses(col)]" colClasses(col)]"
> >
<component :is="fieldComponents[col.type]" class="border-gray-100 dark:border-gray-900" <component :is="fieldComponents[col.type]" class="border-gray-100 dark:border-gray-900"
:property="col" :value="row[col.id]" :property="col" :value="row[col.id]"
/> />
</td> </td>
<td v-if="hasActions" class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 border-b" <td v-if="hasActions" class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 border-b"
style="width: 100px" style="width: 100px"
> >
<record-operations :form="form" :structure="columns" :rowid="row.id" @deleted="$emit('deleted')" /> <record-operations :form="form" :structure="columns" :rowid="row.id" @deleted="$emit('deleted')"/>
</td> </td>
</tr> </tr>
<tr v-if="loading" class="n-table-row border-t bg-gray-50 dark:bg-gray-900"> <tr v-if="loading" class="n-table-row border-t bg-gray-50 dark:bg-gray-900">
<td :colspan="columns.length" class="p-8 w-full"> <td :colspan="columns.length" class="p-8 w-full">
<Loader class="w-4 h-4 mx-auto" /> <Loader class="w-4 h-4 mx-auto"/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tbody v-else key="body-content" class="n-table-body"> <tbody v-else key="body-content" class="n-table-body">
<tr class="n-table-row loader w-full"> <tr class="n-table-row loader w-full">
<td :colspan="columns.length" class="n-table-cell w-full p-8"> <td :colspan="columns.length" class="n-table-cell w-full p-8">
<Loader v-if="loading" class="w-4 h-4 mx-auto" /> <Loader v-if="loading" class="w-4 h-4 mx-auto"/>
<p v-else class="text-gray-500 text-center"> <p v-else class="text-gray-500 text-center">
No data found. No data found.
</p> </p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</template> </template>
@ -90,7 +88,7 @@ import clonedeep from 'clone-deep'
import {hash} from "~/lib/utils.js"; import {hash} from "~/lib/utils.js";
export default { export default {
components: { ResizableTh, RecordOperations }, components: {ResizableTh, RecordOperations},
props: { props: {
columns: { columns: {
type: Array, type: Array,
@ -111,77 +109,64 @@ export default {
} }
}, },
setup () { setup() {
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
return { return {
workingFormStore workingFormStore,
form: storeToRefs(workingFormStore).content,
} }
}, },
data () { data() {
return { return {
tableHash: null, tableHash: null,
skip: false skip: false,
} hasActions: true,
}, internalColumns: [],
fieldComponents: {
computed: { text: shallowRef(OpenText),
form: { number: shallowRef(OpenText),
get () { select: shallowRef(OpenSelect),
return this.workingFormStore.content multi_select: shallowRef(OpenSelect),
}, date: shallowRef(OpenDate),
/* We add a setter */ files: shallowRef(OpenFile),
set (value) { checkbox: shallowRef(OpenCheckbox),
this.workingFormStore.set(value) url: shallowRef(OpenUrl),
} email: shallowRef(OpenText),
}, phone_number: shallowRef(OpenText),
hasActions () { signature: shallowRef(OpenFile)
// In future if want to hide based on condition
return true
},
fieldComponents () {
return {
text: OpenText,
number: OpenText,
select: OpenSelect,
multi_select: OpenSelect,
date: OpenDate,
files: OpenFile,
checkbox: OpenCheckbox,
url: OpenUrl,
email: OpenText,
phone_number: OpenText,
signature: OpenFile
} }
} }
}, },
watch: { watch: {
'columns': { 'columns': {
handler () { handler() {
this.internalColumns = clonedeep(this.columns)
this.onStructureChange() this.onStructureChange()
}, },
deep: true deep: true
}, },
data () { data() {
this.$nextTick(() => { this.$nextTick(() => {
this.handleScroll() this.handleScroll()
}) })
} }
}, },
mounted () { mounted() {
this.internalColumns = clonedeep(this.columns)
const parent = document.getElementById('table-page') const parent = document.getElementById('table-page')
this.tableHash = hash(JSON.stringify(this.form.properties)) this.tableHash = hash(JSON.stringify(this.form.properties))
if (parent) { if (parent) {
parent.addEventListener('scroll', this.handleScroll, { passive: true }) parent.addEventListener('scroll', this.handleScroll, {passive: true})
} }
window.addEventListener('resize', this.handleScroll) window.addEventListener('resize', this.handleScroll)
this.onStructureChange() this.onStructureChange()
this.handleScroll() this.handleScroll()
}, },
beforeUnmount () { beforeUnmount() {
const parent = document.getElementById('table-page') const parent = document.getElementById('table-page')
if (parent) { if (parent) {
parent.removeEventListener('scroll', this.handleScroll) parent.removeEventListener('scroll', this.handleScroll)
@ -190,7 +175,7 @@ export default {
}, },
methods: { methods: {
colClasses (col) { colClasses(col) {
let colAlign, colColor, colFontWeight, colWrap let colAlign, colColor, colFontWeight, colWrap
// Column align // Column align
@ -215,12 +200,12 @@ export default {
return [colAlign, colColor, colWrap, colFontWeight] return [colAlign, colColor, colWrap, colFontWeight]
}, },
onStructureChange () { onStructureChange() {
if (this.columns) { if (this.internalColumns) {
this.$nextTick(() => { this.$nextTick(() => {
this.columns.forEach(col => { this.internalColumns.forEach(col => {
if (!col.hasOwnProperty('cell_width')) { if (!col.hasOwnProperty('cell_width')) {
if (this.allowResize && this.columns.length && document.getElementById('table-head-cell-' + col.id)) { if (this.allowResize && this.internalColumns.length && document.getElementById('table-head-cell-' + col.id)) {
// Within editor // Within editor
this.resizeCol(col, document.getElementById('table-head-cell-' + col.id).offsetWidth) this.resizeCol(col, document.getElementById('table-head-cell-' + col.id).offsetWidth)
} }
@ -229,17 +214,16 @@ export default {
}) })
} }
}, },
resizeCol (col, width) { resizeCol(col, width) {
if (!this.form) return if (!this.form) return
const columns = clonedeep(this.columns) const index = this.internalColumns.findIndex(c => c.id === col.id)
const index = this.columns.findIndex(c => c.id === col.id) this.internalColumns[index].cell_width = width
columns[index].cell_width = width this.setColumns(this.internalColumns)
this.setColumns(columns)
this.$nextTick(() => { this.$nextTick(() => {
this.$emit('resize') this.$emit('resize')
}) })
}, },
handleScroll () { handleScroll() {
const parent = document.getElementById('table-page') const parent = document.getElementById('table-page')
const posTop = parent.getBoundingClientRect().top const posTop = parent.getBoundingClientRect().top
const tablePosition = Math.max(0, posTop - this.$refs.table.getBoundingClientRect().top) const tablePosition = Math.max(0, posTop - this.$refs.table.getBoundingClientRect().top)
@ -269,7 +253,7 @@ export default {
} }
}, },
setColumns(val) { setColumns(val) {
this.$emit('update-columns',val) this.$emit('update-columns', val)
} }
} }
} }
@ -299,87 +283,4 @@ export default {
min-width: 80px; min-width: 80px;
} }
} }
.notion-table {
td {
&.text-gray {
color: #787774;
}
&.text-brown {
color: #9f6b53;
}
&.text-orange {
color: #d9730d;
}
&.text-yellow {
color: #cb912f;
}
&.text-green {
color: #448361;
}
&.text-blue {
color: #337ea9;
}
&.text-purple {
color: #9065b0;
}
&.text-pink {
color: #c14c8a;
}
&.text-red {
color: #d44c47;
}
}
}
.dark {
.notion-table {
td {
&.text-gray {
color: #9b9b9b;
}
&.text-brown {
color: #ba856f;
}
&.text-orange {
color: #c77d48;
}
&.text-yellow {
color: #ca9849;
}
&.text-green {
color: #529e72;
}
&.text-blue {
color: #5e87c9;
}
&.text-purple {
color: #9d68d3;
}
&.text-pink {
color: #d15796;
}
&.text-red {
color: #df5452;
}
}
}
}
</style> </style>

View File

@ -13,10 +13,7 @@ import OpenTag from './OpenTag.vue'
export default { export default {
components: { OpenTag }, components: { OpenTag },
props: { props: {
value: { value: {}
type: Object
}
}, },
data () { data () {
@ -31,8 +28,5 @@ export default {
return false return false
} }
}, },
mounted () {
},
methods: {}
} }
</script> </script>

View File

@ -1,41 +1,7 @@
<template> <template>
<span v-if="!valueIsObject"> <span>
{{ value }} {{ value }}
</span> </span>
<span v-else>
<span
v-for="(item, i) in value.responseData"
:key="i"
:class="{
'font-semibold': item.annotations.bold && !item.annotations.code,
italic: item.annotations.italic,
'line-through': item.annotations.strikethrough,
underline: item.annotations.underline,
'bg-pink-100 py-1 px-2 rounded-lg text-pink-500': item.annotations.code,
'font-serif': item.type == 'equation',
}"
:style="{
color:
item.annotations.color != 'default'
? getColor(item.annotations.color)
: null,
'background-color':
item.annotations.color != 'default' &&
item.annotations.color.split('_')[1]
? getBgColor(item.annotations.color.split('_')[0])
: 'none',
}"
>
<a
v-if="item.href"
:href="item.href"
rel="noopener noreferrer"
target="_blank"
class="text-blue-600 underline"
>{{ item.plain_text }}</a>
<span v-else-if="!item.href">{{ item.plain_text }}</span>
</span>
</span>
</template> </template>
<script> <script>
@ -45,55 +11,6 @@ export default {
value: { value: {
required: true required: true
} }
}, },
data () {
return {}
},
computed: {
valueIsObject () {
if (
typeof this.value === 'object' &&
!Array.isArray(this.value) &&
this.value !== null
) {
return true
}
return false
}
},
mounted () {
},
methods: {
getColor (color) {
return {
red: '#e03e3e',
gray: '#9b9a97',
brown: '#64473a',
orange: '#d9730d',
yellow: '#dfab01',
teal: '#0f7b6c',
blue: '#0b6e99',
purple: '#6940a5',
pink: '#ad1a72'
}[color]
},
getBgColor (color) {
return {
red: '#fbe4e4',
gray: '#ebeced',
brown: '#e9e5e3',
orange: '#faebdd',
yellow: '#fbf3db',
teal: '#ddedea',
blue: '#ddebf1',
purple: '#eae4f2',
pink: '#f4dfeb'
}[color]
}
}
} }
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<th ref="th" :style="{width: width}"> <th ref="th" :style="{width: width}">
<slot /> <slot/>
<div v-if="allowResize" class="absolute right-0 top-0 w-0 z-10"> <div v-if="allowResize" class="absolute right-0 top-0 w-0 z-10">
<div class="resize-handler bg-transparent cursor-move hover:bg-blue-500 opacity-80 transition-colors" <div class="resize-handler bg-transparent cursor-move hover:bg-blue-500 opacity-80 transition-colors"
@mousedown="mouseDownHandler" @mousedown="mouseDownHandler"
@ -21,20 +21,18 @@ export default {
} }
}, },
data () { data() {
return { return {
x: 0, x: 0,
w: 0 w: 0,
lastEmit: Date.now(),
throttlePeriod: 50 // milliseconds
} }
}, },
computed: {
},
mounted () {
},
methods: { methods: {
mouseDownHandler (e) { mouseDownHandler(e) {
if (process.server) return
// Get the current mouse position // Get the current mouse position
this.x = e.clientX this.x = e.clientX
@ -43,24 +41,21 @@ export default {
this.w = parseInt(styles.width, 10) this.w = parseInt(styles.width, 10)
// Attach the listeners to `document` // Attach the listeners to `document`
if (process.client) { document.addEventListener('mousemove', this.mouseMoveHandler)
document.addEventListener('mousemove', this.mouseMoveHandler) document.addEventListener('mouseup', this.mouseUpHandler)
document.addEventListener('mouseup', this.mouseUpHandler) },
mouseMoveHandler(e) {
const now = Date.now()
if (now - this.lastEmit > this.throttlePeriod) {
const dx = e.clientX - this.x
this.$emit('resize-width', this.w + dx)
this.lastEmit = now
} }
}, },
mouseMoveHandler (e) { mouseUpHandler() {
// How far the mouse has been moved
const dx = e.clientX - this.x
// Adjust the dimension of element
this.$emit('resize-width', this.w + dx)
},
mouseUpHandler () {
// Remove the handlers of `mousemove` and `mouseup` // Remove the handlers of `mousemove` and `mouseup`
if (process.client) { document.removeEventListener('mousemove', this.mouseMoveHandler)
document.removeEventListener('mousemove', this.mouseMoveHandler) document.removeEventListener('mouseup', this.mouseUpHandler)
document.removeEventListener('mouseup', this.mouseUpHandler)
}
} }
} }
} }