Skip to content

Commit 1df8b4c

Browse files
committed
feat: Reorder Application resources
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent ac7b662 commit 1df8b4c

File tree

5 files changed

+203
-80
lines changed

5 files changed

+203
-80
lines changed

lib/Service/ContextService.php

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
66
* SPDX-License-Identifier: AGPL-3.0-or-later
77
*/
8+
89
namespace OCA\Tables\Service;
910

1011
use InvalidArgumentException;
@@ -238,6 +239,56 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i
238239
$currentPages[$updatedContent->getPageId()]['content'][$updatedContent->getId()] = $updatedContent->jsonSerialize();
239240
}
240241
unset($nodesBeingAdded);
242+
243+
// Update order of nodes
244+
$startPageId = null;
245+
foreach ($currentPages as $page) {
246+
if ($page['page_type'] === Page::TYPE_STARTPAGE) {
247+
$startPageId = $page['id'];
248+
break;
249+
}
250+
}
251+
252+
if ($startPageId !== null && isset($currentPages[$startPageId]['content'])) {
253+
$nodeTypeIdToRelId = [];
254+
foreach ($currentNodes as $relId => $nodeData) {
255+
$key = sprintf('t%di%d', $nodeData['node_type'], $nodeData['node_id']);
256+
$nodeTypeIdToRelId[$key] = $relId;
257+
}
258+
259+
$relIdToContentId = [];
260+
foreach ($currentPages[$startPageId]['content'] as $contentId => $content) {
261+
$relIdToContentId[$content['node_rel_id']] = $contentId;
262+
}
263+
264+
$contentsToUpdate = [];
265+
foreach ($nodes as $index => $node) {
266+
$key = sprintf('t%di%d', $node['type'], $node['id']);
267+
if (isset($nodeTypeIdToRelId[$key])) {
268+
$relId = $nodeTypeIdToRelId[$key];
269+
if (isset($relIdToContentId[$relId])) {
270+
$contentId = $relIdToContentId[$relId];
271+
$newOrder = ($index + 1) * 10;
272+
273+
// Optimistic check: if order is already correct in memory, we might skip?
274+
// However, simpler to just add to update list, updateContentOrder should handle exceptions.
275+
// But to avoid DB writes if nothing changed:
276+
if ($currentPages[$startPageId]['content'][$contentId]['order'] !== $newOrder) {
277+
$contentsToUpdate[] = [
278+
'id' => $contentId,
279+
'order' => $newOrder
280+
];
281+
$currentPages[$startPageId]['content'][$contentId]['order'] = $newOrder;
282+
$hasUpdatedNodeInformation = true;
283+
}
284+
}
285+
}
286+
}
287+
288+
if (!empty($contentsToUpdate)) {
289+
$this->updateContentOrder($startPageId, $contentsToUpdate);
290+
}
291+
}
241292
}
242293

243294
$context = $this->contextMapper->update($context);
@@ -316,8 +367,10 @@ public function transfer(int $contextId, string $newOwnerId, int $newOwnerType):
316367
}
317368

