feat: Migrazione della struttura dei model JS da Coloquent a Spraypaint
Il commit riguarda la rimozione di Coloquent e l'adozione del framework Spraypaint per la struttura dei model. Contiene variazioni nell'implementazione dei metodi associati ai modelli e una modifica delle chiamate per l'ottenimento dei dati. Inoltre, le modifiche aiuteranno a potenziare le prestazioni delle applicazioni.
This commit is contained in:
parent
b448da234e
commit
71b03c14b6
|
@ -11,3 +11,7 @@ rules:
|
||||||
- error
|
- error
|
||||||
- except-parens
|
- except-parens
|
||||||
no-plusplus: off
|
no-plusplus: off
|
||||||
|
new-cap:
|
||||||
|
- error
|
||||||
|
- capIsNewExceptions: ['Model', 'Attr', 'HasMany', 'HasOne', 'BelongsTo', 'Link', 'Stream']
|
||||||
|
properties: false
|
||||||
|
|
|
@ -18,11 +18,10 @@
|
||||||
"@maicol07/material-web-additions": "^1.4.4",
|
"@maicol07/material-web-additions": "^1.4.4",
|
||||||
"@material/mwc-snackbar": "^0.27.0",
|
"@material/mwc-snackbar": "^0.27.0",
|
||||||
"@material/mwc-top-app-bar": "^0.27.0",
|
"@material/mwc-top-app-bar": "^0.27.0",
|
||||||
"@material/web": "1.0.1",
|
"@material/web": "^1.0.1",
|
||||||
"@mdi/js": "^7.3.67",
|
"@mdi/js": "^7.3.67",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"collect.js": "^4.36.1",
|
"collect.js": "^4.36.1",
|
||||||
"coloquent": "npm:@maicol07/coloquent@3.0.1-beta",
|
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"include-media": "^2.0.0",
|
"include-media": "^2.0.0",
|
||||||
"lit": "^3.1.0",
|
"lit": "^3.1.0",
|
||||||
|
@ -32,6 +31,7 @@
|
||||||
"postcss-scss": "^4.0.9",
|
"postcss-scss": "^4.0.9",
|
||||||
"prntr": "^2.0.18",
|
"prntr": "^2.0.18",
|
||||||
"readable-http-codes": "^1.1.1",
|
"readable-http-codes": "^1.1.1",
|
||||||
|
"spraypaint": "^0.10.24",
|
||||||
"ts-pattern": "^5.0.6",
|
"ts-pattern": "^5.0.6",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript-cookie": "^1.0.6"
|
"typescript-cookie": "^1.0.6"
|
||||||
|
|
|
@ -14,7 +14,7 @@ import DataTable, {DataTableAttributes} from '@osm/Components/DataTable/DataTabl
|
||||||
import DataTableColumn, {DataTableColumnAttributes} from '@osm/Components/DataTable/DataTableColumn';
|
import DataTableColumn, {DataTableColumnAttributes} from '@osm/Components/DataTable/DataTableColumn';
|
||||||
import RecordsTableColumn from '@osm/Components/DataTable/RecordsTableColumn';
|
import RecordsTableColumn from '@osm/Components/DataTable/RecordsTableColumn';
|
||||||
import MdIcon from '@osm/Components/MdIcon';
|
import MdIcon from '@osm/Components/MdIcon';
|
||||||
import Model from '@osm/Models/Model';
|
import Model from '@osm/Models/Record';
|
||||||
import {isVnode} from '@osm/utils/misc';
|
import {isVnode} from '@osm/utils/misc';
|
||||||
import collect, {Collection} from 'collect.js';
|
import collect, {Collection} from 'collect.js';
|
||||||
import {
|
import {
|
||||||
|
@ -28,7 +28,7 @@ export interface RecordsTableColumnAttributes extends DataTableColumnAttributes
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecordsTableAttributes<M extends Model<any, any>> extends DataTableAttributes {
|
export interface RecordsTableAttributes<M extends Model> extends DataTableAttributes {
|
||||||
cols: Collection<Children> | Collection<RecordsTableColumnAttributes> | Collection<Children | RecordsTableColumnAttributes>;
|
cols: Collection<Children> | Collection<RecordsTableColumnAttributes> | Collection<Children | RecordsTableColumnAttributes>;
|
||||||
records: Map<string, M>;
|
records: Map<string, M>;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
@ -42,7 +42,7 @@ export interface RecordsTableAttributes<M extends Model<any, any>> extends DataT
|
||||||
valueModifier?(value: any, attribute: string, record: M): any;
|
valueModifier?(value: any, attribute: string, record: M): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RecordsTable<M extends Model<any, any>, A extends RecordsTableAttributes<M> = RecordsTableAttributes<M>> extends DataTable<A> {
|
export default class RecordsTable<M extends Model, A extends RecordsTableAttributes<M> = RecordsTableAttributes<M>> extends DataTable<A> {
|
||||||
element!: MdDataTable;
|
element!: MdDataTable;
|
||||||
selectedRecordsIds: string[] = [];
|
selectedRecordsIds: string[] = [];
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ export default class RecordsTable<M extends Model<any, any>, A extends RecordsTa
|
||||||
cells.put('actions', this.tableRowActions(vnode, record).values<Children>().all());
|
cells.put('actions', this.tableRowActions(vnode, record).values<Children>().all());
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.set(record.getId()!, cells);
|
rows.set(record.id!, cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
|
@ -238,7 +238,7 @@ export default class RecordsTable<M extends Model<any, any>, A extends RecordsTa
|
||||||
|
|
||||||
protected onDeleteRecordButtonClicked(vnode: Vnode<A>, record: M, event: MouseEvent) {
|
protected onDeleteRecordButtonClicked(vnode: Vnode<A>, record: M, event: MouseEvent) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
vnode.attrs.onDeleteRecordButtonClick?.(record.getId()!, event);
|
vnode.attrs.onDeleteRecordButtonClick?.(record.id!, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDeleteSelectedRecordsButtonClicked(vnode: Vnode<A>, event: MouseEvent) {
|
protected onDeleteSelectedRecordsButtonClicked(vnode: Vnode<A>, event: MouseEvent) {
|
||||||
|
@ -247,9 +247,10 @@ export default class RecordsTable<M extends Model<any, any>, A extends RecordsTa
|
||||||
|
|
||||||
protected getModelValue(record: M, attribute: string, vnode: Vnode<A>): unknown {
|
protected getModelValue(record: M, attribute: string, vnode: Vnode<A>): unknown {
|
||||||
// Check if is a relation
|
// Check if is a relation
|
||||||
let value: unknown = record.getAttribute(attribute);
|
// @ts-expect-error
|
||||||
|
let value: unknown = record[attribute];
|
||||||
if (attribute === 'id') {
|
if (attribute === 'id') {
|
||||||
value = record.getId();
|
value = record.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vnode.attrs.valueModifier?.(value, attribute, record) ?? value;
|
return vnode.attrs.valueModifier?.(value, attribute, record) ?? value;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {mdiFloppy} from '@mdi/js';
|
import {mdiFloppy} from '@mdi/js';
|
||||||
import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog';
|
import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog';
|
||||||
import MdIcon from '@osm/Components/MdIcon';
|
import MdIcon from '@osm/Components/MdIcon';
|
||||||
import Model from '@osm/Models/Model';
|
import Model from '@osm/Models/Record';
|
||||||
import {
|
import {
|
||||||
VnodeCollection,
|
VnodeCollection,
|
||||||
VnodeCollectionItem
|
VnodeCollectionItem
|
||||||
|
@ -13,7 +13,6 @@ import {
|
||||||
showSnackbar
|
showSnackbar
|
||||||
} from '@osm/utils/misc';
|
} from '@osm/utils/misc';
|
||||||
import collect, {Collection} from 'collect.js';
|
import collect, {Collection} from 'collect.js';
|
||||||
import {SaveResponse} from 'coloquent';
|
|
||||||
import {
|
import {
|
||||||
Children,
|
Children,
|
||||||
Vnode,
|
Vnode,
|
||||||
|
@ -23,7 +22,7 @@ import Stream from 'mithril/stream';
|
||||||
import {Form} from 'mithril-utilities';
|
import {Form} from 'mithril-utilities';
|
||||||
import {Class} from 'type-fest';
|
import {Class} from 'type-fest';
|
||||||
|
|
||||||
export default abstract class AddEditRecordDialog<M extends Model<any, any>> extends RecordDialog<M> {
|
export default abstract class AddEditRecordDialog<M extends Model> extends RecordDialog<M> {
|
||||||
// eslint-disable-next-line unicorn/no-null
|
// eslint-disable-next-line unicorn/no-null
|
||||||
protected formElement: HTMLFormElement | null = null;
|
protected formElement: HTMLFormElement | null = null;
|
||||||
protected abstract formState: Map<string, Stream<any>>;
|
protected abstract formState: Map<string, Stream<any>>;
|
||||||
|
@ -47,12 +46,13 @@ export default abstract class AddEditRecordDialog<M extends Model<any, any>> ext
|
||||||
super.oncreate(vnode);
|
super.oncreate(vnode);
|
||||||
|
|
||||||
this.formElement = this.element.querySelector('form');
|
this.formElement = this.element.querySelector('form');
|
||||||
this.element.querySelector(`#saveBtn${this.formId}`)?.setAttribute('form', this.formId!);
|
this.element.querySelector(`#saveBtn${this.formId!}`)?.setAttribute('form', this.formId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
fillForm() {
|
fillForm() {
|
||||||
for (const [key, value] of this.formState) {
|
for (const [key, value] of this.formState) {
|
||||||
value(this.record.getAttribute(key) ?? value());
|
// @ts-ignore
|
||||||
|
value(this.record[key] ?? value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,27 +126,25 @@ export default abstract class AddEditRecordDialog<M extends Model<any, any>> ext
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<boolean> {
|
async save(): Promise<boolean> {
|
||||||
this.record.setAttributes(this.modelAttributesFromFormState);
|
this.record.assignAttributes(this.modelAttributesFromFormState);
|
||||||
try {
|
try {
|
||||||
const response = await this.record.save();
|
const result = await this.record.save();
|
||||||
this.afterSave(response);
|
this.afterSave(result);
|
||||||
return response.getModelId() !== undefined;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.onSaveError(error as JSONAPI.RequestError);
|
this.onSaveError(error as JSONAPI.RequestError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterSave(response: SaveResponse<M>): void {
|
afterSave(result: boolean): void {
|
||||||
const responseModel = response.getModel() as M;
|
if (result) {
|
||||||
if (responseModel !== undefined) {
|
|
||||||
this.record = responseModel;
|
|
||||||
void showSnackbar(__('Record salvato con successo'));
|
void showSnackbar(__('Record salvato con successo'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSaveError(error: JSONAPI.RequestError): void {
|
onSaveError(error: JSONAPI.RequestError): void {
|
||||||
const message = error.response.data.message;
|
const {message} = error.response.data;
|
||||||
void showSnackbar(message, false);
|
void showSnackbar(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {mdiDelete} from '@mdi/js';
|
import {mdiDelete} from '@mdi/js';
|
||||||
import MdIcon from '@osm/Components/MdIcon';
|
import MdIcon from '@osm/Components/MdIcon';
|
||||||
import Model from '@osm/Models/Model';
|
import Model from '@osm/Models/Record';
|
||||||
import {VnodeCollection} from '@osm/typings/jsx';
|
import {VnodeCollection} from '@osm/typings/jsx';
|
||||||
import {showSnackbar} from '@osm/utils/misc';
|
import {showSnackbar} from '@osm/utils/misc';
|
||||||
import collect from 'collect.js';
|
import collect from 'collect.js';
|
||||||
|
@ -12,11 +12,11 @@ import {RequestError} from 'mithril-utilities';
|
||||||
|
|
||||||
import RecordDialog, {RecordDialogAttributes} from './RecordDialog';
|
import RecordDialog, {RecordDialogAttributes} from './RecordDialog';
|
||||||
|
|
||||||
export interface DeleteRecordDialogAttributes<M extends Model<any, any>> extends RecordDialogAttributes<M> {
|
export interface DeleteRecordDialogAttributes<M extends Model> extends RecordDialogAttributes<M> {
|
||||||
records: M | M[];
|
records: M | M[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DeleteRecordDialog<M extends Model<any, any>, A extends DeleteRecordDialogAttributes<M> = DeleteRecordDialogAttributes<M>> extends RecordDialog<M, A> {
|
export default class DeleteRecordDialog<M extends Model, A extends DeleteRecordDialogAttributes<M> = DeleteRecordDialogAttributes<M>> extends RecordDialog<M, A> {
|
||||||
records!: M[];
|
records!: M[];
|
||||||
|
|
||||||
oninit(vnode: Vnode<A, this>) {
|
oninit(vnode: Vnode<A, this>) {
|
||||||
|
@ -32,7 +32,7 @@ export default class DeleteRecordDialog<M extends Model<any, any>, A extends Del
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>{text}</p>
|
<p>{text}</p>
|
||||||
<ul>{this.records.map((record) => <li key={record.getId()}>{this.recordSummary(record, vnode)}</li>)}</ul>
|
<ul>{this.records.map((record) => <li key={record.id}>{this.recordSummary(record, vnode)}</li>)}</ul>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export default class DeleteRecordDialog<M extends Model<any, any>, A extends Del
|
||||||
}
|
}
|
||||||
|
|
||||||
recordSummary(record: M, vnode: Vnode<A, this>): Children {
|
recordSummary(record: M, vnode: Vnode<A, this>): Children {
|
||||||
return __('ID: :recordId', {recordId: record.getId()!});
|
return __('ID: :recordId', {recordId: record.id!});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onConfirmButtonClicked() {
|
async onConfirmButtonClicked() {
|
||||||
|
@ -74,12 +74,12 @@ export default class DeleteRecordDialog<M extends Model<any, any>, A extends Del
|
||||||
|
|
||||||
async deleteRecord() {
|
async deleteRecord() {
|
||||||
try {
|
try {
|
||||||
const promises = this.records.map((record) => record.delete());
|
const promises = this.records.map((record) => record.destroy());
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
// TODO: Better way for pluralization in i18n
|
// TODO: Better way for pluralization in i18n
|
||||||
void showSnackbar(this.records.length > 1 ? __('Record eliminati!') : __('Record eliminato!'));
|
void showSnackbar(this.records.length > 1 ? __('Record eliminati!') : __('Record eliminato!'));
|
||||||
this.close('deleted');
|
void this.close('deleted');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
void showSnackbar(__('Errore durante l\'eliminazione del record! :error', {error: (error as RequestError<{message: string}>).response.message}), false);
|
void showSnackbar(__('Errore durante l\'eliminazione del record! :error', {error: (error as RequestError<{message: string}>).response.message}), false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import '@maicol07/material-web-additions/layout-grid/layout-grid.js';
|
||||||
import '@material/web/button/text-button.js';
|
import '@material/web/button/text-button.js';
|
||||||
|
|
||||||
import Dialog, {DialogAttributes} from '@osm/Components/Dialogs/Dialog';
|
import Dialog, {DialogAttributes} from '@osm/Components/Dialogs/Dialog';
|
||||||
import Model from '@osm/Models/Model';
|
import Model from '@osm/Models/Record';
|
||||||
import {Vnode} from 'mithril';
|
import {Vnode} from 'mithril';
|
||||||
|
|
||||||
export interface RecordDialogAttributes<M extends Model<any, any>> extends DialogAttributes {
|
export interface RecordDialogAttributes<M extends Model<any, any>> extends DialogAttributes {
|
||||||
|
|
|
@ -3,27 +3,27 @@ import '@material/web/button/outlined-button.js';
|
||||||
import {mdiChevronLeft} from '@mdi/js';
|
import {mdiChevronLeft} from '@mdi/js';
|
||||||
import MdIcon from '@osm/Components/MdIcon';
|
import MdIcon from '@osm/Components/MdIcon';
|
||||||
import Page, {PageAttributes} from '@osm/Components/Page';
|
import Page, {PageAttributes} from '@osm/Components/Page';
|
||||||
import Model from '@osm/Models/Model';
|
import Model from '@osm/Models/Record';
|
||||||
import {showSnackbar} from '@osm/utils/misc';
|
import {showSnackbar} from '@osm/utils/misc';
|
||||||
import {Builder} from 'coloquent';
|
|
||||||
import {
|
import {
|
||||||
Children,
|
Children,
|
||||||
Vnode
|
Vnode
|
||||||
} from 'mithril';
|
} from 'mithril';
|
||||||
|
import {Scope} from 'spraypaint';
|
||||||
import {Class} from 'type-fest';
|
import {Class} from 'type-fest';
|
||||||
|
|
||||||
export interface RecordPageAttributes<M extends Model<any, any>> extends PageAttributes {
|
export interface RecordPageAttributes<M extends Model> extends PageAttributes {
|
||||||
record: M;
|
record: M;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default abstract class RecordPage<M extends Model<any, any>, A extends RecordPageAttributes<M> = RecordPageAttributes<M>> extends Page<A> {
|
export default abstract class RecordPage<M extends Model, A extends RecordPageAttributes<M> = RecordPageAttributes<M>> extends Page<A> {
|
||||||
abstract recordType: Class<M> & typeof Model<any, any>;
|
abstract recordType: Class<M> & typeof Model;
|
||||||
record?: M;
|
record?: M;
|
||||||
|
|
||||||
async oninit(vnode: Vnode<A, this>) {
|
async oninit(vnode: Vnode<A, this>) {
|
||||||
super.oninit(vnode);
|
super.oninit(vnode);
|
||||||
const {id: recordId} = route().params as {id: number | string};
|
const {id: recordId} = route().params as {id: number | string};
|
||||||
if (recordId !== this.record?.getId()) {
|
if (recordId !== this.record?.id) {
|
||||||
await this.loadRecord(recordId);
|
await this.loadRecord(recordId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,24 +32,23 @@ export default abstract class RecordPage<M extends Model<any, any>, A extends Re
|
||||||
if (recordId && recordId !== 'new' && !this.record) {
|
if (recordId && recordId !== 'new' && !this.record) {
|
||||||
try {
|
try {
|
||||||
const response = await this.modelQuery().find(recordId);
|
const response = await this.modelQuery().find(recordId);
|
||||||
this.record = response.getData() || undefined;
|
this.record = response.data || undefined;
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error(e);
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
void showSnackbar(__('Errore durante il caricamento del record'));
|
void showSnackbar(__('Errore durante il caricamento del record'));
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.record) {
|
if (!this.record) {
|
||||||
// @ts-expect-error — This won't be abstract when implemented
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
this.record = new this.recordType();
|
this.record = new this.recordType();
|
||||||
}
|
}
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
modelQuery(): Builder<M> {
|
modelQuery(): Scope<M> {
|
||||||
return this.recordType.query();
|
return this.recordType as unknown as Scope<M>;
|
||||||
}
|
}
|
||||||
|
|
||||||
contents(vnode: Vnode<A>): Children {
|
contents(vnode: Vnode<A>): Children {
|
||||||
|
|
|
@ -21,9 +21,8 @@ import DeleteRecordDialog, {DeleteRecordDialogAttributes} from '@osm/Components/
|
||||||
import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog';
|
import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog';
|
||||||
import MdIcon from '@osm/Components/MdIcon';
|
import MdIcon from '@osm/Components/MdIcon';
|
||||||
import Page, {PageAttributes} from '@osm/Components/Page';
|
import Page, {PageAttributes} from '@osm/Components/Page';
|
||||||
import Model from '@osm/Models/Model';
|
import Record from '@osm/Models/Record';
|
||||||
import collect, {type Collection} from 'collect.js';
|
import collect, {type Collection} from 'collect.js';
|
||||||
import {SortDirection} from 'coloquent';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import type {
|
import type {
|
||||||
Children,
|
Children,
|
||||||
|
@ -31,38 +30,25 @@ import type {
|
||||||
VnodeDOM
|
VnodeDOM
|
||||||
} from 'mithril';
|
} from 'mithril';
|
||||||
import Stream from 'mithril/stream';
|
import Stream from 'mithril/stream';
|
||||||
|
import {Scope} from 'spraypaint';
|
||||||
|
import {SortDir} from 'spraypaint/lib-esm/scope';
|
||||||
import {match} from 'ts-pattern';
|
import {match} from 'ts-pattern';
|
||||||
import {Match} from 'ts-pattern/dist/types/Match';
|
import {Match} from 'ts-pattern/dist/types/Match';
|
||||||
import type {Class} from 'type-fest';
|
import type {Class} from 'type-fest';
|
||||||
|
|
||||||
export interface Meta {
|
type RecordDialogVnode<M extends Record, D extends RecordDialog<M>> = Vnode<RecordDialogAttributes<M>, D>;
|
||||||
current_page: number;
|
type DeleteRecordDialogVnode<M extends Record, D extends DeleteRecordDialog<M>> = Vnode<DeleteRecordDialogAttributes<M>, D>;
|
||||||
from: number;
|
|
||||||
last_page: number;
|
|
||||||
path: string;
|
|
||||||
per_page: number;
|
|
||||||
to: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JSONAPIResponse {
|
|
||||||
meta: Meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type RecordDialogVnode<M extends Model<any, any>, D extends RecordDialog<M>> = Vnode<RecordDialogAttributes<M>, D>;
|
|
||||||
type DeleteRecordDialogVnode<M extends Model<any, any>, D extends DeleteRecordDialog<M>> = Vnode<DeleteRecordDialogAttributes<M>, D>;
|
|
||||||
|
|
||||||
// noinspection JSUnusedLocalSymbols
|
// noinspection JSUnusedLocalSymbols
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
export default abstract class RecordsPage<
|
export default abstract class RecordsPage<
|
||||||
M extends Model<any, any>,
|
M extends Record,
|
||||||
D extends AddEditRecordDialog<M> = AddEditRecordDialog<M>,
|
D extends AddEditRecordDialog<M> = AddEditRecordDialog<M>,
|
||||||
DRD extends DeleteRecordDialog<M> = DeleteRecordDialog<M>
|
DRD extends DeleteRecordDialog<M> = DeleteRecordDialog<M>
|
||||||
> extends Page {
|
> extends Page {
|
||||||
abstract modelType: Class<M> & typeof Model<any, any>;
|
abstract modelType: Class<M> & typeof Record;
|
||||||
recordDialogType?: Class<D>;
|
recordDialogType?: Class<D>;
|
||||||
deleteRecordDialogType?: Class<DRD>;
|
deleteRecordDialogType?: Class<DRD>;
|
||||||
|
|
||||||
|
@ -86,15 +72,13 @@ export default abstract class RecordsPage<
|
||||||
protected lastRowOfPage = this.totalRecords;
|
protected lastRowOfPage = this.totalRecords;
|
||||||
|
|
||||||
protected filters: Map<string, string> = new Map();
|
protected filters: Map<string, string> = new Map();
|
||||||
protected sort: Map<string, SortDirection> = new Map([['id', SortDirection.ASC]]);
|
protected sort: Map<string, SortDir> = new Map();
|
||||||
protected relatedFilters: Map<string, string> = new Map();
|
protected relatedFilters: Map<string, string> = new Map();
|
||||||
private listenedFilterColumns: string[] = [];
|
private listenedFilterColumns: string[] = [];
|
||||||
private listenedSortedColumns: string[] = [];
|
private listenedSortedColumns: string[] = [];
|
||||||
|
|
||||||
oninit(vnode: Vnode<PageAttributes, this>) {
|
oninit(vnode: Vnode<PageAttributes, this>) {
|
||||||
super.oninit(vnode);
|
super.oninit(vnode);
|
||||||
// @ts-ignore
|
|
||||||
this.modelType.pageSize = this.currentPageSize;
|
|
||||||
// Redraw on a first load to call onbeforeupdate
|
// Redraw on a first load to call onbeforeupdate
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
@ -111,24 +95,21 @@ export default abstract class RecordsPage<
|
||||||
async loadRecords() {
|
async loadRecords() {
|
||||||
this.isTableLoading = true;
|
this.isTableLoading = true;
|
||||||
|
|
||||||
let query = this.modelQuery();
|
const query = this.modelQuery();
|
||||||
|
|
||||||
// Fix Restify when filtering relations
|
const response = await query.page(this.currentPage).all();
|
||||||
query = query.option('related', query.getQuery().getInclude().join(','));
|
const rawResponse = response.raw;
|
||||||
|
this.lastPage = rawResponse.meta?.last_page as number;
|
||||||
const response = await query.get(this.currentPage);
|
this.firstRowOfPage = rawResponse.meta?.from as number;
|
||||||
const rawResponse = response.getHttpClientResponse().getData() as JSONAPIResponse;
|
this.lastRowOfPage = rawResponse.meta?.to as number;
|
||||||
this.lastPage = rawResponse.meta.last_page;
|
this.currentPageSize = rawResponse.meta?.per_page as number;
|
||||||
this.firstRowOfPage = rawResponse.meta.from;
|
this.totalRecords = rawResponse.meta?.total as number;
|
||||||
this.lastRowOfPage = rawResponse.meta.to;
|
const {data} = response;
|
||||||
this.currentPageSize = rawResponse.meta.per_page;
|
|
||||||
this.totalRecords = rawResponse.meta.total;
|
|
||||||
const data = response.getData();
|
|
||||||
|
|
||||||
this.records.clear();
|
this.records.clear();
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
for (const record of data) {
|
for (const record of data) {
|
||||||
this.records.set(record.getId()!, record);
|
this.records.set(record.id, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,31 +125,29 @@ export default abstract class RecordsPage<
|
||||||
*/
|
*/
|
||||||
private static convertToSnakeCase(string_: string, trim = false, removeSpecials = false, underscoredNumbers = false) {
|
private static convertToSnakeCase(string_: string, trim = false, removeSpecials = false, underscoredNumbers = false) {
|
||||||
return string_.replace(removeSpecials ? /[^\w ]/g : '', '')
|
return string_.replace(removeSpecials ? /[^\w ]/g : '', '')
|
||||||
.replace(/([ _]+)/g, '_')
|
.replace(/[ _]+/g, '_')
|
||||||
.replace(trim ? /(^_|_$)/gm : '', '')
|
.replace(trim ? /(^_|_$)/gm : '', '')
|
||||||
.replace(underscoredNumbers ? /([^\dA-Z_])([^_a-z])/g : /([^\dA-Z_])([^\d_a-z])/g, (m, preUpper, upper) => `${preUpper}_${upper}`)
|
.replace(underscoredNumbers ? /([^\dA-Z_])([^_a-z])/g : /([^\dA-Z_])([^\d_a-z])/g, (m, preUpper: string, upper: string) => `${preUpper}_${upper}`)
|
||||||
.replace(underscoredNumbers ? /([^\d_]\d|\d[^\d_])/g : '', (m, index) => (index ? index.split('').join('_') : ''))
|
.replace(underscoredNumbers ? /([^\d_]\d|\d[^\d_])/g : '', (m, index: string) => (index ? [...index].join('_') : ''))
|
||||||
.replace(/([A-Z])([A-Z])([^\dA-Z_])/g, (m, previousUpper, upper, lower) => `${previousUpper}_${upper}${lower}`)
|
.replace(/([A-Z])([A-Z])([^\dA-Z_])/g, (m, previousUpper: string, upper: string, lower: string) => `${previousUpper}_${upper}${lower}`)
|
||||||
.replaceAll('_.', '.') // remove redundant underscores
|
.replaceAll('_.', '.') // Remove redundant underscores
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
modelQuery() {
|
modelQuery() {
|
||||||
// @ts-ignore
|
let query: Scope<M> = this.modelType.per(this.currentPageSize);
|
||||||
let query = this.modelType.query<M>();
|
|
||||||
|
|
||||||
for (const [attribute, value] of this.filters) {
|
for (const [attribute, value] of this.filters) {
|
||||||
// Query = query.where(attribute, value); TODO: Revert when Restify uses JSONAPI syntax
|
query = query.where({[RecordsPage.convertToSnakeCase(attribute)]: value});
|
||||||
query = query.option(RecordsPage.convertToSnakeCase(attribute), value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [relation, value] of this.relatedFilters) {
|
for (const [relation, value] of this.relatedFilters) {
|
||||||
query = query.option('related', relation)
|
query = query.where({related: relation, search: value}); // TODO: Check
|
||||||
.option('search', value);
|
// .where('search', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [attribute, value] of this.sort) {
|
for (const [attribute, value] of this.sort) {
|
||||||
query = query.orderBy(RecordsPage.convertToSnakeCase(attribute), value);
|
query = query.order({[RecordsPage.convertToSnakeCase(attribute)]: value});
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@ -244,7 +223,7 @@ export default abstract class RecordsPage<
|
||||||
|
|
||||||
updateRecord(model: M) {
|
updateRecord(model: M) {
|
||||||
if (this.recordPageRouteName) {
|
if (this.recordPageRouteName) {
|
||||||
router.visit(route(this.recordPageRouteName, {id: model.getId()!}));
|
router.visit(route(this.recordPageRouteName, {id: model.id}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,8 +237,8 @@ export default abstract class RecordsPage<
|
||||||
for (const [key, state] of this.recordDialogsStates) {
|
for (const [key, state] of this.recordDialogsStates) {
|
||||||
// noinspection LocalVariableNamingConventionJS
|
// noinspection LocalVariableNamingConventionJS
|
||||||
const RD = this.recordDialogType!;
|
const RD = this.recordDialogType!;
|
||||||
const record = key instanceof Model ? key : this.records.get(key);
|
const record = key instanceof Record ? key : this.records.get(key);
|
||||||
const vnodeKey = record?.getId() ?? (key as string);
|
const vnodeKey = record?.id ?? (key as string);
|
||||||
collection.put(vnodeKey, <RD key={vnodeKey} record={record} open={state}/>);
|
collection.put(vnodeKey, <RD key={vnodeKey} record={record} open={state}/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,14 +282,16 @@ export default abstract class RecordsPage<
|
||||||
onTablePageChange(event: CustomEvent<PaginateDetail>) {
|
onTablePageChange(event: CustomEvent<PaginateDetail>) {
|
||||||
const {pageSize, action} = event.detail;
|
const {pageSize, action} = event.detail;
|
||||||
this.currentPageSize = pageSize;
|
this.currentPageSize = pageSize;
|
||||||
const {currentPage} = this;
|
const {currentPage, lastPage} = this;
|
||||||
match(action)
|
match(action)
|
||||||
.with('first', () => (this.currentPage = 1))
|
.with('first', () => (this.currentPage = 1))
|
||||||
.with('previous', () => (this.currentPage--))
|
.with('previous', () => (this.currentPage--))
|
||||||
.with('next', () => (this.currentPage++))
|
.with('next', () => (this.currentPage++))
|
||||||
.with('last', () => (this.currentPage = this.lastPage))
|
.with('last', () => (this.currentPage = lastPage))
|
||||||
.with('current', () => {})
|
.with('current', () => {})
|
||||||
.run();
|
.run();
|
||||||
|
// We need to check if the page has changed
|
||||||
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
||||||
if (currentPage !== this.currentPage) {
|
if (currentPage !== this.currentPage) {
|
||||||
this.refreshRecords = true;
|
this.refreshRecords = true;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
@ -356,13 +337,13 @@ export default abstract class RecordsPage<
|
||||||
const {column, isDescending} = (event as CustomEvent<SortButtonClickedEventDetail>).detail;
|
const {column, isDescending} = (event as CustomEvent<SortButtonClickedEventDetail>).detail;
|
||||||
const modelAttribute = column.dataset.sortAttribute ?? column.dataset.modelAttribute!;
|
const modelAttribute = column.dataset.sortAttribute ?? column.dataset.modelAttribute!;
|
||||||
this.sort.clear();
|
this.sort.clear();
|
||||||
this.sort.set(modelAttribute, isDescending ? SortDirection.DESC : SortDirection.ASC);
|
this.sort.set(modelAttribute, isDescending ? 'desc' : 'asc');
|
||||||
this.refreshRecords = true;
|
this.refreshRecords = true;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
openDeleteRecordsDialog(records: M | M[]) {
|
openDeleteRecordsDialog(records: M | M[]) {
|
||||||
const key = records instanceof Model ? records.getId()! : records.map((r) => r.getId()).join(',');
|
const key = records instanceof Record ? records.id! : records.map((r) => r.id).join(',');
|
||||||
let state = this.deleteRecordsDialogStates.get(key);
|
let state = this.deleteRecordsDialogStates.get(key);
|
||||||
|
|
||||||
if (!state) {
|
if (!state) {
|
||||||
|
@ -386,7 +367,7 @@ export default abstract class RecordsPage<
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getRecordDialogState(record?: M, slug?: string) {
|
protected getRecordDialogState(record?: M, slug?: string) {
|
||||||
const key: string = slug ?? record?.getId() ?? '';
|
const key: string = slug ?? record?.id ?? '';
|
||||||
|
|
||||||
if (!this.recordDialogsStates.has(key)) {
|
if (!this.recordDialogsStates.has(key)) {
|
||||||
const state = Stream<boolean>(false);
|
const state = Stream<boolean>(false);
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import type {
|
|
||||||
HttpClient,
|
|
||||||
HttpClientPromise
|
|
||||||
} from 'coloquent';
|
|
||||||
import {Request} from 'mithril-utilities';
|
|
||||||
|
|
||||||
import RequestHttpClientPromise from './RequestHttpClientPromise';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class RequestHttpClient
|
|
||||||
*
|
|
||||||
* NOTE: This class is not meant to be used directly, but only for Models.
|
|
||||||
* You should use the {@link Request} class instead.
|
|
||||||
*/
|
|
||||||
export default class RequestHttpClient implements HttpClient {
|
|
||||||
request: Request;
|
|
||||||
|
|
||||||
constructor(requestInstance?: Request) {
|
|
||||||
this.request = requestInstance ?? new Request({
|
|
||||||
background: true,
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/vnd.api+json',
|
|
||||||
'Content-Type': 'application/vnd.api+json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(url: string): HttpClientPromise {
|
|
||||||
this.request.options.method = 'delete';
|
|
||||||
this.request.options.url = url;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
get(url: string): HttpClientPromise {
|
|
||||||
this.request.options.method = 'get';
|
|
||||||
this.request.options.url = url;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
getImplementingClient() {
|
|
||||||
return this.request;
|
|
||||||
}
|
|
||||||
|
|
||||||
head(url: string): HttpClientPromise {
|
|
||||||
this.request.options.method = 'head';
|
|
||||||
this.request.options.url = url;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(url: string, data?: Record<string, any>): HttpClientPromise {
|
|
||||||
this.request.options.method = 'patch';
|
|
||||||
this.request.options.url = url;
|
|
||||||
this.request.options.body = data;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
post(url: string, data?: Record<string, any>): HttpClientPromise {
|
|
||||||
this.request.options.method = 'post';
|
|
||||||
this.request.options.url = url;
|
|
||||||
this.request.options.body = data;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
put(url: string, data?: Record<string, any>): HttpClientPromise {
|
|
||||||
this.request.options.method = 'put';
|
|
||||||
this.request.options.url = url;
|
|
||||||
this.request.options.body = data;
|
|
||||||
return new RequestHttpClientPromise(this.request.send());
|
|
||||||
}
|
|
||||||
|
|
||||||
setWithCredentials(withCredientials: boolean): void {
|
|
||||||
this.request.options.withCredentials = withCredientials;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import RequestHttpClientResponse from '@osm/Models/Http/RequestHttpClientResponse';
|
|
||||||
import type {
|
|
||||||
HttpClientPromise,
|
|
||||||
HttpClientResponse
|
|
||||||
} from 'coloquent';
|
|
||||||
import type {Thenable} from 'coloquent/dist/httpclient/Types';
|
|
||||||
|
|
||||||
export default class RequestHttpClientPromise implements HttpClientPromise {
|
|
||||||
constructor(private response: Promise<any>) {}
|
|
||||||
|
|
||||||
catch<U>(onRejected?: (error: any) => (Thenable<U> | U)): Promise<U> {
|
|
||||||
return this.response.catch(onRejected) as Promise<U>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line unicorn/no-thenable
|
|
||||||
then<U>(
|
|
||||||
onFulfilled?: (value: HttpClientResponse) => (Thenable<U> | U),
|
|
||||||
onRejected?: (error: any) => void | (Thenable<U> | U)
|
|
||||||
): Promise<U> {
|
|
||||||
const wrappedOnFulfilled = onFulfilled === undefined
|
|
||||||
? undefined
|
|
||||||
: ((responsePromise: any) => onFulfilled(new RequestHttpClientResponse(responsePromise)));
|
|
||||||
return this.response.then<U>(
|
|
||||||
wrappedOnFulfilled,
|
|
||||||
// @ts-ignore
|
|
||||||
onRejected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import type {HttpClientResponse} from 'coloquent';
|
|
||||||
|
|
||||||
export default class RequestHttpClientResponse implements HttpClientResponse {
|
|
||||||
constructor(private response: any) {}
|
|
||||||
|
|
||||||
getData(): any {
|
|
||||||
return this.response;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnderlying(): any {
|
|
||||||
return this.response;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
import RequestHttpClient from '@osm/Models/Http/RequestHttpClient';
|
|
||||||
import {
|
|
||||||
Model as BaseModel,
|
|
||||||
PaginationStrategy,
|
|
||||||
PluralResponse
|
|
||||||
} from 'coloquent';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import type {ValueOf} from 'type-fest';
|
|
||||||
|
|
||||||
export interface ModelAttributes {
|
|
||||||
id: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelRelations {
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base model for all models.
|
|
||||||
*/
|
|
||||||
export default abstract class Model<A extends ModelAttributes, R extends ModelRelations> extends BaseModel {
|
|
||||||
protected static paginationStrategy = PaginationStrategy.PageBased;
|
|
||||||
protected static jsonApiBaseUrl = '/api/restify';
|
|
||||||
getHttpClient() { return new RequestHttpClient(); }
|
|
||||||
|
|
||||||
static dates: Record<string, string> = {
|
|
||||||
createdAt: 'YYYY-MM-DDTHH:mm:ss.ssssssZ',
|
|
||||||
updatedAt: 'YYYY-MM-DDTHH:mm:ss.ssssssZ'
|
|
||||||
};
|
|
||||||
|
|
||||||
protected static get jsonApiType() {
|
|
||||||
return `${this.name.toLowerCase()}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the instances of the model. (Alias of {@link Model.get}).
|
|
||||||
*/
|
|
||||||
static all<M extends typeof Model<any, any> & {
|
|
||||||
new (): InstanceType<M>;
|
|
||||||
// @ts-ignore
|
|
||||||
}>(this: M): Promise<PluralResponse<InstanceType<M>>> {
|
|
||||||
// @ts-expect-error
|
|
||||||
return this.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set multiple attributes on the model.
|
|
||||||
*/
|
|
||||||
setAttributes(attributes: Partial<A> | Map<keyof A, ValueOf<A>>) {
|
|
||||||
// Record to map
|
|
||||||
if (!(attributes instanceof Map)) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
attributes = new Map(Object.entries(attributes) as [keyof A, ValueOf<A>][]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [attribute, value] of attributes) {
|
|
||||||
this.setAttribute(attribute, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttribute<AN extends keyof A = keyof A>(attributeName: AN) {
|
|
||||||
return super.getAttribute(attributeName as string) as ValueOf<A, AN>;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttributes() {
|
|
||||||
return super.getAttributes() as ModelAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getAttributeAsDate(attributeName: string) {
|
|
||||||
// @ts-ignore
|
|
||||||
let attribute: string | Date = (this.attributes as Map<string, string | null>).get(attributeName);
|
|
||||||
if (attribute && dayjs(attribute).isValid()) {
|
|
||||||
attribute = super.getAttributeAsDate(attributeName) as Date;
|
|
||||||
}
|
|
||||||
return attribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAttribute<AN extends keyof A = keyof A>(attributeName: AN, value: ValueOf<A, AN>) {
|
|
||||||
const date = dayjs(value as string | Date | undefined);
|
|
||||||
// @ts-expect-error
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
||||||
if (this.isDateAttribute(attributeName) && date.isValid()) {
|
|
||||||
const format = this.constructor.dates[attributeName as string];
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
value = (format === 'YYYY-MM-DDTHH:mm:ss.ssssssZ' ? date.toISOString() : date.format(format)) as ValueOf<A, AN>;
|
|
||||||
}
|
|
||||||
// @ts-expect-error — This is needed to parse the dates correctly.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
||||||
this.attributes.set(attributeName as string, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRelation<RN extends keyof R = keyof R>(relationName: RN) {
|
|
||||||
return super.getRelation(relationName as string) as ValueOf<R, RN>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model ID.
|
|
||||||
*/
|
|
||||||
getId() {
|
|
||||||
return this.getApiId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the model is new (not already saved).
|
|
||||||
*/
|
|
||||||
isNew() {
|
|
||||||
return this.getId() === undefined;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {Attr, Model, SpraypaintBase} from 'spraypaint';
|
||||||
|
|
||||||
|
@Model()
|
||||||
|
export default class Record extends SpraypaintBase {
|
||||||
|
static baseUrl = '';
|
||||||
|
static apiNamespace = '/api/restify';
|
||||||
|
static clientApplication = 'OpenSTAManager';
|
||||||
|
|
||||||
|
@Attr({persist: false}) createdAt!: string;
|
||||||
|
@Attr({persist: false}) updatedAt!: string;
|
||||||
|
|
||||||
|
isNew(): boolean {
|
||||||
|
return this.id === undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
import Model, {
|
import Record from '@osm/Models/Record';
|
||||||
ModelAttributes,
|
import {Attr, Model} from 'spraypaint';
|
||||||
ModelRelations
|
|
||||||
} from '@osm/Models/Model';
|
|
||||||
|
|
||||||
export interface UserAttributes extends ModelAttributes {
|
@Model()
|
||||||
username: string;
|
export default class User extends Record {
|
||||||
email: string;
|
static jsonapiType = 'users';
|
||||||
|
|
||||||
|
@Attr() username!: string;
|
||||||
|
@Attr() email!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRelations extends ModelRelations {
|
|
||||||
// Notifications: DatabaseNotifications
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class User extends Model<UserAttributes, UserRelations> {}
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ export default class UserRecord extends RecordPage<User> {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.backButton(vnode)}
|
{this.backButton(vnode)}
|
||||||
<h1>{this.record?.getAttribute('username')}</h1>
|
<h1>{this.record?.username}</h1>
|
||||||
<code>
|
<code>
|
||||||
{JSON.stringify(this.record?.getAttributes())}
|
{JSON.stringify(this.record?.attributes)}
|
||||||
</code>
|
</code>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,9 +35,9 @@ export default class UsersRecordDialog extends AddEditRecordDialog<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
if (this.record.isNew()) {
|
// if (this.record.isNew()) {
|
||||||
this.record.setAttribute('password', 'default');
|
// this.record.password = 'default';
|
||||||
}
|
// }
|
||||||
return super.save();
|
return super.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue