Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mobx-restful-table",
"version": "2.5.5",
"version": "2.6.0",
"license": "LGPL-3.0",
"author": "shiy2008@gmail.com",
"description": "A Pagination Table & Scroll List component suite for CRUD operation, which is based on MobX RESTful & React.",
Expand Down Expand Up @@ -36,32 +36,32 @@
"react-bootstrap": "^2.10.10",
"react-bootstrap-editor": "^2.1.1",
"regenerator-runtime": "^0.14.1",
"web-utility": "^4.6.2"
"web-utility": "^4.6.4"
},
"peerDependencies": {
"react": ">=16.8"
},
"devDependencies": {
"@octokit/openapi-types": "^26.0.0",
"@parcel/config-default": "~2.16.0",
"@parcel/packager-ts": "~2.16.0",
"@parcel/transformer-typescript-tsc": "~2.16.0",
"@parcel/transformer-typescript-types": "~2.16.0",
"@types/lodash": "^4.17.20",
"@types/node": "^22.18.11",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@octokit/openapi-types": "^27.0.0",
"@parcel/config-default": "~2.16.1",
"@parcel/packager-ts": "~2.16.1",
"@parcel/transformer-typescript-tsc": "~2.16.1",
"@parcel/transformer-typescript-types": "~2.16.1",
"@types/lodash": "^4.17.21",
"@types/node": "^22.19.1",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
"husky": "^9.1.7",
"idea-react": "^2.0.0-rc.13",
"koajax": "^3.1.2",
"lint-staged": "^16.2.5",
"mobx-github": "^0.6.0",
"parcel": "~2.16.0",
"lint-staged": "^16.2.7",
"mobx-github": "^0.6.2",
"parcel": "~2.16.1",
"prettier": "^3.6.2",
"prismjs": "^1.30.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"rimraf": "^6.0.1",
"rimraf": "^6.1.2",
"typedoc": "^0.28.14",
"typedoc-plugin-mdn-links": "^5.0.10",
"typescript": "~5.9.3"
Expand Down
1,523 changes: 685 additions & 838 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion preview/content.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GitRepository } from 'mobx-github';
import { GitRepository, RepositoryFilter } from 'mobx-github';
import { FC } from 'react';
import { Form, InputGroup } from 'react-bootstrap';

