@shamar/adonis 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/assets/admin.css +1 -1
- package/assets/input.css +42 -2
- package/assets/shamar-ui.js +290 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/controllers/admin_controller.d.ts +2 -0
- package/dist/controllers/admin_controller.d.ts.map +1 -1
- package/dist/controllers/admin_controller.js +80 -3
- package/dist/controllers/admin_controller.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +2 -1
- package/dist/provider.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +27 -5
- package/dist/routes.js.map +1 -1
- package/dist/shamar/branding.d.ts +11 -10
- package/dist/shamar/branding.d.ts.map +1 -1
- package/dist/shamar/branding.js +42 -2
- package/dist/shamar/branding.js.map +1 -1
- package/dist/shamar/list-query.d.ts +11 -2
- package/dist/shamar/list-query.d.ts.map +1 -1
- package/dist/shamar/list-query.js +29 -1
- package/dist/shamar/list-query.js.map +1 -1
- package/dist/shamar/relation-fields.d.ts +15 -0
- package/dist/shamar/relation-fields.d.ts.map +1 -1
- package/dist/shamar/relation-fields.js +22 -2
- package/dist/shamar/relation-fields.js.map +1 -1
- package/dist/shamar/relation-table.d.ts +37 -0
- package/dist/shamar/relation-table.d.ts.map +1 -0
- package/dist/shamar/relation-table.js +61 -0
- package/dist/shamar/relation-table.js.map +1 -0
- package/dist/shamar/view-context.d.ts +6 -0
- package/dist/shamar/view-context.d.ts.map +1 -1
- package/dist/shamar/view-context.js +4 -0
- package/dist/shamar/view-context.js.map +1 -1
- package/package.json +5 -5
- package/resources/views/shamar/form.edge +7 -1
- package/resources/views/shamar/index.edge +3 -2
- package/resources/views/shamar/partials/detail-entry.edge +9 -0
- package/resources/views/shamar/partials/field-relation-table.edge +309 -105
- package/resources/views/shamar/partials/list-row.edge +17 -4
- package/resources/views/shamar/partials/list-toolbar.edge +1 -1
- package/resources/views/shamar/partials/shell-open.edge +4 -0
- package/resources/views/shamar/show.edge +59 -14
package/assets/shamar-ui.js
CHANGED
|
@@ -818,9 +818,12 @@
|
|
|
818
818
|
|
|
819
819
|
openRecord() {
|
|
820
820
|
if (!this.value || !this.detailUrlBase) return;
|
|
821
|
+
const url = this.readonly
|
|
822
|
+
? `${this.detailUrlBase}/${this.value}`
|
|
823
|
+
: `${this.detailUrlBase}/${this.value}/edit`;
|
|
821
824
|
window.ShamarUI.openDialog({
|
|
822
|
-
url
|
|
823
|
-
title: this.label || 'Edit record',
|
|
825
|
+
url,
|
|
826
|
+
title: this.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
824
827
|
slug: this.relatedResource,
|
|
825
828
|
});
|
|
826
829
|
},
|
|
@@ -1119,9 +1122,12 @@
|
|
|
1119
1122
|
|
|
1120
1123
|
openRecord(item) {
|
|
1121
1124
|
if (!item?.id || !this.detailUrlBase) return;
|
|
1125
|
+
const url = this.readonly
|
|
1126
|
+
? `${this.detailUrlBase}/${item.id}`
|
|
1127
|
+
: `${this.detailUrlBase}/${item.id}/edit`;
|
|
1122
1128
|
window.ShamarUI.openDialog({
|
|
1123
|
-
url
|
|
1124
|
-
title: item.label || 'Edit record',
|
|
1129
|
+
url,
|
|
1130
|
+
title: item.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
1125
1131
|
slug: this.relatedResource,
|
|
1126
1132
|
});
|
|
1127
1133
|
},
|
|
@@ -1453,10 +1459,30 @@
|
|
|
1453
1459
|
function createShamarM2mTable(cfg) {
|
|
1454
1460
|
const self = createShamarM2m(cfg);
|
|
1455
1461
|
self.addOpen = false;
|
|
1462
|
+
self.listUrl = cfg.listUrl || null;
|
|
1463
|
+
self.columns = Array.isArray(cfg.columns) ? cfg.columns : [];
|
|
1464
|
+
self.listHeaders = Array.isArray(cfg.listHeaders) ? cfg.listHeaders : [];
|
|
1465
|
+
self.rows = [];
|
|
1466
|
+
self.total = 0;
|
|
1467
|
+
self.page = 1;
|
|
1468
|
+
self.pageCount = 1;
|
|
1469
|
+
self.perPage = String(cfg.perPage || 10);
|
|
1470
|
+
self.listSearch = '';
|
|
1471
|
+
self.listFilters = [];
|
|
1472
|
+
self.listSort = cfg.defaultSort || '';
|
|
1473
|
+
self.listDirection = cfg.defaultDirection || 'asc';
|
|
1474
|
+
self.listLoading = false;
|
|
1475
|
+
self.filterPanelOpen = false;
|
|
1476
|
+
self._listAbort = null;
|
|
1477
|
+
|
|
1456
1478
|
const prevInit = self.init;
|
|
1457
1479
|
const prevDestroy = self.destroy;
|
|
1458
1480
|
const prevPick = self.pick;
|
|
1459
1481
|
const prevCreateAndEdit = self.createAndEdit;
|
|
1482
|
+
const prevAttach = self.attachHasMany;
|
|
1483
|
+
const prevDetach = self.detachHasMany;
|
|
1484
|
+
const prevOpenRecord = self.openRecord;
|
|
1485
|
+
|
|
1460
1486
|
self.init = function initTable() {
|
|
1461
1487
|
prevInit.call(this);
|
|
1462
1488
|
this._tablePickHandler = (event) => {
|
|
@@ -1467,13 +1493,190 @@
|
|
|
1467
1493
|
}
|
|
1468
1494
|
};
|
|
1469
1495
|
window.addEventListener('shamar-m2m-pick', this._tablePickHandler);
|
|
1496
|
+
if (this.listUrl) {
|
|
1497
|
+
this.loadList();
|
|
1498
|
+
}
|
|
1470
1499
|
};
|
|
1500
|
+
|
|
1471
1501
|
self.destroy = function destroyTable() {
|
|
1472
1502
|
prevDestroy.call(this);
|
|
1473
1503
|
if (this._tablePickHandler) {
|
|
1474
1504
|
window.removeEventListener('shamar-m2m-pick', this._tablePickHandler);
|
|
1475
1505
|
}
|
|
1506
|
+
if (this._listAbort) this._listAbort.abort();
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
Object.defineProperty(self, 'filterableHeaders', {
|
|
1510
|
+
get() {
|
|
1511
|
+
return (this.listHeaders || []).filter(
|
|
1512
|
+
(header) =>
|
|
1513
|
+
header.filter_kind === 'boolean' ||
|
|
1514
|
+
(header.filter_kind === 'select' && Array.isArray(header.options)),
|
|
1515
|
+
);
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
Object.defineProperty(self, 'availableResults', {
|
|
1520
|
+
get() {
|
|
1521
|
+
const linked = new Set([
|
|
1522
|
+
...(this.selected || []).map((item) => String(item.id)),
|
|
1523
|
+
...(this.rows || []).map((row) => String(row.id)),
|
|
1524
|
+
]);
|
|
1525
|
+
return (this.results || []).filter((item) => !linked.has(String(item.id)));
|
|
1526
|
+
},
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
self.cellAlignment = function cellAlignment(columnName) {
|
|
1530
|
+
const column = (this.columns || []).find((entry) => entry.name === columnName);
|
|
1531
|
+
return column?.alignmentClass || '';
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
self.buildListUrl = function buildListUrl() {
|
|
1535
|
+
const url = new URL(this.listUrl, window.location.origin);
|
|
1536
|
+
if (this.listSearch) url.searchParams.set('search', this.listSearch);
|
|
1537
|
+
if (this.listSort) {
|
|
1538
|
+
url.searchParams.set('sort', this.listSort);
|
|
1539
|
+
if (this.listDirection === 'asc' || this.listDirection === 'desc') {
|
|
1540
|
+
url.searchParams.set('direction', this.listDirection);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
url.searchParams.set('page', String(this.page || 1));
|
|
1544
|
+
url.searchParams.set('perPage', String(this.perPage || 10));
|
|
1545
|
+
if (this.listFilters?.length) {
|
|
1546
|
+
url.searchParams.set('filters', JSON.stringify(this.listFilters));
|
|
1547
|
+
}
|
|
1548
|
+
return `${url.pathname}${url.search}`;
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
self.loadList = async function loadList() {
|
|
1552
|
+
if (!this.listUrl) return;
|
|
1553
|
+
if (this._listAbort) this._listAbort.abort();
|
|
1554
|
+
const ctl = new AbortController();
|
|
1555
|
+
this._listAbort = ctl;
|
|
1556
|
+
this.listLoading = true;
|
|
1557
|
+
try {
|
|
1558
|
+
const response = await fetch(this.buildListUrl(), {
|
|
1559
|
+
signal: ctl.signal,
|
|
1560
|
+
credentials: 'same-origin',
|
|
1561
|
+
headers: { Accept: 'application/json' },
|
|
1562
|
+
});
|
|
1563
|
+
if (!response.ok) throw new Error('list failed');
|
|
1564
|
+
const data = await response.json();
|
|
1565
|
+
this.rows = Array.isArray(data.rows) ? data.rows : [];
|
|
1566
|
+
this.total = Number(data.total) || 0;
|
|
1567
|
+
this.page = Number(data.page) || 1;
|
|
1568
|
+
this.pageCount = Math.max(1, Number(data.pageCount) || 1);
|
|
1569
|
+
this.perPage = String(data.perPage || this.perPage || 10);
|
|
1570
|
+
if (Array.isArray(data.columns) && data.columns.length) {
|
|
1571
|
+
this.columns = data.columns;
|
|
1572
|
+
}
|
|
1573
|
+
if (Array.isArray(data.listHeaders)) {
|
|
1574
|
+
this.listHeaders = data.listHeaders;
|
|
1575
|
+
}
|
|
1576
|
+
if (data.sort) this.listSort = data.sort;
|
|
1577
|
+
if (data.direction) this.listDirection = data.direction;
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
if (error.name !== 'AbortError') {
|
|
1580
|
+
this.rows = [];
|
|
1581
|
+
this.total = 0;
|
|
1582
|
+
showToast('error', {
|
|
1583
|
+
title: 'Error',
|
|
1584
|
+
message: 'Unable to load related records.',
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
} finally {
|
|
1588
|
+
this.listLoading = false;
|
|
1589
|
+
}
|
|
1476
1590
|
};
|
|
1591
|
+
|
|
1592
|
+
self.applyListSearch = function applyListSearch() {
|
|
1593
|
+
this.page = 1;
|
|
1594
|
+
this.loadList();
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
self.setListPage = function setListPage(page) {
|
|
1598
|
+
const next = Math.max(1, Number(page) || 1);
|
|
1599
|
+
this.page = next;
|
|
1600
|
+
this.loadList();
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
self.toggleListSort = function toggleListSort(field) {
|
|
1604
|
+
if (!field) return;
|
|
1605
|
+
if (this.listSort === field) {
|
|
1606
|
+
this.listDirection = this.listDirection === 'asc' ? 'desc' : 'asc';
|
|
1607
|
+
} else {
|
|
1608
|
+
this.listSort = field;
|
|
1609
|
+
this.listDirection = 'asc';
|
|
1610
|
+
}
|
|
1611
|
+
this.page = 1;
|
|
1612
|
+
this.loadList();
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
self.hasFilter = function hasFilter(header, value) {
|
|
1616
|
+
const field = header.filterField || header.name;
|
|
1617
|
+
return this.listFilters.some(
|
|
1618
|
+
(chip) => chip.field === field && String(chip.value) === String(value),
|
|
1619
|
+
);
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
self.toggleBooleanFilter = function toggleBooleanFilter(header, value) {
|
|
1623
|
+
const field = header.filterField || header.name;
|
|
1624
|
+
const existing = this.listFilters.find((chip) => chip.field === field);
|
|
1625
|
+
if (existing && String(existing.value) === String(value)) {
|
|
1626
|
+
this.listFilters = this.listFilters.filter((chip) => chip.field !== field);
|
|
1627
|
+
} else {
|
|
1628
|
+
this.listFilters = [
|
|
1629
|
+
...this.listFilters.filter((chip) => chip.field !== field),
|
|
1630
|
+
{
|
|
1631
|
+
field,
|
|
1632
|
+
op: '=',
|
|
1633
|
+
value,
|
|
1634
|
+
label: `${header.label}: ${value ? 'Yes' : 'No'}`,
|
|
1635
|
+
},
|
|
1636
|
+
];
|
|
1637
|
+
}
|
|
1638
|
+
this.page = 1;
|
|
1639
|
+
this.loadList();
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
self.toggleSelectFilter = function toggleSelectFilter(header, opt) {
|
|
1643
|
+
const field = header.filterField || header.name;
|
|
1644
|
+
const value = opt.value;
|
|
1645
|
+
if (this.hasFilter(header, value)) {
|
|
1646
|
+
this.listFilters = this.listFilters.filter(
|
|
1647
|
+
(chip) => !(chip.field === field && String(chip.value) === String(value)),
|
|
1648
|
+
);
|
|
1649
|
+
} else {
|
|
1650
|
+
this.listFilters = [
|
|
1651
|
+
...this.listFilters.filter((chip) => chip.field !== field),
|
|
1652
|
+
{
|
|
1653
|
+
field,
|
|
1654
|
+
op: '=',
|
|
1655
|
+
value,
|
|
1656
|
+
label: `${header.label}: ${opt.label}`,
|
|
1657
|
+
},
|
|
1658
|
+
];
|
|
1659
|
+
}
|
|
1660
|
+
this.page = 1;
|
|
1661
|
+
this.loadList();
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
self.removeListFilter = function removeListFilter(chip) {
|
|
1665
|
+
this.listFilters = this.listFilters.filter(
|
|
1666
|
+
(entry) =>
|
|
1667
|
+
!(entry.field === chip.field && String(entry.value) === String(chip.value)),
|
|
1668
|
+
);
|
|
1669
|
+
this.page = 1;
|
|
1670
|
+
this.loadList();
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
self.clearListFilters = function clearListFilters() {
|
|
1674
|
+
this.listFilters = [];
|
|
1675
|
+
this.filterPanelOpen = false;
|
|
1676
|
+
this.page = 1;
|
|
1677
|
+
this.loadList();
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1477
1680
|
self.toggleAdd = function toggleAdd() {
|
|
1478
1681
|
this.addOpen = !this.addOpen;
|
|
1479
1682
|
if (this.addOpen) {
|
|
@@ -1482,18 +1685,76 @@
|
|
|
1482
1685
|
this.$nextTick?.(() => this.$refs.addInput?.focus());
|
|
1483
1686
|
}
|
|
1484
1687
|
};
|
|
1688
|
+
|
|
1485
1689
|
self.closeAdd = function closeAdd() {
|
|
1486
1690
|
this.addOpen = false;
|
|
1487
1691
|
this.query = '';
|
|
1488
1692
|
};
|
|
1693
|
+
|
|
1489
1694
|
self.pick = function pickTable(item) {
|
|
1695
|
+
if (
|
|
1696
|
+
this.listUrl &&
|
|
1697
|
+
item?.id != null &&
|
|
1698
|
+
this.rows.some((row) => String(row.id) === String(item.id))
|
|
1699
|
+
) {
|
|
1700
|
+
this.closeAdd();
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1490
1703
|
prevPick.call(this, item);
|
|
1491
1704
|
this.closeAdd();
|
|
1492
1705
|
};
|
|
1706
|
+
|
|
1707
|
+
self.attachHasMany = async function attachHasManyTable(item) {
|
|
1708
|
+
await prevAttach.call(this, item);
|
|
1709
|
+
if (this.listUrl) {
|
|
1710
|
+
this.page = 1;
|
|
1711
|
+
await this.loadList();
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
self.detachHasMany = async function detachHasManyTable(relatedId) {
|
|
1716
|
+
await prevDetach.call(this, relatedId);
|
|
1717
|
+
if (this.listUrl) {
|
|
1718
|
+
await this.loadList();
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
self.openRecord = function openRecordTable(item) {
|
|
1723
|
+
if (!item?.id || !this.detailUrlBase) return;
|
|
1724
|
+
const url = this.readonly
|
|
1725
|
+
? `${this.detailUrlBase}/${item.id}`
|
|
1726
|
+
: `${this.detailUrlBase}/${item.id}/edit`;
|
|
1727
|
+
window.ShamarUI.openDialog({
|
|
1728
|
+
url,
|
|
1729
|
+
title: item.label || (this.readonly ? 'View record' : 'Edit record'),
|
|
1730
|
+
slug: this.relatedResource,
|
|
1731
|
+
onResult: () => {
|
|
1732
|
+
if (this.listUrl) this.loadList();
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1493
1737
|
self.createAndEdit = function createAndEditTable() {
|
|
1494
1738
|
this.closeAdd();
|
|
1495
|
-
|
|
1739
|
+
if (!this.createUrl) return;
|
|
1740
|
+
const q = (this.query || '').trim();
|
|
1741
|
+
const url = q
|
|
1742
|
+
? `${this.createUrl}${this.createUrl.includes('?') ? '&' : '?'}name=${encodeURIComponent(q)}`
|
|
1743
|
+
: this.createUrl;
|
|
1744
|
+
const title = q ? `New ${this.singularLabel}` : `Create ${this.singularLabel}`;
|
|
1745
|
+
window.ShamarUI.openDialog({
|
|
1746
|
+
url,
|
|
1747
|
+
title,
|
|
1748
|
+
slug: this.relatedResource,
|
|
1749
|
+
onResult: (detail) => {
|
|
1750
|
+
if (detail?.id) {
|
|
1751
|
+
prevPick.call(this, { id: detail.id, label: detail.label });
|
|
1752
|
+
}
|
|
1753
|
+
if (this.listUrl) this.loadList();
|
|
1754
|
+
},
|
|
1755
|
+
});
|
|
1496
1756
|
};
|
|
1757
|
+
|
|
1497
1758
|
return self;
|
|
1498
1759
|
}
|
|
1499
1760
|
|
|
@@ -3134,12 +3395,36 @@
|
|
|
3134
3395
|
applyListHrefs();
|
|
3135
3396
|
bindListViewSwitcher();
|
|
3136
3397
|
bindListRefresh();
|
|
3398
|
+
bindListRowNavigation();
|
|
3137
3399
|
bindMediaUploads();
|
|
3138
3400
|
bindFormSaveShortcut();
|
|
3139
3401
|
bindFormAutosave();
|
|
3140
3402
|
bindRecordPagerNav();
|
|
3141
3403
|
});
|
|
3142
3404
|
|
|
3405
|
+
function bindListRowNavigation() {
|
|
3406
|
+
document.addEventListener('click', (event) => {
|
|
3407
|
+
if (!(event instanceof MouseEvent) || event.button !== 0) return;
|
|
3408
|
+
if (event.defaultPrevented) return;
|
|
3409
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3410
|
+
const target = event.target;
|
|
3411
|
+
if (!(target instanceof Element)) return;
|
|
3412
|
+
if (
|
|
3413
|
+
target.closest(
|
|
3414
|
+
'a, button, input, select, textarea, label, [data-shamar-stop-row-nav]',
|
|
3415
|
+
)
|
|
3416
|
+
) {
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
const row = target.closest('tr[data-shamar-row-href]');
|
|
3420
|
+
if (!row) return;
|
|
3421
|
+
const href = row.getAttribute('data-shamar-row-href');
|
|
3422
|
+
if (!href) return;
|
|
3423
|
+
event.preventDefault();
|
|
3424
|
+
window.location.assign(href);
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3143
3428
|
function relationConfigsFromForm(form) {
|
|
3144
3429
|
const configs = [];
|
|
3145
3430
|
if (!(form instanceof HTMLFormElement)) return configs;
|
package/dist/config.d.ts
CHANGED
|
@@ -71,6 +71,33 @@ export interface ShamarConfig {
|
|
|
71
71
|
};
|
|
72
72
|
/** Default branding inherited by panels without their own. */
|
|
73
73
|
branding?: PanelConfig['branding'];
|
|
74
|
+
/**
|
|
75
|
+
* Optional REST / OpenAPI docs settings (consumed by `@shamar/rest`).
|
|
76
|
+
* Prefer nesting here or use a separate `config/shamar_rest.ts`.
|
|
77
|
+
*/
|
|
78
|
+
rest?: {
|
|
79
|
+
enabled?: boolean;
|
|
80
|
+
openapiPath?: string;
|
|
81
|
+
docs?: {
|
|
82
|
+
enabled?: boolean;
|
|
83
|
+
path?: string;
|
|
84
|
+
ui?: 'scalar';
|
|
85
|
+
};
|
|
86
|
+
openapi?: {
|
|
87
|
+
title?: string;
|
|
88
|
+
version?: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
servers?: Array<{
|
|
91
|
+
url: string;
|
|
92
|
+
description?: string;
|
|
93
|
+
}>;
|
|
94
|
+
};
|
|
95
|
+
security?: boolean;
|
|
96
|
+
discover?: false | {
|
|
97
|
+
prefixes?: string[];
|
|
98
|
+
exclude?: string[];
|
|
99
|
+
};
|
|
100
|
+
};
|
|
74
101
|
}
|
|
75
102
|
export declare function defineConfig(config: ShamarConfig): ShamarConfig & {
|
|
76
103
|
panels: PanelConfig[];
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,QAAQ,EAET,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IAC5C;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;IACnC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAC5C,2EAA2E;QAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,oEAAoE;QACpE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,8CAA8C;QAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,wDAAwD;QACxD,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,qEAAqE;QACrE,SAAS,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3C,4CAA4C;QAC5C,YAAY,CAAC,EAAE,YAAY,CAAC;QAC5B,sEAAsE;QACtE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACvC;;;;;;;;WAQG;QACH,OAAO,CAAC,EAAE;YACR,OAAO,EAAE,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,iBAAiB,KACnB,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;YACxD;;;;eAIG;YACH,UAAU,CAAC,EAAE,OAAO,CAAC;YACrB;;;eAGG;YACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;SACrC,CAAC;QACF,iEAAiE;QACjE,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;KAC9F,CAAC;IACF,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAqB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAqB,MAAM,cAAc,CAAC;AAqGvE,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,QAAQ;QACjC,SAAS,EAAE,aAAa;QACxB,GAAG,EAAE,OAAO;QACZ,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,IAAI,EAAE;QACrC,GAAG,MAAM;QACT,MAAM;KACP,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,WAAW,IAAI,KAAK,EAAE,CAAC;AAEhC,SAAS,eAAe,CAAC,MAAoB;IAC3C,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,MAAM,KAAK,GAAG,OAAQ,KAAsB,CAAC,KAAK,KAAK,UAAU;gBAC/D,CAAC,CAAE,KAAsB,CAAC,KAAK,EAAE;gBACjC,CAAC,CAAE,KAAqB,CAAC;YAC3B,OAAO;gBACL,GAAG,KAAK;gBACR,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG;gBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;gBAC3C,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;aACxC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL;YACE,EAAE,EAAE,OAAO;YACX,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,QAAQ;YAC7B,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;SACzC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -9,6 +9,7 @@ export declare class AdminController {
|
|
|
9
9
|
private readonly basePath;
|
|
10
10
|
private readonly registry;
|
|
11
11
|
private readonly panelBranding;
|
|
12
|
+
private readonly panelContentMaxWidth?;
|
|
12
13
|
private readonly authorizer;
|
|
13
14
|
constructor(panel: PanelRuntime, config: ShamarConfig, authorizer?: Authorizer);
|
|
14
15
|
private isAuthContext;
|
|
@@ -35,6 +36,7 @@ export declare class AdminController {
|
|
|
35
36
|
formState(ctx: ShamarHttpContext): Promise<unknown>;
|
|
36
37
|
summary(ctx: ShamarHttpContext): Promise<unknown>;
|
|
37
38
|
relationSearch(ctx: ShamarHttpContext): Promise<unknown>;
|
|
39
|
+
relationTable(ctx: ShamarHttpContext): Promise<unknown>;
|
|
38
40
|
relationQuickCreate(ctx: ShamarHttpContext): Promise<unknown>;
|
|
39
41
|
relationAttach(ctx: ShamarHttpContext): Promise<unknown>;
|
|
40
42
|
relationDetach(ctx: ShamarHttpContext): Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_controller.d.ts","sourceRoot":"","sources":["../../src/controllers/admin_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_controller.d.ts","sourceRoot":"","sources":["../../src/controllers/admin_controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveGridItemStyle, isValidationException } from '@shamar/core';
|
|
1
|
+
import { resolveGridItemStyle, isValidationException, relationUsesListTable } from '@shamar/core';
|
|
2
2
|
import { Authorizer as AuthorizerClass } from '@shamar/cherubim';
|
|
3
3
|
import { ResourceController } from '../controller.js';
|
|
4
4
|
import { authRequired, assertResourceAccess, buildAuthContext, ForbiddenError, resourcePolicyFlags, respondForbidden, respondUnauthorized, UnauthorizedError, } from '../shamar/auth.js';
|
|
@@ -9,6 +9,7 @@ import { formSections, formSchemaTree, detailSections, detailSchemaTree, normali
|
|
|
9
9
|
import { evaluateFormState, formClientFields } from '../form-state.js';
|
|
10
10
|
import { buildRelationUiConfig, collectRelationIds, isRelationField, recordSummaryLabel, resolveRelatedMeta, relationTitleAttribute, } from '../shamar/relation-fields.js';
|
|
11
11
|
import { hydrateRecordsForDisplay } from '../shamar/display-relations.js';
|
|
12
|
+
import { buildRelationTableRows, relationTableListMeta, } from '../shamar/relation-table.js';
|
|
12
13
|
export class AdminController {
|
|
13
14
|
panel;
|
|
14
15
|
config;
|
|
@@ -16,6 +17,7 @@ export class AdminController {
|
|
|
16
17
|
basePath;
|
|
17
18
|
registry;
|
|
18
19
|
panelBranding;
|
|
20
|
+
panelContentMaxWidth;
|
|
19
21
|
authorizer;
|
|
20
22
|
constructor(panel, config, authorizer) {
|
|
21
23
|
this.panel = panel;
|
|
@@ -23,6 +25,7 @@ export class AdminController {
|
|
|
23
25
|
this.registry = panel.registry;
|
|
24
26
|
this.basePath = panel.path;
|
|
25
27
|
this.panelBranding = panel.config.branding ?? config.branding;
|
|
28
|
+
this.panelContentMaxWidth = panel.config.contentMaxWidth;
|
|
26
29
|
this.resources = new ResourceController(panel.adapter);
|
|
27
30
|
this.authorizer = authorizer ?? new AuthorizerClass();
|
|
28
31
|
}
|
|
@@ -71,6 +74,7 @@ export class AdminController {
|
|
|
71
74
|
registry: this.registry,
|
|
72
75
|
basePath: this.basePath,
|
|
73
76
|
branding: this.panelBranding,
|
|
77
|
+
panelContentMaxWidth: this.panelContentMaxWidth,
|
|
74
78
|
authorizer: this.authorizer,
|
|
75
79
|
authCtx,
|
|
76
80
|
flash: readFlash(ctx),
|
|
@@ -274,6 +278,7 @@ export class AdminController {
|
|
|
274
278
|
if (this.wantsJson(ctx, options?.asJson)) {
|
|
275
279
|
return ctx.response.json(record);
|
|
276
280
|
}
|
|
281
|
+
const embed = this.isEmbed(ctx);
|
|
277
282
|
const title = recordTitle(meta, record);
|
|
278
283
|
const policy = resourcePolicyFlags(this.authorizer, authCtx, this.registry, meta, record);
|
|
279
284
|
const shell = this.shellOpts(ctx, authCtx, {
|
|
@@ -295,11 +300,14 @@ export class AdminController {
|
|
|
295
300
|
resource: meta,
|
|
296
301
|
record,
|
|
297
302
|
id: record.id,
|
|
303
|
+
embed,
|
|
298
304
|
detailSchema: detailSchemaTree(meta),
|
|
299
305
|
detailSections: detailSections(meta),
|
|
300
306
|
relationUi,
|
|
301
307
|
formErrors: {},
|
|
302
|
-
recordPager:
|
|
308
|
+
recordPager: embed
|
|
309
|
+
? null
|
|
310
|
+
: await this.resolveRecordPager(ctx, meta, String(record.id), 'show'),
|
|
303
311
|
resolveGridItemStyle,
|
|
304
312
|
recordActions: visibleRowActions(meta, policy, record),
|
|
305
313
|
});
|
|
@@ -431,6 +439,66 @@ export class AdminController {
|
|
|
431
439
|
});
|
|
432
440
|
return ctx.response.json({ results });
|
|
433
441
|
}
|
|
442
|
+
async relationTable(ctx) {
|
|
443
|
+
const meta = this.requireResource(ctx);
|
|
444
|
+
const authResult = await this.ensureResourceAction(ctx, meta, 'viewAny', undefined, true);
|
|
445
|
+
if (!this.isAuthContext(authResult))
|
|
446
|
+
return authResult;
|
|
447
|
+
const authCtx = authResult;
|
|
448
|
+
const fieldName = String(ctx.request.input('field') ?? '');
|
|
449
|
+
const field = meta.fields.find((entry) => entry.name === fieldName);
|
|
450
|
+
if (!field?.relation || field.relation.kind !== 'hasMany' || !field.relation.foreignKey) {
|
|
451
|
+
return ctx.response.badRequest({ message: 'Relation table requires a hasMany field' });
|
|
452
|
+
}
|
|
453
|
+
const parentId = ctx.request.input('parentId');
|
|
454
|
+
if (!parentId) {
|
|
455
|
+
return ctx.response.badRequest({ message: 'parentId is required' });
|
|
456
|
+
}
|
|
457
|
+
const related = resolveRelatedMeta(this.registry, field.relation);
|
|
458
|
+
const relatedAuth = await this.ensureResourceAction(ctx, related, 'viewAny', undefined, true);
|
|
459
|
+
if (!this.isAuthContext(relatedAuth))
|
|
460
|
+
return relatedAuth;
|
|
461
|
+
const { columnConfigs, columns, listHeaders } = relationTableListMeta(related, field.relation.foreignKey);
|
|
462
|
+
const rawQuery = this.listQuery(ctx);
|
|
463
|
+
const hasFiltersParam = ctx.request.input('filters') !== undefined && ctx.request.input('filters') !== null;
|
|
464
|
+
const normalized = normalizeListQuery(rawQuery, { perPage: 10 });
|
|
465
|
+
if (!hasFiltersParam && related.defaultFilters?.length) {
|
|
466
|
+
normalized.filters = resolveDefaultFilters(related, listHeaders);
|
|
467
|
+
}
|
|
468
|
+
else if (normalized.filters?.length) {
|
|
469
|
+
normalized.filters = labelListFilters(normalized.filters, listHeaders);
|
|
470
|
+
}
|
|
471
|
+
if (!normalized.sort && related.defaultSort) {
|
|
472
|
+
normalized.sort = related.defaultSort.field;
|
|
473
|
+
normalized.direction = related.defaultSort.direction;
|
|
474
|
+
}
|
|
475
|
+
const query = this.withPolicyScope(authCtx, related, {
|
|
476
|
+
...normalized,
|
|
477
|
+
scope: {
|
|
478
|
+
...(normalized.scope ?? {}),
|
|
479
|
+
[field.relation.foreignKey]: String(parentId),
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
const result = await this.resources.index(related, query);
|
|
483
|
+
await hydrateRecordsForDisplay(related, result.items, this.registry, this.panel.adapter);
|
|
484
|
+
decorateRevokedStatus(result.items);
|
|
485
|
+
const titleAttribute = relationTitleAttribute(field.relation);
|
|
486
|
+
const rows = buildRelationTableRows(related, columnConfigs, result.items, this.basePath, (record) => recordSummaryLabel(related, record, titleAttribute));
|
|
487
|
+
return ctx.response.json({
|
|
488
|
+
items: result.items,
|
|
489
|
+
rows,
|
|
490
|
+
total: result.total,
|
|
491
|
+
page: result.page,
|
|
492
|
+
perPage: result.perPage,
|
|
493
|
+
pageCount: result.pageCount,
|
|
494
|
+
columns,
|
|
495
|
+
listHeaders,
|
|
496
|
+
filters: normalized.filters ?? [],
|
|
497
|
+
sort: normalized.sort ?? null,
|
|
498
|
+
direction: normalized.direction ?? null,
|
|
499
|
+
search: normalized.search ?? '',
|
|
500
|
+
});
|
|
501
|
+
}
|
|
434
502
|
async relationQuickCreate(ctx) {
|
|
435
503
|
const meta = this.requireResource(ctx);
|
|
436
504
|
const authResult = await this.ensureResourceAction(ctx, meta, 'create', undefined, true);
|
|
@@ -970,7 +1038,16 @@ export class AdminController {
|
|
|
970
1038
|
const titleAttribute = relationTitleAttribute(field.relation);
|
|
971
1039
|
const widget = field.relation.widget ?? 'combobox';
|
|
972
1040
|
let initialItems = [];
|
|
973
|
-
|
|
1041
|
+
const skipPreload = field.relation.kind === 'hasMany' &&
|
|
1042
|
+
widget === 'table' &&
|
|
1043
|
+
record?.id != null &&
|
|
1044
|
+
!!field.relation.foreignKey &&
|
|
1045
|
+
relationUsesListTable(field.relation);
|
|
1046
|
+
if (skipPreload) {
|
|
1047
|
+
// RelationTable loads linked rows via GET relation-table (search/page/filters).
|
|
1048
|
+
initialItems = [];
|
|
1049
|
+
}
|
|
1050
|
+
else if (field.relation.kind === 'hasMany' && record?.id != null && field.relation.foreignKey) {
|
|
974
1051
|
initialItems = await this.panel.adapter.search(related, {
|
|
975
1052
|
titleAttribute,
|
|
976
1053
|
limit: field.relation.preloadLimit ?? 100,
|