318369
$auditEvent = new CriticalActionPerformedEvent(
319-
sprintf('Tables application with ID %d was transferred to user %s',
320-
$contextId, $newOwnerId,
370+
sprintf(
371+
'Tables application with ID %d was transferred to user %s',
372+
$contextId,
373+
$newOwnerId,
321374
)
322375
);
323376

src/modules/modals/EditContext.vue

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<NcDialog v-if="showModal"
7-
:name="t('tables', 'Edit application')"
8-
size="normal"
9-
data-cy="editContextModal"
6+
<NcDialog v-if="showModal" :name="t('tables', 'Edit application')" size="normal" data-cy="editContextModal"
107
@closing="actionCancel">
118
<div class="modal__content" data-cy="editContextModal">
129
<div class="row">
@@ -30,7 +27,8 @@
3027
<div class="col-4">
3128
{{ t('tables', 'Description') }}
3229
</div>
33-
<input v-model="description" type="text" data-cy="editContextDes" :placeholder="t('tables', 'Description of the application')">
30+
<input v-model="description" type="text" data-cy="editContextDes"
31+
:placeholder="t('tables', 'Description of the application')">
3432
</div>
3533
<div class="col-4 row space-T">
3634
<div class="col-4">
@@ -55,7 +53,8 @@
5553
{{ t('tables', 'I really want to delete this application!') }}
5654
</NcButton>
5755
<div class="right-additional-button">
58-
<NcButton v-if="ownsContext(localContext)" data-cy="transferContextSubmitBtn" @click="actionTransfer">
56+
<NcButton v-if="ownsContext(localContext)" data-cy="transferContextSubmitBtn"
57+
@click="actionTransfer">
5958
{{ t('tables', 'Transfer application') }}
6059
</NcButton>
6160
<NcButton type="primary" data-cy="editContextSubmitBtn" @click="submit">
@@ -232,24 +231,35 @@ export default {
232231
},
233232
getContextResources(context) {
234233
const resources = []
235-
const nodes = Object.values(context.nodes)
236-
for (const node of nodes) {
237-
if (parseInt(node.node_type) === NODE_TYPE_TABLE || parseInt(node.node_type) === NODE_TYPE_VIEW) {
238-
const element = parseInt(node.node_type) === NODE_TYPE_TABLE ? this.tables.find(t => t.id === node.node_id) : this.views.find(v => v.id === node.node_id)
239-
if (element) {
240-
const elementKey = parseInt(node.node_type) === NODE_TYPE_TABLE ? 'table-' : 'view-'
241-
const resource = {
242-
title: element.title,
243-
emoji: element.emoji,
244-
key: `${elementKey}` + element.id,
245-
nodeType: parseInt(node.node_type) === NODE_TYPE_TABLE ? NODE_TYPE_TABLE : NODE_TYPE_VIEW,
246-
id: (element.id).toString(),
247-
permissionRead: this.getPermissionFromBitmask(node.permissions, PERMISSION_READ),
248-
permissionCreate: this.getPermissionFromBitmask(node.permissions, PERMISSION_CREATE),
249-
permissionUpdate: this.getPermissionFromBitmask(node.permissions, PERMISSION_UPDATE),
250-
permissionDelete: this.getPermissionFromBitmask(node.permissions, PERMISSION_DELETE),
234+
if (context && context.pages) {
235+
const pages = Object.values(context.pages)
236+
const startPage = pages.find(p => p.page_type === 'startpage')
237+
238+
if (startPage && startPage.content) {
239+
const sortedContent = Object.values(startPage.content).sort((a, b) => a.order - b.order)
240+
241+
for (const content of sortedContent) {
242+
const node = context.nodes[content.node_rel_id]
243+
if (!node) continue
244+
245+
if (parseInt(node.node_type) === NODE_TYPE_TABLE || parseInt(node.node_type) === NODE_TYPE_VIEW) {
246+
const element = parseInt(node.node_type) === NODE_TYPE_TABLE ? this.tables.find(t => t.id === node.node_id) : this.views.find(v => v.id === node.node_id)
247+
if (element) {
248+
const elementKey = parseInt(node.node_type) === NODE_TYPE_TABLE ? 'table-' : 'view-'
249+
const resource = {
250+
title: element.title,
251+
emoji: element.emoji,
252+
key: `${elementKey}` + element.id,
253+
nodeType: parseInt(node.node_type) === NODE_TYPE_TABLE ? NODE_TYPE_TABLE : NODE_TYPE_VIEW,
254+
id: (element.id).toString(),
255+
permissionRead: this.getPermissionFromBitmask(node.permissions, PERMISSION_READ),
256+
permissionCreate: this.getPermissionFromBitmask(node.permissions, PERMISSION_CREATE),
257+
permissionUpdate: this.getPermissionFromBitmask(node.permissions, PERMISSION_UPDATE),
258+
permissionDelete: this.getPermissionFromBitmask(node.permissions, PERMISSION_DELETE),
259+
}
260+
resources.push(resource)
261+
}
251262
}
252-
resources.push(resource)
253263
}
254264
}
255265
}

src/pages/Context.vue

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -165,46 +165,62 @@ export default {
165165
166166
this.icon = await this.getContextIcon(this.activeContext.iconName)
167167
168-
if (this.context && this.context.nodes) {
169-
for (const [, node] of Object.entries(this.context.nodes)) {
170-
try {
171-
const nodeType = parseInt(node.node_type)
172-
if (nodeType === NODE_TYPE_TABLE) {
173-
const table = this.tables.find(table => table.id === node.node_id)
174-
if (table) {
175-
await this.loadColumnsFromBE({
176-
view: null,
177-
tableId: table.id,
178-
})
179-
await this.loadRowsFromBE({
180-
viewId: null,
181-
tableId: table.id,
182-
})
183-
table.key = (table.id).toString()
184-
table.isView = false
185-
this.contextResources.push(table)
186-
}
168+
this.icon = await this.getContextIcon(this.activeContext.iconName)
169+
170+
if (this.context && this.context.pages) {
171+
const pages = Object.values(this.context.pages)
172+
const startPage = pages.find(p => p.page_type === 'startpage')
173+
174+
if (startPage && startPage.content) {
175+
const sortedContent = Object.values(startPage.content).sort((a, b) => a.order - b.order)
176+
177+
for (const content of sortedContent) {
178+
const node = this.context.nodes[content.node_rel_id]
179+
if (!node) continue
180+
181+
try {
182+
const nodeType = parseInt(node.node_type)
183+
if (nodeType === NODE_TYPE_TABLE) {
184+
const table = this.tables.find(table => table.id === node.node_id)
185+
if (table) {
186+
await this.loadColumnsFromBE({
187+
view: null,
188+
tableId: table.id,
189+
})
190+
await this.loadRowsFromBE({
191+
viewId: null,
192+
tableId: table.id,
193+
})
194+
table.key = (table.id).toString()
195+
table.isView = false
196+
this.contextResources.push(table)
197+
}
187198
188-
} else if (nodeType === NODE_TYPE_VIEW) {
189-
const view = this.views.find(view => view.id === node.node_id)
190-
if (view) {
191-
await this.loadColumnsFromBE({
192-
view,
193-
})
194-
await this.loadRowsFromBE({
195-
viewId: view.id,
196-
tableId: view.tableId,
197-
})
198-
view.key = 'view-' + (view.id).toString()
199-
view.isView = true
200-
this.contextResources.push(view)
199+
} else if (nodeType === NODE_TYPE_VIEW) {
200+
const view = this.views.find(view => view.id === node.node_id)
201+
if (view) {
202+
await this.loadColumnsFromBE({
203+
view,
204+
})
205+
await this.loadRowsFromBE({
206+
viewId: view.id,
207+
tableId: view.tableId,
208+
})
209+
view.key = 'view-' + (view.id).toString()
210+
view.isView = true
211+
this.contextResources.push(view)
212+
}
201213
}
214+
} catch (err) {
215+
console.error(`Failed to load resource ${node.node_id}:`, err)
216+
this.errorMessage = t('tables', 'Some resources in this application could not be loaded')
202217
}
203-
} catch (err) {
204-
console.error(`Failed to load resource ${node.node_id}:`, err)
205-
this.errorMessage = t('tables', 'Some resources in this application could not be loaded')
206218
}
207219
}
220+
// TODO: check this
221+
// Fallback if no startpage or content (though unexpected for valid contexts with nodes)
222+
// If nodes exist but not in startpage content, they won't be shown.
223+
// This matches backend logic where nodes are added to startpage.
208224
}
209225
} catch (e) {
210226
if (e.message === 'NOT_FOUND') {

src/shared/components/ncContextResource/NcContextResource.vue

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
<div>
77
<div>
88
<ResourceForm :resources="localResources" data-cy="contextResourceForm" @add="addResource" />
9-
<ResourceList :resources="localResources" data-cy="contextResourceList" @remove="removeResource" />
10-
<ResourceSharees :select-users="true" :select-groups="true" :receivers="localReceivers" data-cy="contextResourceShare" @update="updateReceivers" />
11-
<ResourceSharePermissions :resources="localResources" data-cy="contextResourcePerms" @update="updateResourcePermissions" />
9+
<ResourceList :resources="localResources" data-cy="contextResourceList" @remove="removeResource"
10+
@update:resources="updateOrder" />
11+
<ResourceSharees :select-users="true" :select-groups="true" :receivers="localReceivers"
12+
data-cy="contextResourceShare" @update="updateReceivers" />
13+
<ResourceSharePermissions :resources="localResources" data-cy="contextResourcePerms"
14+
@update="updateResourcePermissions" />
1215
</div>
1316
</div>
1417
</template>
@@ -83,6 +86,10 @@ export default {
8386
this.contextResources.push(resource)
8487
this.localResources = this.contextResources
8588
},
89+
updateOrder(resources) {
90+
this.contextResources = resources
91+
this.localResources = this.contextResources
92+
},
8693
updateReceivers(receivers) {
8794
this.localReceivers = receivers
8895
},

0 commit comments

Comments
 (0)