5htp-core 0.5.0-8 → 0.5.0-9
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/package.json +1 -1
- package/src/client/components/Table/index.tsx +40 -6
- package/src/client/components/inputv3/Rte/Editor.tsx +63 -4
- package/src/client/components/inputv3/Rte/index.tsx +11 -76
- package/src/client/services/router/components/router.tsx +3 -2
- package/src/common/errors/index.tsx +10 -6
- package/src/common/router/index.ts +1 -0
- package/src/server/services/router/index.ts +19 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.5.0-
|
|
4
|
+
"version": "0.5.0-9",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -20,14 +20,20 @@ import Checkbox from '../inputv3/Checkbox';
|
|
|
20
20
|
export type TDonneeInconnue = { id: any } & {[cle: string]: any};
|
|
21
21
|
|
|
22
22
|
export type Props<TRow> = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
|
|
23
|
+
|
|
24
|
+
// Appearence
|
|
26
25
|
stickyHeader?: boolean,
|
|
26
|
+
className?: string,
|
|
27
27
|
|
|
28
|
+
// Data
|
|
29
|
+
data: TRow[],
|
|
28
30
|
setData?: (rows: TRow[]) => void,
|
|
31
|
+
columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
|
|
29
32
|
empty?: ComponentChild | false,
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
// Interactions
|
|
35
|
+
sort?: TSortOptions,
|
|
36
|
+
onSort?: (columnId: string | null, order: TSortOptions["order"]) => void,
|
|
31
37
|
|
|
32
38
|
selection?: [TRow[], React.SetStateAction<TRow[]>],
|
|
33
39
|
maxSelection?: number,
|
|
@@ -38,13 +44,19 @@ export type TColumn = JSX.HTMLAttributes<HTMLElement> & {
|
|
|
38
44
|
cell: ComponentChild,
|
|
39
45
|
raw?: number | string | boolean,
|
|
40
46
|
stick?: boolean,
|
|
47
|
+
sort?: TSortOptions
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type TSortOptions = {
|
|
51
|
+
id: string,
|
|
52
|
+
order: 'desc' | 'asc'
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/*----------------------------------
|
|
44
56
|
- COMPOSANTS
|
|
45
57
|
----------------------------------*/
|
|
46
58
|
export default function Liste<TRow extends TDonneeInconnue>({
|
|
47
|
-
stickyHeader,
|
|
59
|
+
stickyHeader, onSort, sort: sorted,
|
|
48
60
|
data: rows, setData, empty,
|
|
49
61
|
selection: selectionState, maxSelection,
|
|
50
62
|
columns, ...props
|
|
@@ -99,6 +111,7 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
99
111
|
|
|
100
112
|
{columns(row, rows, iDonnee).map(({
|
|
101
113
|
label, cell, class: className, raw,
|
|
114
|
+
sort,
|
|
102
115
|
stick, width, ...cellProps
|
|
103
116
|
}) => {
|
|
104
117
|
|
|
@@ -123,9 +136,30 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
const isCurrentlySorted = sort && sorted && sorted.id === sort.id;
|
|
140
|
+
const isSortable = sort && onSort;
|
|
141
|
+
if (isSortable) {
|
|
142
|
+
classe += ' clickable';
|
|
143
|
+
cellProps.onClick = () => {
|
|
144
|
+
if (isCurrentlySorted)
|
|
145
|
+
onSort(null, sort.order);
|
|
146
|
+
else
|
|
147
|
+
onSort(sort.id, sort.order);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
if (iDonnee === 0) renduColonnes.push(
|
|
127
152
|
<th class={classe} {...cellProps}>
|
|
128
|
-
|
|
153
|
+
<div class="row sp-btw">
|
|
154
|
+
|
|
155
|
+
{isSortable ? (
|
|
156
|
+
<a>{label}</a>
|
|
157
|
+
) : label}
|
|
158
|
+
|
|
159
|
+
{isCurrentlySorted && (
|
|
160
|
+
<i src={sort.order === "asc" ? "caret-up" : "caret-down"} />
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
129
163
|
</th>
|
|
130
164
|
);
|
|
131
165
|
|
|
@@ -84,10 +84,6 @@ export const EMPTY_STATE = '{"root":{"children":[{"children":[],"direction":null
|
|
|
84
84
|
- TYPES
|
|
85
85
|
----------------------------------*/
|
|
86
86
|
|
|
87
|
-
export type Props = {
|
|
88
|
-
preview?: boolean,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
87
|
const ValueControlPlugin = ({ props, value }) => {
|
|
92
88
|
|
|
93
89
|
const [editor] = useLexicalComposerContext();
|
|
@@ -111,6 +107,69 @@ export default ({ value, setValue, props }: {
|
|
|
111
107
|
setValue: (value: string) => void,
|
|
112
108
|
props: TRteProps
|
|
113
109
|
}) => {
|
|
110
|
+
|
|
111
|
+
let {
|
|
112
|
+
// Decoration
|
|
113
|
+
title,
|
|
114
|
+
// Actions
|
|
115
|
+
preview = true
|
|
116
|
+
} = props;
|
|
117
|
+
|
|
118
|
+
/*----------------------------------
|
|
119
|
+
- PREVIEW
|
|
120
|
+
----------------------------------*/
|
|
121
|
+
|
|
122
|
+
const [isPreview, setIsPreview] = React.useState(preview);
|
|
123
|
+
const [html, setHTML] = React.useState(null);
|
|
124
|
+
|
|
125
|
+
React.useEffect(() => {
|
|
126
|
+
if (isPreview) {
|
|
127
|
+
renderPreview(value).then(setHTML);
|
|
128
|
+
}
|
|
129
|
+
}, [value, isPreview]);
|
|
130
|
+
|
|
131
|
+
// When isPreview changes, close the active editor
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
if (!isPreview) {
|
|
134
|
+
|
|
135
|
+
// Close active editor
|
|
136
|
+
if (RichEditorUtils.active && RichEditorUtils.active?.title !== title)
|
|
137
|
+
RichEditorUtils.active.close();
|
|
138
|
+
|
|
139
|
+
// Set active Editor
|
|
140
|
+
RichEditorUtils.active = {
|
|
141
|
+
title,
|
|
142
|
+
close: () => preview ? setIsPreview(true) : null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
}, [isPreview]);
|
|
147
|
+
|
|
148
|
+
const renderPreview = async (value: {} | undefined) => {
|
|
149
|
+
|
|
150
|
+
if (!value)
|
|
151
|
+
return '';
|
|
152
|
+
|
|
153
|
+
if (typeof document === 'undefined')
|
|
154
|
+
throw new Error("HTML preview disabled in server side.");
|
|
155
|
+
|
|
156
|
+
const html = await RichEditorUtils.jsonToHtml(value);
|
|
157
|
+
|
|
158
|
+
return html;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isPreview)
|
|
162
|
+
return (
|
|
163
|
+
html === null ? (
|
|
164
|
+
<div class="col al-center h-2">
|
|
165
|
+
<i src="spin" />
|
|
166
|
+
</div>
|
|
167
|
+
) : (
|
|
168
|
+
<div class="preview reading h-1-4 scrollable col clickable"
|
|
169
|
+
onClick={() => setIsPreview(false)}
|
|
170
|
+
dangerouslySetInnerHTML={{ __html: html }} />
|
|
171
|
+
)
|
|
172
|
+
)
|
|
114
173
|
|
|
115
174
|
/*----------------------------------
|
|
116
175
|
- INIT
|
|
@@ -21,6 +21,7 @@ import './style.less';
|
|
|
21
21
|
|
|
22
22
|
export type Props = {
|
|
23
23
|
preview?: boolean,
|
|
24
|
+
title: string,
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/*----------------------------------
|
|
@@ -28,22 +29,14 @@ export type Props = {
|
|
|
28
29
|
----------------------------------*/
|
|
29
30
|
export default (props: Props & InputBaseProps<string>) => {
|
|
30
31
|
|
|
31
|
-
let {
|
|
32
|
-
// Decoration
|
|
33
|
-
required, size, title, className = '',
|
|
34
|
-
// State
|
|
35
|
-
errors,
|
|
36
|
-
// Actions
|
|
37
|
-
preview = true
|
|
38
|
-
} = props;
|
|
32
|
+
let { className = '' } = props;
|
|
39
33
|
|
|
40
34
|
/*----------------------------------
|
|
41
35
|
- INIT
|
|
42
36
|
----------------------------------*/
|
|
43
37
|
|
|
44
38
|
const [Editor, setEditor] = React.useState<{ default: typeof TEditor }>(null);
|
|
45
|
-
|
|
46
|
-
const [html, setHTML] = React.useState(null);
|
|
39
|
+
|
|
47
40
|
const [{ value }, setValue] = useInput(props, undefined, true);
|
|
48
41
|
|
|
49
42
|
className += ' input rte';
|
|
@@ -53,46 +46,13 @@ export default (props: Props & InputBaseProps<string>) => {
|
|
|
53
46
|
----------------------------------*/
|
|
54
47
|
|
|
55
48
|
React.useEffect(() => {
|
|
56
|
-
if (
|
|
57
|
-
renderPreview(value).then(setHTML);
|
|
58
|
-
}
|
|
59
|
-
}, [value, isPreview]);
|
|
60
|
-
|
|
61
|
-
// When isPreview changes, close the active editor
|
|
62
|
-
React.useEffect(() => {
|
|
63
|
-
if (!isPreview) {
|
|
64
|
-
|
|
65
|
-
// Close active editor
|
|
66
|
-
if (RichEditorUtils.active && RichEditorUtils.active?.title !== title)
|
|
67
|
-
RichEditorUtils.active.close();
|
|
68
|
-
|
|
69
|
-
// Set active Editor
|
|
70
|
-
RichEditorUtils.active = {
|
|
71
|
-
title,
|
|
72
|
-
close: () => preview ? setIsPreview(true) : null
|
|
73
|
-
}
|
|
49
|
+
if (!Editor) {
|
|
74
50
|
|
|
75
51
|
// Load editor component if not alreayd done
|
|
76
52
|
// We lazy load since it's heavy and needs to be loade donly on client side
|
|
77
|
-
|
|
78
|
-
import('./Editor').then(setEditor);
|
|
79
|
-
}
|
|
80
|
-
|
|
53
|
+
import('./Editor').then(setEditor);
|
|
81
54
|
}
|
|
82
|
-
}, [
|
|
83
|
-
|
|
84
|
-
const renderPreview = async (value: {} | undefined) => {
|
|
85
|
-
|
|
86
|
-
if (!value)
|
|
87
|
-
return '';
|
|
88
|
-
|
|
89
|
-
if (typeof document === 'undefined')
|
|
90
|
-
throw new Error("HTML preview disabled in server side.");
|
|
91
|
-
|
|
92
|
-
const html = await RichEditorUtils.jsonToHtml(value);
|
|
93
|
-
|
|
94
|
-
return html;
|
|
95
|
-
}
|
|
55
|
+
}, []);
|
|
96
56
|
|
|
97
57
|
/*----------------------------------
|
|
98
58
|
- RENDER
|
|
@@ -101,40 +61,15 @@ export default (props: Props & InputBaseProps<string>) => {
|
|
|
101
61
|
<InputWrapper {...props}>
|
|
102
62
|
<div class={className}>
|
|
103
63
|
|
|
104
|
-
{
|
|
64
|
+
{Editor === null ? (
|
|
105
65
|
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
</div>
|
|
110
|
-
) : (
|
|
111
|
-
<div class="preview reading h-1-4 scrollable col clickable"
|
|
112
|
-
onClick={() => setIsPreview(false)}
|
|
113
|
-
dangerouslySetInnerHTML={{ __html: html }} />
|
|
114
|
-
)
|
|
66
|
+
<div class="col al-center h-2">
|
|
67
|
+
<i src="spin" />
|
|
68
|
+
</div>
|
|
115
69
|
|
|
116
|
-
) :
|
|
70
|
+
) : (
|
|
117
71
|
<Editor.default value={value} setValue={setValue} props={props} />
|
|
118
72
|
)}
|
|
119
|
-
|
|
120
|
-
{/* <Tag {...fieldProps}
|
|
121
|
-
|
|
122
|
-
placeholder={props.title}
|
|
123
|
-
|
|
124
|
-
// @ts-ignore: Property 'ref' does not exist on type 'IntrinsicAttributes'
|
|
125
|
-
ref={refInput}
|
|
126
|
-
value={value}
|
|
127
|
-
onFocus={() => setState({ focus: true })}
|
|
128
|
-
onBlur={() => setState({ focus: false })}
|
|
129
|
-
onChange={(e) => updateValue(e.target.value)}
|
|
130
|
-
onKeyDown={(e: KeyboardEvent) => {
|
|
131
|
-
|
|
132
|
-
if (onPressEnter && e.key === 'Enter' && value !== undefined) {
|
|
133
|
-
commitValue();
|
|
134
|
-
onPressEnter(value)
|
|
135
|
-
}
|
|
136
|
-
}}
|
|
137
|
-
/> */}
|
|
138
73
|
</div>
|
|
139
74
|
</InputWrapper>
|
|
140
75
|
)
|
|
@@ -147,12 +147,12 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
|
|
|
147
147
|
if (newLayout && curLayout && newLayout.path !== curLayout.path) {
|
|
148
148
|
|
|
149
149
|
// TEMPORARY FIX: reload everything when we change layout
|
|
150
|
-
// Because layout can have a different theme
|
|
150
|
+
// Because layout can have a different CSS theme
|
|
151
151
|
// But when we call setLayout, the style of the previous layout are still oaded and applied
|
|
152
152
|
// Find a way to unload the previous layout / page resources before to load the new one
|
|
153
153
|
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
154
154
|
window.location.replace( request ? request.url : window.location.href );
|
|
155
|
-
return
|
|
155
|
+
return page; // Don't spread since it's an instance
|
|
156
156
|
|
|
157
157
|
context.app.setLayout(newLayout);
|
|
158
158
|
}
|
|
@@ -191,6 +191,7 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
|
|
|
191
191
|
// Reset scroll
|
|
192
192
|
window.scrollTo(0, 0);
|
|
193
193
|
// Should be called AFTER rendering the page (so after the state change)
|
|
194
|
+
console.log("CHANGE PAGE", currentPage);
|
|
194
195
|
currentPage?.updateClient();
|
|
195
196
|
// Scroll to the selected content via url hash
|
|
196
197
|
restoreScroll(currentPage);
|
|
@@ -17,9 +17,13 @@ type TJsonError = {
|
|
|
17
17
|
message: string,
|
|
18
18
|
// Form fields
|
|
19
19
|
errors?: TListeErreursSaisie
|
|
20
|
-
} &
|
|
20
|
+
} & TErrorDetails
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type TErrorDetails = {
|
|
23
|
+
// Allow to identify the error catched (ex: displaying custop content, running custom actions, ...)
|
|
24
|
+
id?: string,
|
|
25
|
+
data?: {},
|
|
26
|
+
// For debugging
|
|
23
27
|
stack?: string,
|
|
24
28
|
origin?: string,
|
|
25
29
|
}
|
|
@@ -66,13 +70,13 @@ export abstract class CoreError extends Error {
|
|
|
66
70
|
public abstract http: number;
|
|
67
71
|
public title: string = "Uh Oh ...";
|
|
68
72
|
public message: string;
|
|
69
|
-
public details:
|
|
73
|
+
public details: TErrorDetails = {};
|
|
70
74
|
|
|
71
75
|
// Note: On ne le redéfini pas ici, car déjà présent dans Error
|
|
72
76
|
// La redéfinition reset la valeur du stacktrace
|
|
73
77
|
//public stack?: string;
|
|
74
78
|
|
|
75
|
-
public constructor(message?: string, details?:
|
|
79
|
+
public constructor(message?: string, details?: TErrorDetails) {
|
|
76
80
|
|
|
77
81
|
super(message);
|
|
78
82
|
|
|
@@ -120,7 +124,7 @@ export class InputErrorSchema extends CoreError {
|
|
|
120
124
|
return chaines.join('; ');
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
public constructor( public errors: TListeErreursSaisie, details?:
|
|
127
|
+
public constructor( public errors: TListeErreursSaisie, details?: TErrorDetails) {
|
|
124
128
|
|
|
125
129
|
super( InputErrorSchema.listeToString(errors), details );
|
|
126
130
|
|
|
@@ -199,7 +203,7 @@ export class NetworkError extends Error {
|
|
|
199
203
|
export const viaHttpCode = (
|
|
200
204
|
code: number,
|
|
201
205
|
message: string,
|
|
202
|
-
details?:
|
|
206
|
+
details?: TErrorDetails
|
|
203
207
|
): CoreError => {
|
|
204
208
|
return fromJson({
|
|
205
209
|
code,
|
|
@@ -459,12 +459,15 @@ declare type Routes = {
|
|
|
459
459
|
|
|
460
460
|
public async resolve(request: ServerRequest<this>): Promise<ServerResponse<this>> {
|
|
461
461
|
|
|
462
|
-
|
|
462
|
+
const logId = LogPrefix + ' ' + (request.isVirtual ? ' ---- ' : '') + request.ip + ' ' + request.method + ' ' + request.domain + ' ' + request.path;
|
|
463
|
+
console.info(logId);
|
|
464
|
+
const timeStart = Date.now();
|
|
463
465
|
|
|
464
466
|
if (this.status === 'starting') {
|
|
465
467
|
console.log(LogPrefix, `Waiting for servert to be resdy before resolving request`);
|
|
466
468
|
await this.started;
|
|
467
469
|
}
|
|
470
|
+
|
|
468
471
|
try {
|
|
469
472
|
|
|
470
473
|
const response = new ServerResponse<this>(request);
|
|
@@ -488,11 +491,14 @@ declare type Routes = {
|
|
|
488
491
|
// Run on resolution hooks. Ex: authentication check
|
|
489
492
|
await this.runHook('resolved', route);
|
|
490
493
|
|
|
494
|
+
const timeEndResolving = Date.now();
|
|
495
|
+
|
|
491
496
|
// Create response
|
|
492
497
|
await response.runController(route);
|
|
493
|
-
if (response.wasProvided)
|
|
494
|
-
|
|
498
|
+
if (response.wasProvided) {
|
|
499
|
+
this.printTakenTime(logId, timeStart, timeEndResolving);
|
|
495
500
|
return response;
|
|
501
|
+
}
|
|
496
502
|
}
|
|
497
503
|
|
|
498
504
|
throw new NotFound();
|
|
@@ -508,10 +514,20 @@ declare type Routes = {
|
|
|
508
514
|
error.details.origin = errOrigin;
|
|
509
515
|
}
|
|
510
516
|
|
|
517
|
+
this.printTakenTime(logId, timeStart);
|
|
511
518
|
throw error;
|
|
512
519
|
}
|
|
513
520
|
}
|
|
514
521
|
|
|
522
|
+
private printTakenTime = (logId: string, timeStart: number, timeEndResolving?: number) => {
|
|
523
|
+
|
|
524
|
+
if (this.app.env.name === 'server') return;
|
|
525
|
+
|
|
526
|
+
console.log(logId + ' ' + Math.round(Date.now() - timeStart) + 'ms' +
|
|
527
|
+
(timeEndResolving === undefined ? '' : ' | Routing: ' + Math.round(timeEndResolving - timeStart))
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
515
531
|
private async resolveApiBatch( fetchers: TFetcherList, request: ServerRequest<this> ) {
|
|
516
532
|
|
|
517
533
|
// TODO: use api.fetchSync instead
|