opnform-host-nginx/client/components/open/tables/OpenTable.vue

406 lines
11 KiB
Vue
Raw Normal View History

2023-12-09 15:47:03 +01:00
<template>
<table
:id="'table-' + tableHash"
ref="table"
class="notion-table n-table whitespace-no-wrap bg-white dark:bg-notion-dark-light relative"
>
<thead
:id="'table-header-' + tableHash"
ref="header"
class="n-table-head top-0 z-10"
:class="{ absolute: data.length !== 0 }"
style="will-change: transform; transform: translate3d(0px, 0px, 0px)"
>
<tr class="n-table-row overflow-x-hidden">
<resizable-th
v-for="(col) in columns"
:id="'table-head-cell-' + col.id"
:key="col.id"
scope="col"
:allow-resize="allowResize"
:width="col.width ? col.width + 'px' : '150px'"
class="n-table-cell p-0 relative"
@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"
>
{{ col.name }}
</p>
</resizable-th>
<th
Readonly User (#637) * Readonly User * Refactor FormPolicy and TemplatePolicy to centralize write operation logic - Introduced a private method `canPerformWriteOperation` in both FormPolicy and TemplatePolicy to encapsulate the logic for determining if a user can perform write operations on the respective models. - Updated the `update`, `delete`, `restore`, and `forceDelete` methods in FormPolicy to use the new method for improved readability and maintainability. - Simplified the `update` and `delete` methods in TemplatePolicy to leverage the centralized write operation logic. This refactoring enhances code clarity and reduces duplication across policy classes. * Refactor user and workspace permissions handling - Updated FormController to authorize form creation based on workspace context. - Removed the `is_readonly` attribute from UserResource and integrated it into WorkspaceResource for better encapsulation. - Refactored User model to eliminate the `getIsReadonlyAttribute` method, shifting readonly logic to the Workspace model. - Adjusted FormPolicy and TemplatePolicy to utilize workspace readonly checks for user permissions. - Updated various frontend components to reference workspace readonly status instead of user readonly status, enhancing clarity and consistency in permission handling. These changes improve the management of user permissions in relation to workspaces, ensuring a more robust and maintainable authorization system. * Fix isReadonlyUser * fix pint --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-30 14:35:23 +01:00
v-if="hasActions"
class="n-table-cell p-0 relative"
style="width: 100px"
>
<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"
>
Actions
</p>
</th>
</tr>
2023-12-09 15:47:03 +01:00
</thead>
<tbody
v-if="formData.length > 0"
class="n-table-body bg-white dark:bg-notion-dark-light"
>
<tr
v-if="objectHas($slots, 'actions')"
:id="'table-actions-' + tableHash"
ref="actions-row"
class="action-row absolute w-full"
style="will-change: transform; transform: translate3d(0px, 32px, 0px)"
>
<td
:colspan="columns.length"
class="p-1"
>
<slot name="actions" />
</td>
</tr>
<tr
v-for="(row, index) in formData"
:key="row.id"
class="n-table-row"
:class="{ first: index === 0 }"
>
<td
v-for="(col, colIndex) in columns"
:key="col.id"
:style="{ width: col.width ? col.width + 'px' : '150px' }"
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,
'whitespace-normal break-words': wrapColumns[col.id] === true,
},
colClasses(col),
]"
>
<component
:is="fieldComponents[col.type]"
class="border-gray-100 dark:border-gray-900"
:property="col"
:value="row[col.id]"
/>
</td>
<td
v-if="hasActions"
class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 border-b"
style="width: 100px"
>
<div class="flex justify-center">
<record-operations
:form="form"
:structure="columns"
:submission="row"
@deleted="(submission) => $emit('deleted', submission)"
@updated="(submission) => $emit('updated', submission)"
/>
</div>
</td>
</tr>
<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"
>
<Loader class="w-4 h-4 mx-auto" />
</td>
</tr>
2023-12-09 15:47:03 +01:00
</tbody>
<tbody
v-else
key="body-content"
class="n-table-body"
>
<tr class="n-table-row loader w-full">
<td
:colspan="columns.length"
class="n-table-cell w-full p-8"
>
<Loader
v-if="loading"
class="w-4 h-4 mx-auto"
/>
<p
v-else
class="text-gray-500 text-center"
>
No data found.
</p>
</td>
</tr>
2023-12-09 15:47:03 +01:00
</tbody>
</table>
</template>
<script>
import OpenText from "./components/OpenText.vue"
import OpenUrl from "./components/OpenUrl.vue"
import OpenSelect from "./components/OpenSelect.vue"
import OpenMatrix from "./components/OpenMatrix.vue"
import OpenDate from "./components/OpenDate.vue"
import OpenFile from "./components/OpenFile.vue"
import OpenCheckbox from "./components/OpenCheckbox.vue"
import ResizableTh from "./components/ResizableTh.vue"
import RecordOperations from "../components/RecordOperations.vue"
import clonedeep from "clone-deep"
import { hash } from "~/lib/utils.js"
import { default as _has } from "lodash/has"
2023-12-09 15:47:03 +01:00
export default {
components: { ResizableTh, RecordOperations },
2023-12-09 15:47:03 +01:00
props: {
2024-01-18 12:02:09 +01:00
columns: {
type: Array,
default: () => [],
2024-01-18 12:02:09 +01:00
},
wrapColumns: {
type: Object,
default: () => {},
},
2023-12-09 15:47:03 +01:00
data: {
type: Array,
default: () => [],
2023-12-09 15:47:03 +01:00
},
loading: {
type: Boolean,
default: false,
2023-12-09 15:47:03 +01:00
},
allowResize: {
required: false,
default: true,
type: Boolean,
},
scrollParent: {
type: [Boolean, Object],
default: null
2024-02-22 16:56:35 +01:00
},
2023-12-09 15:47:03 +01:00
},
emits: ["updated", "deleted", "resize", "update-columns"],
2023-12-09 15:47:03 +01:00
2024-01-28 19:53:49 +01:00
setup() {
2023-12-09 15:47:03 +01:00
const workingFormStore = useWorkingFormStore()
return {
2024-01-28 19:53:49 +01:00
workingFormStore,
form: storeToRefs(workingFormStore).content,
Readonly User (#637) * Readonly User * Refactor FormPolicy and TemplatePolicy to centralize write operation logic - Introduced a private method `canPerformWriteOperation` in both FormPolicy and TemplatePolicy to encapsulate the logic for determining if a user can perform write operations on the respective models. - Updated the `update`, `delete`, `restore`, and `forceDelete` methods in FormPolicy to use the new method for improved readability and maintainability. - Simplified the `update` and `delete` methods in TemplatePolicy to leverage the centralized write operation logic. This refactoring enhances code clarity and reduces duplication across policy classes. * Refactor user and workspace permissions handling - Updated FormController to authorize form creation based on workspace context. - Removed the `is_readonly` attribute from UserResource and integrated it into WorkspaceResource for better encapsulation. - Refactored User model to eliminate the `getIsReadonlyAttribute` method, shifting readonly logic to the Workspace model. - Adjusted FormPolicy and TemplatePolicy to utilize workspace readonly checks for user permissions. - Updated various frontend components to reference workspace readonly status instead of user readonly status, enhancing clarity and consistency in permission handling. These changes improve the management of user permissions in relation to workspaces, ensuring a more robust and maintainable authorization system. * Fix isReadonlyUser * fix pint --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-30 14:35:23 +01:00
user: useAuthStore().user,
workspace: useWorkspacesStore().getCurrent,
2023-12-09 15:47:03 +01:00
}
},
2024-01-28 19:53:49 +01:00
data() {
2023-12-09 15:47:03 +01:00
return {
tableHash: null,
2024-01-28 19:53:49 +01:00
skip: false,
internalColumns: [],
2024-02-22 16:56:35 +01:00
rafId: null,
2024-01-28 19:53:49 +01:00
fieldComponents: {
text: shallowRef(OpenText),
rich_text: shallowRef(OpenText),
2024-01-28 19:53:49 +01:00
number: shallowRef(OpenText),
rating: shallowRef(OpenText),
scale: shallowRef(OpenText),
slider: shallowRef(OpenText),
2024-01-28 19:53:49 +01:00
select: shallowRef(OpenSelect),
matrix: shallowRef(OpenMatrix),
2024-01-28 19:53:49 +01:00
multi_select: shallowRef(OpenSelect),
date: shallowRef(OpenDate),
files: shallowRef(OpenFile),
checkbox: shallowRef(OpenCheckbox),
url: shallowRef(OpenUrl),
email: shallowRef(OpenText),
phone_number: shallowRef(OpenText),
signature: shallowRef(OpenFile),
},
2023-12-09 15:47:03 +01:00
}
},
2024-05-30 06:04:55 +02:00
computed: {
Readonly User (#637) * Readonly User * Refactor FormPolicy and TemplatePolicy to centralize write operation logic - Introduced a private method `canPerformWriteOperation` in both FormPolicy and TemplatePolicy to encapsulate the logic for determining if a user can perform write operations on the respective models. - Updated the `update`, `delete`, `restore`, and `forceDelete` methods in FormPolicy to use the new method for improved readability and maintainability. - Simplified the `update` and `delete` methods in TemplatePolicy to leverage the centralized write operation logic. This refactoring enhances code clarity and reduces duplication across policy classes. * Refactor user and workspace permissions handling - Updated FormController to authorize form creation based on workspace context. - Removed the `is_readonly` attribute from UserResource and integrated it into WorkspaceResource for better encapsulation. - Refactored User model to eliminate the `getIsReadonlyAttribute` method, shifting readonly logic to the Workspace model. - Adjusted FormPolicy and TemplatePolicy to utilize workspace readonly checks for user permissions. - Updated various frontend components to reference workspace readonly status instead of user readonly status, enhancing clarity and consistency in permission handling. These changes improve the management of user permissions in relation to workspaces, ensuring a more robust and maintainable authorization system. * Fix isReadonlyUser * fix pint --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-30 14:35:23 +01:00
hasActions() {
return !this.workspace.is_readonly
},
2024-05-30 06:04:55 +02:00
formData() {
return [...this.data].sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
}
},
2023-12-09 15:47:03 +01:00
watch: {
columns: {
2024-01-28 19:53:49 +01:00
handler() {
this.internalColumns = clonedeep(this.columns)
2023-12-09 15:47:03 +01:00
this.onStructureChange()
},
2024-05-30 06:04:55 +02:00
deep: true
2023-12-09 15:47:03 +01:00
},
2024-01-28 19:53:49 +01:00
data() {
2023-12-09 15:47:03 +01:00
this.$nextTick(() => {
this.handleScroll()
})
}
},
2024-01-28 19:53:49 +01:00
mounted() {
this.internalColumns = clonedeep(this.columns)
const parent = this.scrollParent ?? document.getElementById("table-page")
this.tableHash = hash(JSON.stringify(this.form.properties))
2023-12-09 15:47:03 +01:00
if (parent) {
parent.addEventListener("scroll", this.handleScroll, { passive: false })
2023-12-09 15:47:03 +01:00
}
window.addEventListener("resize", this.handleScroll)
2023-12-09 15:47:03 +01:00
this.onStructureChange()
this.handleScroll()
},
2024-01-28 19:53:49 +01:00
beforeUnmount() {
const parent = this.scrollParent ?? document.getElementById("table-page")
2023-12-09 15:47:03 +01:00
if (parent) {
parent.removeEventListener("scroll", this.handleScroll)
2023-12-09 15:47:03 +01:00
}
window.removeEventListener("resize", this.handleScroll)
2023-12-09 15:47:03 +01:00
},
methods: {
2024-01-28 19:53:49 +01:00
colClasses(col) {
let colColor, colFontWeight, colWrap
2023-12-09 15:47:03 +01:00
// Column align
const colAlign = `text-${col.alignment ? col.alignment : "left"}`
2023-12-09 15:47:03 +01:00
// Column color
colColor = null
if (!_has(col, "color") || col.color === "default") {
colColor = "text-gray-700 dark:text-gray-300"
2023-12-09 15:47:03 +01:00
}
colColor = `text-${col.color}`
// Column font weight
if (_has(col, "bold") && col.bold) {
colFontWeight = "font-semibold"
2023-12-09 15:47:03 +01:00
}
// Column wrapping
if (!_has(col, "wrap_text") || !col.wrap_text) {
colWrap = "truncate"
2023-12-09 15:47:03 +01:00
}
return [colAlign, colColor, colWrap, colFontWeight]
},
2024-01-28 19:53:49 +01:00
onStructureChange() {
if (this.internalColumns) {
2023-12-09 15:47:03 +01:00
this.$nextTick(() => {
this.internalColumns.forEach((col) => {
if (!_has(col, "width")) {
if (
this.allowResize &&
this.internalColumns.length &&
document.getElementById("table-head-cell-" + col.id)
) {
2023-12-09 15:47:03 +01:00
// Within editor
this.resizeCol(
col,
document.getElementById("table-head-cell-" + col.id)
.offsetWidth,
)
2023-12-09 15:47:03 +01:00
}
}
})
})
}
},
2024-01-28 19:53:49 +01:00
resizeCol(col, width) {
2023-12-09 15:47:03 +01:00
if (!this.form) return
const index = this.internalColumns.findIndex((c) => c.id === col.id)
this.internalColumns[index].width = width
2024-01-28 19:53:49 +01:00
this.setColumns(this.internalColumns)
2023-12-09 15:47:03 +01:00
this.$nextTick(() => {
this.$emit("resize")
2023-12-09 15:47:03 +01:00
})
},
2024-01-28 19:53:49 +01:00
handleScroll() {
2024-02-22 16:56:35 +01:00
if (this.rafId) {
cancelAnimationFrame(this.rafId)
2024-02-22 16:56:35 +01:00
}
this.rafId = requestAnimationFrame(() => {
const table = this.$refs.table
const tableHeader = document.getElementById(
"table-header-" + this.tableHash,
)
const tableActionsRow = document.getElementById(
"table-actions-" + this.tableHash,
)
2024-02-22 16:56:35 +01:00
if (!table || !tableHeader) return
2024-02-22 16:56:35 +01:00
const scrollTop =
window.pageYOffset || document.documentElement.scrollTop
const tableRect = table.getBoundingClientRect()
2024-02-22 16:56:35 +01:00
// The starting point of the table relative to the viewport
const tableStart = tableRect.top + scrollTop
2024-02-22 16:56:35 +01:00
// The end point of the table relative to the viewport
const tableEnd = tableStart + tableRect.height
2024-02-22 16:56:35 +01:00
let headerY = scrollTop - tableStart
let actionsY = scrollTop + window.innerHeight - tableEnd
2024-02-22 16:56:35 +01:00
if (headerY < 0) headerY = 0
2024-02-22 16:56:35 +01:00
if (scrollTop + window.innerHeight > tableEnd) {
actionsY =
tableRect.height - (scrollTop + window.innerHeight - tableEnd)
2023-12-09 15:47:03 +01:00
} else {
actionsY = tableRect.height
2024-02-22 16:56:35 +01:00
}
if (tableHeader) {
tableHeader.style.transform = `translate3d(0px, ${headerY}px, 0px)`
2023-12-09 15:47:03 +01:00
}
if (tableActionsRow) {
tableActionsRow.style.transform = `translate3d(0px, ${actionsY}px, 0px)`
2023-12-09 15:47:03 +01:00
}
})
2024-01-18 12:02:09 +01:00
},
setColumns(val) {
this.$emit("update-columns", val)
},
objectHas(object, key) {
return _has(object, key)
},
},
2023-12-09 15:47:03 +01:00
}
</script>
<style lang="scss">
.n-table {
.n-table-head {
height: 33px;
.resize-handler {
height: 33px;
width: 5px;
margin-left: -3px;
}
}
.n-table-row {
display: flex;
&.first,
&.loader {
2023-12-09 15:47:03 +01:00
margin-top: 33px;
}
}
.n-table-cell {
min-width: 80px;
}
}
</style>