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 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-8",
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
- data: TRow[],
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
- className?: string,
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
- {label}
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
- const [isPreview, setIsPreview] = React.useState(preview);
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 (isPreview) {
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
- if (!Editor) {
78
- import('./Editor').then(setEditor);
79
- }
80
-
53
+ import('./Editor').then(setEditor);
81
54
  }
82
- }, [isPreview]);
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
- {isPreview ? (
64
+ {Editor === null ? (
105
65
 
106
- html === null ? (
107
- <div class="col al-center h-2">
108
- <i src="spin" />
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
- ) : Editor !== null && (
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 { ...page }
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
- } & TDetailsErreur
20
+ } & TErrorDetails
21
21
 
22
- type TDetailsErreur = {
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: TDetailsErreur = {};
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?: TDetailsErreur) {
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?: TDetailsErreur) {
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?: TDetailsErreur
206
+ details?: TErrorDetails
203
207
  ): CoreError => {
204
208
  return fromJson({
205
209
  code,
@@ -78,6 +78,7 @@ export type TRouteOptions = {
78
78
  // Resolving
79
79
  domain?: string,
80
80
  accept?: string,
81
+ raw?: boolean, // true to return raw data
81
82
  auth?: TUserRole | boolean,
82
83
 
83
84
  // Rendering
@@ -459,12 +459,15 @@ declare type Routes = {
459
459
 
460
460
  public async resolve(request: ServerRequest<this>): Promise<ServerResponse<this>> {
461
461
 
462
- console.info(LogPrefix, request.ip, request.method, request.domain, request.path);
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
- // On continue l'itération des routes, sauf si des données ont été fournie dans la réponse (.json(), .html(), ...)
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