Expand All @@ -7,6 +7,7 @@ import {
BadgeBar,
BadgeInput,
Column,
Field,
FileModel,
FilePicker,
FilePreview,
Expand All @@ -30,6 +31,14 @@ class MyFileModel extends FileModel {}

const fileStore = new MyFileModel();

const filterFields: Field<RepositoryFilter>[] = [
{ key: 'full_name', renderLabel: 'Repository Name' },
{ key: 'homepage', renderLabel: 'Home Page' },
{ key: 'language', renderLabel: 'Programming Language' },
{ key: 'topics', renderLabel: 'Topic' },
{ key: 'description', renderLabel: 'Description' },
];

const columns: Column<GitRepository>[] = [
{
key: 'full_name',
Expand Down Expand Up @@ -211,6 +220,7 @@ export const Content: FC = () => (
hover
editable
deletable
filterFields={filterFields}
columns={columns}
store={repositoryStore}
translator={i18n}
Expand Down
2 changes: 1 addition & 1 deletion source/FileUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class FileUploader extends FormComponent<FileUploaderProps> {
super.componentDidMount();

const { store } = this.props;
debugger

store.files = this.value || [];
}

Expand Down
53 changes: 38 additions & 15 deletions source/RestForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import { ObservedComponent } from 'mobx-react-helper';
import { DataObject, Filter, IDType, ListModel } from 'mobx-restful';
import { FormEvent, Fragment, InputHTMLAttributes, ReactNode } from 'react';
import { Button, Form, FormGroupProps, FormProps, InputGroup } from 'react-bootstrap';
import { Button, ButtonProps, Form, FormGroupProps, FormProps, InputGroup } from 'react-bootstrap';
import { Editor, EditorProps } from 'react-bootstrap-editor';
import { formatDate, formToJSON, isEmpty } from 'web-utility';

Expand Down Expand Up @@ -46,12 +46,14 @@ export interface FieldBoxProps<D extends DataObject>
}

export interface RestFormProps<D extends DataObject, F extends Filter<D> = Filter<D>>
extends Pick<FormProps, 'className' | 'style'> {
extends Pick<FormProps, 'className' | 'style'>,
Pick<ButtonProps, 'size'> {
id?: IDType;
fields: Field<D>[];
store: ListModel<D, F>;
store?: ListModel<D, F>;
translator: TranslationModel<string, 'submit' | 'cancel'>;
onSubmit?: (data: D) => any;
onReset?: (data: Partial<D>) => any;
}

@observer
Expand Down Expand Up @@ -104,7 +106,7 @@ export class RestForm<
componentDidMount() {
const { id, store } = this.props;

if (id) store.getOne(id);
if (id) store?.getOne(id);
}

handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
Expand All @@ -118,16 +120,24 @@ export class RestForm<

if (valid) {
const { id, store, onSubmit } = this.props;
let data = formToJSON<D>(form);

const updated = await store.updateOne(formToJSON(form), id);
data = await store?.updateOne(data, id);

onSubmit?.(updated);
onSubmit?.(data);

store.clearCurrent();
store?.clearCurrent();
}
this.validated = false;
};

handleReset = ({ currentTarget }: FormEvent<HTMLFormElement>) => {
const { onReset, store } = this.props;

onReset?.(formToJSON(currentTarget));
store?.clearCurrent();
};

@computed
get fields(): Field<D>[] {
const { fields } = this.observedProps;
Expand Down Expand Up @@ -163,7 +173,7 @@ export class RestForm<
get fieldReady() {
const { id, store } = this.observedProps;

return !id || store.downloading < 1;
return !id || store?.downloading < 1;
}

renderFile =
Expand Down Expand Up @@ -239,6 +249,7 @@ export class RestForm<
{...props}
{...meta}
{...{ type, step, label }}
size={this.observedProps.size}
name={key.toString()}
defaultValue={RestForm.dateValueOf({ type, step }, value)}
/>
Expand All @@ -259,29 +270,41 @@ export class RestForm<

render() {
const { fields, readOnly, customValidation, validated } = this,
{ id, className = '', store, translator, ...props } = this.observedProps;
const { downloading, uploading, currentOne } = store,
{
id,
className = 'd-flex flex-column gap-3',
size,
store,
translator,
...props
} = this.props;
const { downloading, uploading, currentOne = {} as D } = store || {},
{ t } = translator;
const loading = downloading > 0 || uploading > 0;

return (
<Form
className={`d-flex flex-column gap-3 ${className}`}
{...props}
{...{ className, validated }}
noValidate={customValidation}
validated={validated}
onSubmit={this.handleSubmit}
onReset={() => store.clearCurrent()}
onReset={this.handleReset}
>
{fields.map(({ renderInput, ...meta }) => (
<Fragment key={meta.key?.toString()}>{renderInput?.(currentOne, meta)}</Fragment>
))}
{!readOnly && (
<footer className="d-flex gap-3">
<Button className="flex-fill" type="submit" disabled={loading}>
<Button className="flex-fill" size={size} type="submit" disabled={loading}>
{t('submit')}
</Button>
<Button className="flex-fill" type="reset" variant="danger" disabled={loading}>
<Button
className="flex-fill"
size={size}
type="reset"
variant="danger"
disabled={loading}
>
{t('cancel')}
</Button>
</footer>
Expand Down
4 changes: 2 additions & 2 deletions source/RestFormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isEmpty } from 'web-utility';
import { RestForm, RestFormProps } from './RestForm';

export const RestFormModal = observer(
<T extends DataObject>({ fields, store, translator }: RestFormProps<T>) => {
<T extends DataObject>({ fields, store, translator, ...props }: RestFormProps<T>) => {
const { indexKey, currentOne } = store;

const editing = !isEmpty(currentOne),
Expand All @@ -17,7 +17,7 @@ export const RestFormModal = observer(
<Modal.Header closeButton>{ID}</Modal.Header>

<Modal.Body>
<RestForm id={ID} {...{ fields, store, translator }} />
<RestForm id={ID} {...{ fields, store, translator, ...props }} />
</Modal.Body>
</Modal>
);
Expand Down
69 changes: 53 additions & 16 deletions source/RestTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { isEmpty } from 'web-utility';
import { BadgeBar } from './BadgeBar';
import { FilePreview } from './FilePreview';
import { Pager } from './Pager';
import { Field, RestFormProps } from './RestForm';
import { Field, RestForm, RestFormProps } from './RestForm';
import { RestFormModal } from './RestFormModal';

export interface Column<T extends DataObject> extends Omit<Field<T>, 'renderLabel'> {
Expand All @@ -27,9 +27,10 @@ type Translator<T extends DataObject> = RestFormProps<T>['translator'] &
'create' | 'view' | 'edit' | 'delete' | 'total_x_rows' | 'sure_to_delete_x'
>;
export interface RestTableProps<D extends DataObject, F extends Filter<D> = Filter<D>>
extends Omit<TableProps, 'onSubmit'>,
Omit<RestFormProps<D>, 'id' | 'fields' | 'translator'> {
extends Omit<TableProps, 'onSubmit' | 'onReset'>,
Omit<RestFormProps<D>, 'id' | 'size' | 'fields' | 'translator'> {
filter?: F;
filterFields?: Field<F>[];
editable?: boolean;
deletable?: boolean;
columns: Column<D>[];
Expand All @@ -51,6 +52,13 @@ export class RestTable<
store.getList(filter);
}

@computed
get fieldSize() {
const { size } = this.observedProps;

return !size ? undefined : size === 'sm' ? 'sm' : 'lg';
}

@observable
accessor checkedKeys: IDType[] = [];

Expand Down Expand Up @@ -113,7 +121,8 @@ export class RestTable<

@computed
get operateColumn(): Column<D> {
const { editable, deletable, columns, store, translator } = this.observedProps;
const { editable, deletable, columns, store, translator } = this.observedProps,
{ fieldSize } = this;
const { t } = translator,
readOnly = columns.every(({ readOnly }) => readOnly),
disabled = columns.every(({ disabled }) => disabled);
Expand All @@ -129,7 +138,7 @@ export class RestTable<
<Button
className="text-nowrap m-1"
variant={readOnly ? 'primary' : 'warning'}
size="sm"
size={fieldSize}
onClick={() => (store.currentOne = data)}
>
{readOnly ? t('view') : t('edit')}
Expand All @@ -140,7 +149,7 @@ export class RestTable<
<Button
className="text-nowrap m-1"
variant="danger"
size="sm"
size={fieldSize}
onClick={() => this.deleteList([data.id])}
>
{t('delete')}
Expand Down Expand Up @@ -225,6 +234,7 @@ export class RestTable<
deletable,
onCheck,
onSubmit,
onReset,
responsive = true,
...tableProps
} = this.props,
Expand Down Expand Up @@ -306,30 +316,50 @@ export class RestTable<
}

render() {
const { className, editable, deletable, store, translator, onSubmit } = this.props;
const {
className = 'overflow-auto d-flex flex-column gap-3',
editable,
deletable,
filterFields,
store,
translator,
onSubmit,
onReset,
...props
} = this.props,
{ fieldSize } = this;
const { t } = translator,
{ indexKey, pageSize, pageIndex, pageCount, totalCount } = store;

return (
<div className={classNames('overflow-auto', className)}>
<header className="d-flex justify-content-between sticky-top bg-white py-3">
<nav className="d-flex align-items-center">
<Pager {...{ pageSize, pageIndex, pageCount }} onChange={this.getList} />

{!!totalCount && <span className="mx-3 fs14">{t('total_x_rows', { totalCount })}</span>}
</nav>
<div>
<div className={className} {...props}>
<header className="sticky-top bg-white py-3">
{filterFields && (
<RestForm
className="d-flex flex-wrap align-items-center gap-3 pb-3 border-bottom"
size={fieldSize}
translator={translator}
fields={filterFields}
onSubmit={filter => store.getList(filter, 1)}
onReset={() => store.getList({}, 1)}
/>
)}
<div className="d-flex justify-content-between align-items-center">
{deletable && (
<Button
className="mx-2"
variant="danger"
size={fieldSize}
onClick={() => this.deleteList(this.checkedKeys)}
>
{t('delete')}
</Button>
)}
{editable && (
<Button onClick={() => (store.currentOne[indexKey] = '' as D[keyof D])}>
<Button
size={fieldSize}
onClick={() => (store.currentOne[indexKey] = '' as D[keyof D])}
>
{t('create')}
</Button>
)}
Expand All @@ -338,8 +368,15 @@ export class RestTable<

{this.renderTable()}

<footer className="d-flex justify-content-between align-items-center">
{!!totalCount && <span className="mx-3 fs14">{t('total_x_rows', { totalCount })}</span>}

<Pager {...{ pageSize, pageIndex, pageCount }} onChange={this.getList} />
</footer>

{editable && (
<RestFormModal
size={fieldSize}
fields={this.columns.map(({ renderHead, ...field }) => ({
...field,
renderLabel: renderHead,
Expand Down