5htp-core 0.3.4-3 → 0.3.5

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.
Files changed (36) hide show
  1. package/package.json +2 -2
  2. package/src/client/app/component.tsx +3 -0
  3. package/src/client/assets/css/components/button.less +6 -5
  4. package/src/client/assets/css/components/card.less +0 -22
  5. package/src/client/assets/css/components.less +0 -25
  6. package/src/client/assets/css/core.less +62 -3
  7. package/src/client/assets/css/text/icons.less +3 -2
  8. package/src/client/assets/css/text/text.less +0 -4
  9. package/src/client/assets/css/theme.less +69 -39
  10. package/src/client/assets/css/utils/layouts.less +1 -12
  11. package/src/client/assets/css/utils/medias.less +2 -15
  12. package/src/client/components/Dialog/index.less +2 -0
  13. package/src/client/components/Row/index.less +2 -0
  14. package/src/client/components/Video/index.less +6 -1
  15. package/src/client/components/containers/Popover/index.tsx +1 -2
  16. package/src/client/components/containers/Popover/popover.less +2 -0
  17. package/src/client/components/data/progressbar/index.less +2 -0
  18. package/src/client/components/inputv3/base.less +6 -0
  19. package/src/client/components/inputv3/file/index.less +2 -0
  20. package/src/client/pages/_messages/401.tsx +1 -2
  21. package/src/client/services/router/components/Page.tsx +1 -0
  22. package/src/client/services/router/components/router.tsx +24 -14
  23. package/src/client/services/router/index.tsx +11 -6
  24. package/src/client/services/router/request/api.ts +6 -3
  25. package/src/client/services/router/response/index.tsx +20 -15
  26. package/src/client/services/router/response/page.ts +1 -1
  27. package/src/common/router/index.ts +41 -0
  28. package/src/common/router/request/api.ts +2 -0
  29. package/src/common/router/response/index.ts +2 -2
  30. package/src/common/router/response/page.ts +1 -0
  31. package/src/server/app/index.ts +0 -1
  32. package/src/server/app/service/index.ts +12 -3
  33. package/src/server/services/router/http/index.ts +5 -7
  34. package/src/server/services/router/index.ts +9 -10
  35. package/src/server/services/router/request/api.ts +7 -2
  36. package/src/server/services/router/response/index.ts +12 -11
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.3.4-3",
4
+ "version": "0.3.5",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "cookie-parser": "^1.4.5",
29
29
  "core-js": "^3.18.1",
30
30
  "cors": "^2.8.5",
31
- "cron-parser": "^4.0.0",
31
+ "cron-parser": "^4.0.0",
32
32
  "dayjs": "^1.11.5",
33
33
  "deep-extend": "^0.6.0",
34
34
  "dottie": "^2.0.2",
@@ -14,6 +14,9 @@ import DialogManager from '@client/components/Dialog/Manager'
14
14
  import RouterComponent from '@client/services/router/components/router';
15
15
  import type { TClientOrServerContext } from '@common/router';
16
16
 
17
+ // Resources
18
+ import '@client/assets/css/core.less';
19
+
17
20
  /*----------------------------------
18
21
  - COMPOSANT
19
22
  ----------------------------------*/
@@ -27,16 +27,17 @@
27
27
  font-weight: 600;
28
28
 
29
29
  // Colors
30
+ background: var(--cBg);
30
31
  color: var(--cTxtAccent);
31
32
 
32
33
  // Hover
33
- transition: all .5s linear;
34
+ //transition: all .5s linear;
34
35
  &:hover,
35
36
  &.selected,
36
37
  li:hover > & {
37
38
 
38
39
  color: var(--cTxtImportant);
39
- transition: all .1s linear;
40
+ //transition: all .1s linear;
40
41
 
41
42
  > i {
42
43
  color: var(--cAccent)
@@ -63,8 +64,8 @@
63
64
  text-align: left;
64
65
  white-space: nowrap; // Autrement, si plusieurs mots, affiché sur plusieurs ligne
65
66
  gap: @spacing / 2;
66
- min-width: 4em;
67
67
  font-size: 1rem;
68
+ line-height: 1.5em;
68
69
  z-index: 1; // Make the label on top of ::before for example
69
70
 
70
71
  li > & {
@@ -99,7 +100,7 @@
99
100
  &:not(:disabled) {
100
101
  &:hover,
101
102
  li:hover > & {
102
- background: var(--cBgHighlight);
103
+ background: var(--cBgActive);
103
104
  }
104
105
  }
105
106
 
@@ -220,7 +221,7 @@ ul.row {
220
221
 
221
222
  &.selected,
222
223
  &:hover {
223
- background: var(--cBg2);
224
+ background: var(--cBgActive);
224
225
  color: var(--cTxtImportant);
225
226
  }
226
227
 
@@ -145,28 +145,6 @@
145
145
  }
146
146
  }
147
147
 
148
- .msg, .card.bg.info {
149
-
150
- padding: @spacing @cardPaddingLong;
151
-
152
- .build-theme-bg( lighten(@cInfo, 35%), darken(@cInfo, 20%));
153
-
154
- }
155
-
156
- .msg, .card.bg {
157
- &.error {
158
- .build-theme-bg( lighten(@cInfo, 35%), darken(@cInfo, 20%));
159
- }
160
-
161
- &.warn {
162
- .build-theme-bg( lighten(@cWarn, 25%), darken(@cWarn, 30%));
163
- }
164
-
165
- &.success {
166
- .build-theme-bg( lighten(@cSuccess, 50%), darken(@cSuccess, 30%));
167
- }
168
- }
169
-
170
148
  iframe {
171
149
  border: none;
172
150
  }
@@ -23,31 +23,6 @@
23
23
  @import './utils/layouts.less';
24
24
  @import './utils/medias.less';
25
25
 
26
- .white-card() {
27
- background: white;
28
- box-shadow: 0 3px 2px fade(#000, 10%);
29
- border: solid 1px fade(#000, 10%);
30
- border-radius: @radius;
31
- }
32
-
33
- .card,
34
- .btn,
35
- .input.text,
36
- .input.select,
37
- i.solid {
38
-
39
- .build-theme-bg( #fff, #8E8E8E);
40
-
41
- &:not(.bg) {
42
- .white-card();
43
- }
44
-
45
- .bg & {
46
- box-shadow: none;
47
- border: none;
48
- }
49
- }
50
-
51
26
  .input.text {
52
27
  &.error {
53
28
  border-color: @cError;
@@ -1,3 +1,5 @@
1
+ @import (reference) '@/client/assets/vars.less';
2
+
1
3
  // Utils
2
4
  @import './utils/sizing.less';
3
5
  @import './utils/spacing.less';
@@ -18,16 +20,41 @@
18
20
  background-repeat: no-repeat;
19
21
  background-position: center;
20
22
 
21
- .build-theme-bg(#000, #fff);
22
23
  text-shadow: 0 0 10px fade(#000, 20%);
23
24
 
25
+ .apply-theme({
26
+ background: #000;
27
+ foreground: #fff;
28
+ accent1: #fff;
29
+ accent2: #fff;
30
+ }, {
31
+ background: #111;
32
+ foreground: #fff;
33
+ accent1: #fff;
34
+ accent2: #fff;
35
+ });
36
+
24
37
  --cTxtDiscret: fade(#fff, 40%)
25
38
  --cTxtDesc: fade(#fff, 60%);
26
39
  --cTxtBase: fade(#fff, 80%);
27
40
  --cTxtImportant: fade(#fff, 100%);
28
- --cTxtAccent: fade(#fff, 20%);
41
+ --cTxtAccent: #FFF;
29
42
 
30
43
  &.light {
44
+ .apply-theme({
45
+ background: #fff;
46
+ foreground: #000;
47
+ accent1: #000;
48
+ accent2: #000;
49
+ },{
50
+ background: #fff;
51
+ foreground: #000;
52
+ accent1: #000;
53
+ accent2: #000;
54
+ });
55
+
56
+ text-shadow: none;
57
+
31
58
  --cTxtDiscret: fade(#000, 40%)
32
59
  --cTxtDesc: fade(#000, 60%);
33
60
  --cTxtBase: fade(#000, 80%);
@@ -55,6 +82,33 @@
55
82
  position: relative;
56
83
  }
57
84
  }
85
+
86
+ &.faded {
87
+ position: relative;
88
+
89
+ > * {
90
+ z-index: 1;
91
+ position: relative;
92
+ }
93
+
94
+ &:after {
95
+ content: ' ';
96
+ display: block;
97
+
98
+ z-index: 0;
99
+ position: absolute;
100
+ bottom: 0px;
101
+ left: 0px;
102
+ right: 0px;
103
+
104
+ height: 85%;
105
+ background: linear-gradient(
106
+ to bottom,
107
+ transparent,
108
+ #fff
109
+ )
110
+ }
111
+ }
58
112
  }
59
113
 
60
114
  &.bg-cover {
@@ -106,4 +160,9 @@ body {
106
160
  -webkit-font-smoothing: antialiased;
107
161
  -moz-osx-font-smoothing: grayscale;
108
162
 
109
- }
163
+ }
164
+
165
+ // Import components style (always after variables declaration)
166
+ @import "@client/assets/css/components.less";
167
+
168
+ @import '@/client/assets/theme.less';
@@ -76,9 +76,10 @@ i.logo {
76
76
  line-height: 2em;
77
77
  flex: 0 0 2em;
78
78
 
79
- background-color: var(--cBg2);
80
79
  border-radius: @radius;
81
- border: none; // For img
80
+ border: none; // For img``
81
+
82
+ background-color: var(--cBg);
82
83
  }
83
84
 
84
85
  i.logo {
@@ -201,11 +201,7 @@ pre {
201
201
  }
202
202
 
203
203
  code {
204
- background: @c3;
205
- border-top: solid 1px @c3 - #111;
206
- border-bottom: solid 1px @c3 - #111;
207
204
  padding: @spacing @readingMargin;
208
- color: @c3 + #888;
209
205
  text-align: left;
210
206
  font-size: 1rem;
211
207
  line-height: 2em;
@@ -1,67 +1,97 @@
1
1
  @bgDelta: #111;
2
2
  @fgDelta: #999;
3
3
 
4
- .build-theme-bg(
5
- @bg,
6
- @fg: if( lightness(@bg) >= 80%, @bg - @fgDelta, @bg + @fgDelta),
7
- @cAccent: @c1,
8
- @cAccent2: @c2,
9
- @apply: true
10
- ) {
4
+ /* Theme structure:
11
5
 
12
- @isLight: boolean( lightness(@bg) >= 80% );
6
+ background?: COLOR;
7
+ foreground: COLOR;
8
+ accent1: COLOR;
9
+ accent2: COLOR;
10
+ */
13
11
 
14
- --cAccent: @cAccent;
15
- --cAccent2: @cAccent2;
12
+ .apply-theme( @theme, @componentsTheme: false, @apply: true ) {
13
+
14
+ .apply-theme-style( @theme, @componentsTheme, @apply );
15
+
16
+ // Children Components
17
+ & when not (@componentsTheme = false) {
18
+ // Default theming for the children components
19
+ @{componentsSelector} {
20
+ // Don't apply the default theme to already themed components
21
+ &:not(.bg) {
22
+ .apply-theme-style( @componentsTheme, false, false );
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ .apply-theme-style( @theme, @componentsTheme: false, @apply: true ) {
29
+
30
+ @isLight: boolean( lightness( @bg ) >= 80% );
31
+
32
+ // Flags
33
+ @bg: @theme[background];
34
+ @bgActive: if( @isLight,
35
+ @bg - #111,
36
+ @bg + #111,
37
+ );
38
+ @fg: @theme[foreground];
16
39
 
17
40
  // Background
18
- // TODO: Nettoyer
19
- @bg2: darken(@bg, 8%);
20
- @bgDark: darken(@bg, 7%);
21
- @bgDarkPlus: darken(@bg, 10%);
22
41
  --cBg: @bg;
23
- --cBg2: @bg2;
24
- --cBgAccent: @bgDark;
25
- --cBgAccentPlus: @bgDarkPlus;
26
- --cBgControl: if( @bg = #fff, @cBgPage, #fff );
27
- --cBgHover: #f5f5f5;
42
+ --cBgActive: @bgActive;
43
+ & when (@apply = true) {
44
+ background: var(--cBg);
45
+ }
28
46
 
29
- @cBgHighlight: @bg + #111;
30
- --cBgHighlight: @cBgHighlight;
47
+ // Accent
48
+ & when (@theme[accent1]) {
49
+ --cAccent: @theme[accent1];
50
+ }
51
+ & when (@theme[accent2]) {
52
+ --cAccent2: @theme[accent2];
53
+ }
31
54
 
32
- // Other
55
+ // Lines
33
56
  @cLine: if( @isLight, @bg - #151515, @bg + #151515);
34
- @cLine2: if( @isLight, @bg - #191919, @bg + #050505);
35
57
  --cLine: @cLine;
58
+ @cLine2: if( @isLight, @bg - #191919, @bg + #050505);
36
59
  --cLine2: @cLine2;
37
60
 
38
61
  // Text
39
- .build-theme-fg(@fg, @bg, @apply);
62
+ .build-theme-fg(@fg, @bg, @theme[accent1], @theme[accent2]);
40
63
 
41
- // Childrens
42
- & when (@apply = true) {
43
- background: var(--cBg);
44
- /*.input.text {
45
- border: if(@bg = #ffffff, solid 2px var(--cLine), none);
46
- }*/
47
- }
64
+ // TO REMOVE
65
+ --cBgControl: if( @bg = #fff, @cBgPage, #fff );
66
+ --cBgHover: #f5f5f5;
48
67
  }
49
68
 
50
- .build-theme-fg( @cTxtBase, @bg: @cBgPage, @apply: true ) {
69
+ .build-theme-fg(
70
+ @cTxtBase,
71
+ @bg: @cBgPage,
72
+ @cAccent: false,
73
+ @cAccent2: false
74
+ ) {
51
75
 
52
76
  @isLight: boolean( lightness(@bg) >= 70% );
77
+
78
+ // Base
79
+ --cTxtBase: @cTxtBase;
53
80
 
54
- // Text
81
+ // Discret
55
82
  @cTxtDiscret: if( @isLight, @bg - #444, @bg + #444);
56
- @cTxtDesc: if( @isLight, @cTxtBase + #222, @cTxtBase - #222);
57
- @cTxtImportant: if( @isLight, @cDark, #fff);
58
- @cTxtAccent: if( @bg = @c1, @cTxtImportant, @c1);
59
-
60
83
  --cTxtDiscret: @cTxtDiscret;
84
+
85
+ // Desc
86
+ @cTxtDesc: if( @isLight, @cTxtBase + #222, @cTxtBase - #222);
61
87
  --cTxtDesc: @cTxtDesc;
62
- --cTxtBase: @cTxtBase;
88
+
89
+ // Important
90
+ @cTxtImportant: if( @isLight, @cDark, #fff);
63
91
  --cTxtImportant: @cTxtImportant;
64
- --cTxtAccent: @cTxtAccent;
92
+
93
+ // Accent
94
+ --cTxtAccent: @cAccent;
65
95
 
66
96
  color: var(--cTxtBase);
67
97
  }
@@ -47,17 +47,6 @@
47
47
  > li {
48
48
  position: relative;
49
49
 
50
- > a:hover::before {
51
- content: ' ';
52
- position: absolute;
53
- top: 0; left: 0; bottom: 0; right: 0;
54
- margin: 0 0.3em;
55
- background: var(--cBgAccent);
56
- border-radius: @radius;
57
- z-index: 0;
58
-
59
- }
60
-
61
50
  > a > * {
62
51
  z-index: 1;
63
52
  position: relative;
@@ -298,7 +287,7 @@
298
287
  &.y-@{i} {
299
288
  /*@t_zones: (@L_max) / @i;
300
289
  @espace_zone: ~"repeat(@{i}, minmax(@{L_min}, @{t_zones}))";*/
301
- @espace_zone: ~"repeat(@{i}, 1fr)";
290
+ @espace_zone: ~"repeat(@{i}, min-content)";
302
291
  .construire(@espace_zone, row);
303
292
  }
304
293
 
@@ -1,23 +1,10 @@
1
-
2
- img.img,
3
- .bg.img {
4
- background: @cBgPage - #101010;
5
- }
6
-
7
1
  img {
8
2
  max-width: 100%;
9
3
  display: block;
10
4
 
11
5
  &.img {
12
6
  border-radius: @radius;
7
+ object-fit: cover;
8
+ object-position: center;
13
9
  }
14
- }
15
-
16
- /*----------------------------------
17
- - IMAGE CONFIG
18
- ----------------------------------*/
19
-
20
- img.img {
21
- object-fit: cover;
22
- object-position: center;
23
10
  }
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  @toast-zindex: 999;
2
4
 
3
5
  #modals,
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  @hideeWidth: 10em;
2
4
 
3
5
  .scrollable-row {
@@ -1,5 +1,10 @@
1
1
  .video {
2
- .build-theme-bg(#000, #fff);
2
+ .apply-theme({
3
+ background: #000;
4
+ foreground: #fff;
5
+ accent1: #fff;
6
+ accent2: #fff;
7
+ });
3
8
 
4
9
  position: relative;
5
10
  text-shadow: 0 0 10px fade(#000, 20%);
@@ -102,12 +102,11 @@ export default (props: Props) => {
102
102
  let renderedContent: ComponentChild;
103
103
  if (active) {
104
104
  //content = typeof content === 'function' ? React.createElement(content) : content;
105
- console.log("render content", content);
106
105
  renderedContent = React.cloneElement(
107
106
  content,
108
107
  {
109
108
  className: (content.props.className || '')
110
- + ' card white popover pd-1'
109
+ + ' card popover pd-1'
111
110
  + (position ? ' pos_' + position.cote : ''),
112
111
 
113
112
  ref: (ref: any) => {
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  // La popover étant positionnée de manière relative à son conteneur,
2
4
  // contPopover doit envelopper le bouton sans aucun espace
3
5
  .contPopover {
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  div.progressbar {
2
4
 
3
5
  background: var(--cLine);
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  @hInput: @sizeComponent;
2
4
  @labelH: 0.6em;
3
5
 
@@ -14,6 +16,9 @@
14
16
  &.text,
15
17
  &.select {
16
18
 
19
+ background: var(--cBg);
20
+ border-radius: @radius;
21
+
17
22
  > i {
18
23
  color: var(--cTxtDesc);
19
24
  }
@@ -108,6 +113,7 @@
108
113
  width: 100%;
109
114
  padding: @labelH 0 0 0;
110
115
  font-size: 1rem;
116
+ color: inherit;
111
117
  }
112
118
 
113
119
  input {
@@ -1,3 +1,5 @@
1
+ @import (reference) "@/client/assets/vars.less";
2
+
1
3
  .input.upload {
2
4
 
3
5
  position: relative;
@@ -21,7 +21,7 @@ import useHeader from '@client/pages/useHeader';
21
21
  ----------------------------------*/
22
22
  Router.error( 401, ({ message, request, page }) => {
23
23
 
24
- request.response?.redirect('/');
24
+ request.response?.redirect('https://becrosspath.com');
25
25
 
26
26
  useHeader({
27
27
  title: 'Authentication Required',
@@ -32,7 +32,6 @@ Router.error( 401, ({ message, request, page }) => {
32
32
 
33
33
  page?.go('/');
34
34
 
35
-
36
35
  }, []);
37
36
 
38
37
  return (
@@ -41,6 +41,7 @@ export default ({ page }: { page: Page }) => {
41
41
  ...apiData,
42
42
  ...context.request.data
43
43
  }}
44
+ context={context}
44
45
  />
45
46
 
46
47
  ) : 'Renderer missing'
@@ -44,6 +44,13 @@ const PageLoading = ({ clientRouter }: { clientRouter?: ClientRouter }) => {
44
44
 
45
45
  }
46
46
 
47
+ const scrollToElement = (selector: string) => document.querySelector( selector )
48
+ ?.scrollIntoView({
49
+ behavior: "smooth",
50
+ block: "start",
51
+ inline: "nearest"
52
+ })
53
+
47
54
  /*----------------------------------
48
55
  - COMPONENT
49
56
  ----------------------------------*/
@@ -72,15 +79,25 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
72
79
 
73
80
  if (!clientRouter) return;
74
81
 
82
+ const currentRequest = context.request;
83
+ context.request = request;
84
+
75
85
  // WARNING: Don"t try to play with pages here, since the object will not be updated
76
86
  // If needed to play with pages, do it in the setPages callback below
87
+ // Unchanged path
88
+ if (request.path === currentRequest.path) {
89
+
90
+ // Scroll to component
91
+ if (request.hash) {
92
+ scrollToElement(request.hash);
93
+ }
94
+
95
+ return;
96
+ }
77
97
 
78
98
  // Set loading state
79
99
  clientRouter.setLoading(true);
80
-
81
- // Load the route chunks
82
- context.request = request;
83
- const newpage = await clientRouter.resolve(request);
100
+ const newpage = context.page = await clientRouter.resolve(request);
84
101
 
85
102
  // Page not found: Directly load with the browser
86
103
  if (newpage === undefined) {
@@ -93,7 +110,7 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
93
110
  }
94
111
 
95
112
  // Fetch API data to hydrate the page
96
- const newData = context.data = await newpage.fetchData();
113
+ const newData = await newpage.fetchData();
97
114
 
98
115
  // Add page container
99
116
  setPages( pages => {
@@ -122,16 +139,12 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
122
139
  context.app.setLayout(newLayout);
123
140
  }
124
141
 
125
- return { current: newpage }
142
+ return { current: newpage }
126
143
  });
127
144
  }
128
145
 
129
146
  const restoreScroll = (currentPage?: Page) => currentPage?.scrollToId
130
- && document.getElementById( currentPage.scrollToId.substring(1) )?.scrollIntoView({
131
- behavior: "smooth",
132
- block: "start",
133
- inline: "nearest"
134
- })
147
+ && scrollToElement( currentPage.scrollToId.substring(1) )
135
148
 
136
149
  // First render
137
150
  React.useEffect(() => {
@@ -146,9 +159,6 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
146
159
  // Load the concerned route
147
160
  const request = new ClientRequest(locationUpdate.location, context.Router);
148
161
  await resolvePage(request);
149
-
150
- // Scroll to the selected content via url hash
151
- //restoreScroll(pages.current);
152
162
  })
153
163
  }, []);
154
164
 
@@ -15,7 +15,9 @@ import type {
15
15
  import type { TBasicSSrData } from '@server/services/router/response';
16
16
 
17
17
  import BaseRouter, {
18
- defaultOptions, TRoute, TErrorRoute, TClientOrServerContext, TRouteModule
18
+ defaultOptions, TRoute, TErrorRoute,
19
+ TClientOrServerContext, TRouteModule,
20
+ buildUrl, TDomainsList
19
21
  } from '@common/router'
20
22
  import { getLayout } from '@common/router/layouts';
21
23
  import { getRegisterPageArgs, buildRegex } from '@common/router/register';
@@ -123,7 +125,8 @@ type THookName = 'location.change' | 'page.changed'
123
125
 
124
126
  type Config<TAdditionnalContext extends {} = {}> = {
125
127
  preload: string[], // List of globs
126
- context: (router: ClientContext) => TAdditionnalContext
128
+ context: (context: ClientContext, router: ClientRouter) => TAdditionnalContext,
129
+ domains: TDomainsList
127
130
  }
128
131
 
129
132
  /*----------------------------------
@@ -153,9 +156,13 @@ export default class ClientRouter<
153
156
  this.initialRender(currentRoute);
154
157
  }
155
158
 
159
+ public url = (path: string, params: {} = {}, absolute: boolean = true) =>
160
+ buildUrl(path, params, this.config.domains, absolute);
161
+
156
162
  public go( url: string ) {
163
+ url = this.url(url, {}, false);
157
164
  console.log( LogPrefix, "Go to", url);
158
- history?.replace(url);
165
+ history?.replace( url );
159
166
  }
160
167
 
161
168
  /*----------------------------------
@@ -371,8 +378,6 @@ export default class ClientRouter<
371
378
  let apiData: {} = {}
372
379
  if (this.ssrContext) {
373
380
 
374
- console.log("SSR Response restitution ...");
375
-
376
381
  request.user = this.ssrContext.user || null;
377
382
 
378
383
  request.data = this.ssrContext.request.data;
@@ -382,7 +387,7 @@ export default class ClientRouter<
382
387
 
383
388
  // Replacer api data par ssr data
384
389
 
385
- const response = await this.createResponse(route, request, apiData)
390
+ const response = await this.createResponse(route, request, apiData);
386
391
 
387
392
  ReactDOM.hydrate((
388
393
  <App context={response.context} />
@@ -66,7 +66,6 @@ export default class ApiClient implements ApiClientService {
66
66
  public delete = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
67
67
  this.createFetcher<TData>('DELETE', path, data, opts);
68
68
 
69
-
70
69
  public set( newData: TObjetDonnees ) {
71
70
 
72
71
  if (!('context' in this.router))
@@ -174,12 +173,15 @@ export default class ApiClient implements ApiClientService {
174
173
  public configure = (...[method, path, data, options]: TFetcherArgs): AxiosRequestConfig => {
175
174
 
176
175
  const { onProgress, captcha } = options || {};
176
+
177
+ const url = this.router.url( path, {}, false );
177
178
 
178
- debug && console.log(`[api] Sending request`, method, path, data);
179
+ debug && console.log(`[api] Sending request`, method, url, data);
179
180
 
181
+ // Create AXIOS config
180
182
  const config: AxiosRequestConfig = {
181
183
 
182
- url: path,
184
+ url,
183
185
  method: method,
184
186
  headers: {
185
187
  'Content-Type': "application/json",
@@ -197,6 +199,7 @@ export default class ApiClient implements ApiClientService {
197
199
 
198
200
  };
199
201
 
202
+ // Format request data
200
203
  if (data) {
201
204
  // URL params
202
205
  if (method === "GET") {
@@ -6,8 +6,8 @@
6
6
  import type ServerRouter from '@server/services/router';
7
7
  import type ServerResponse from '@server/services/router/response';
8
8
 
9
- import type { TRoute, TErrorRoute } from '@common/router';
10
- import BaseResponse from '@common/router/response';
9
+ import type { TAnyRoute, TErrorRoute } from '@common/router';
10
+ import BaseResponse, { TResponseData } from '@common/router/response';
11
11
 
12
12
  import type ClientApplication from '@client/app';
13
13
  import type { default as ClientRouter } from '@client/services/router'
@@ -15,7 +15,6 @@ import type ClientResponse from '@client/services/router/response'
15
15
  import ClientRequest from '@client/services/router/request'
16
16
  import ClientPage from '@client/services/router/response/page'
17
17
  import { history } from '@client/services/router/request/history';
18
- import CommonPage from '@common/router/response/page';
19
18
 
20
19
  /*----------------------------------
21
20
  - TYPES
@@ -34,12 +33,11 @@ export type TRouterContext<
34
33
  // ClientPage context
35
34
  {
36
35
  app: TApplication,
37
- context: TRouterContext<TRouter, TApplication>,
38
36
  request: ClientRequest<TRouter>,
39
- route: TRoute<TRouterContext>,
37
+ route: TAnyRoute<TRouterContext>,
40
38
  api: ClientRequest<TRouter>["api"],
41
39
  page: ClientPage<TRouter>,
42
- //user: User
40
+ data: TObjetDonnees
43
41
  }
44
42
  &
45
43
  // Expose client application services (api, socket, ...)
@@ -61,7 +59,7 @@ export default class ClientPageResponse<
61
59
 
62
60
  public constructor(
63
61
  public request: ClientRequest<TRouter>,
64
- public route: TRoute | TErrorRoute,
62
+ public route: TAnyRoute | TErrorRoute,
65
63
 
66
64
  public app = request.app,
67
65
  ) {
@@ -83,24 +81,31 @@ export default class ClientPageResponse<
83
81
 
84
82
  // Router context
85
83
  app: this.app,
86
- context: undefined as unknown as TRouterContext<TRouter, TRouter["app"]>,
87
84
  request: this.request,
88
85
  route: this.route,
89
86
  api: this.request.api,
90
- page: undefined, // Will be assigned when the controller will be runned
91
- user: this.request.router.ssrContext.user
87
+ // Will be assigned when the controller will be runned
88
+ page: undefined as unknown as ClientPage<TRouter>,
89
+ data: {},
92
90
  }
93
91
 
94
- const completeContext: TRouterContext<TRouter, TRouter["app"]> = {
92
+ const newContext: TRouterContext<TRouter, TRouter["app"]> = {
95
93
  ...basicContext,
96
-
97
94
  // Custom context
98
- ...this.request.router.config.context( basicContext )
95
+ ...this.request.router.config.context( basicContext, this.request.router )
99
96
  }
100
97
 
101
- this.request.router.context = completeContext.context = completeContext;
98
+ // Update context object if already exists
99
+ // NOTE: we don't create a nex instance of context because we don't want to rereder the full page (inc layout) to update the context given by thr react context provider
100
+ const existingContext = this.request.router.context;
101
+ if (existingContext === undefined) {
102
+
103
+ this.request.router.context = newContext
104
+
105
+ } else for(const key in newContext)
106
+ existingContext[ key ] = newContext[ key ];
102
107
 
103
- return completeContext
108
+ return newContext
104
109
  }
105
110
 
106
111
  public async runController( additionnalData: {} = {} ): Promise<ClientPage> {
@@ -45,7 +45,7 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
45
45
  this.context.page = this;
46
46
 
47
47
  // Data succesfully loaded
48
- this.data = data || await this.fetchData();
48
+ this.context.data = this.data = data || await this.fetchData();
49
49
 
50
50
  return this;
51
51
  }
@@ -107,10 +107,51 @@ export type TRouteModule<TRegisteredRoute = any> = {
107
107
  __register?: TAppArrowFunction<TRegisteredRoute>
108
108
  }
109
109
 
110
+ export type TDomainsList = {
111
+ [endpointId: string]: string
112
+ } & {
113
+ current: string
114
+ }
115
+
110
116
  export const defaultOptions = {
111
117
  priority: 0,
112
118
  }
113
119
 
120
+ /*----------------------------------
121
+ - FUNCTIONS
122
+ ----------------------------------*/
123
+ export const buildUrl = (
124
+ path: string,
125
+ params: {} = {},
126
+ domains: TDomainsList,
127
+ absolute: boolean
128
+ ) => {
129
+
130
+ // Relative to domain
131
+ if (path[0] === '/' && absolute)
132
+ return domains.current + path;
133
+ // Other domains of the project
134
+ else if (path[0] === '@') {
135
+
136
+ // Extract domain ID from path
137
+ let domainId: string;
138
+ const slackPos = path.indexOf('/');
139
+ domainId = path.substring(1, slackPos);
140
+ path = path.substring(slackPos);
141
+
142
+ // Get domain
143
+ const domain = domains[ domainId ];
144
+ if (domain === undefined)
145
+ throw new Error("Unknown API endpoint ID: " + domainId);
146
+
147
+ // Return full url
148
+ return domain + path;
149
+
150
+ // Absolute URL
151
+ } else
152
+ return path;
153
+ }
154
+
114
155
  /*----------------------------------
115
156
  - BASE ROUTER
116
157
  ----------------------------------*/
@@ -72,6 +72,8 @@ export default abstract class ApiClient {
72
72
 
73
73
  public abstract delete<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
74
74
 
75
+ public abstract set( newData: TObjetDonnees );
76
+
75
77
  /*----------------------------------
76
78
  - LOW LEVEL
77
79
  ----------------------------------*/
@@ -7,14 +7,14 @@ import { FunctionalComponent } from "preact";
7
7
 
8
8
  // Core
9
9
  import { TAnyRoute } from "..";
10
- import type ClientRequest from '@client/services/router';
10
+ import type ClientRequest from '@client/services/router/request';
11
11
  import Page from '@client/services/router/response/page'
12
12
 
13
13
  /*----------------------------------
14
14
  - TYPES
15
15
  ----------------------------------*/
16
16
 
17
- type TResponseData = Page
17
+ export type TResponseData = Page
18
18
 
19
19
  /*----------------------------------
20
20
  - CONTEXT
@@ -35,6 +35,7 @@ export type TFrontRenderer<
35
35
  TAdditionnalData
36
36
  &
37
37
  {
38
+ context: TClientOrServerContext,
38
39
  data: {[key: string]: PrimitiveValue}
39
40
  }
40
41
  )
@@ -165,7 +165,6 @@ export class Application<
165
165
  public async startServices() {
166
166
 
167
167
  const propsNames = Object.getOwnPropertyNames(this);
168
-
169
168
  for (const propName of propsNames) {
170
169
 
171
170
  // Don't check services prop as it will trigger an error (it's a proxy)
@@ -41,6 +41,10 @@ export type StartedServicesIndex = {
41
41
  [serviceId: string]: AnyService
42
42
  }
43
43
 
44
+ type TServiceUseOptions = {
45
+ optional?: boolean
46
+ }
47
+
44
48
  /*----------------------------------
45
49
  - CONFIG
46
50
  ----------------------------------*/
@@ -147,7 +151,8 @@ export default abstract class Service<
147
151
  TSubServices extends TServiceClass["services"],
148
152
  >(
149
153
  serviceId: TServiceId,
150
- subServices?: TSubServices
154
+ subServices?: TSubServices,
155
+ serviceUseOptions: TServiceUseOptions = {}
151
156
  ): (
152
157
  // We can't pass the services types as a generic to TServiceClass
153
158
  // So we overwrite the services property
@@ -164,8 +169,12 @@ export default abstract class Service<
164
169
 
165
170
  // Check of the service has been configurated
166
171
  const registered = ServicesContainer.registered[ serviceId ];
167
- if (registered === undefined)
168
- throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
172
+ if (registered === undefined) {
173
+ if (serviceUseOptions.optional)
174
+ return undefined;
175
+ else
176
+ throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
177
+ }
169
178
 
170
179
  // Bind subservices
171
180
  if (subServices !== undefined)
@@ -8,7 +8,7 @@ import express from 'express';
8
8
  import http from 'http';
9
9
  import https from 'https';
10
10
  import path from 'path';
11
- import cors from 'cors';
11
+ import cors, { CorsOptions } from 'cors';
12
12
  //var serveStatic = require('serve-static')
13
13
 
14
14
  // Middlewares (npm)
@@ -52,7 +52,8 @@ export type Config = {
52
52
  styles?: string[],
53
53
  images?: string[],
54
54
  scripts: string[],
55
- }
55
+ },
56
+ cors?: CorsOptions
56
57
  }
57
58
 
58
59
  export type Hooks = {
@@ -200,11 +201,8 @@ export default class HttpServer {
200
201
  - PAGES / API
201
202
  ----------------------------------*/
202
203
 
203
- // TODO: Migrer dans app
204
- //routes.use('/chrome', cors());
205
- // TODO: Trouver une solution pour n'autoriser les requetes que depuis l'application & dopamyn.io
206
- // https://www.google.com/search?q=http+cors+from+android%7Cwindows%7Cdesktop%7Cmodile+app
207
- //routes.use('/auth', cors());
204
+ if (this.config.cors !== undefined)
205
+ routes.use( cors( this.config.cors ));
208
206
 
209
207
  routes.use( csp.expressCspHeader({
210
208
  directives: {
@@ -24,7 +24,8 @@ import type DisksManager from '@server/services/disks';
24
24
  import { CoreError, NotFound } from '@common/errors';
25
25
  import BaseRouter, {
26
26
  TRoute, TErrorRoute, TRouteModule,
27
- TRouteOptions, defaultOptions
27
+ TRouteOptions, defaultOptions,
28
+ buildUrl, TDomainsList
28
29
  } from '@common/router';
29
30
  import { buildRegex, getRegisterPageArgs } from '@common/router/register';
30
31
  import { layoutsList, getLayout } from '@common/router/layouts';
@@ -86,6 +87,8 @@ export type Config<
86
87
 
87
88
  disk?: string, // Disk driver ID
88
89
 
90
+ domains: TDomainsList,
91
+
89
92
  http: HttpServiceConfig
90
93
 
91
94
  context: (
@@ -114,7 +117,9 @@ export default class ServerRouter<
114
117
  TSubservices extends Services = Services
115
118
  > extends Service<Config, Hooks, Application, TSubservices> implements BaseRouter {
116
119
 
117
- public disks = this.use('Core/Disks') as unknown as DisksManager;
120
+ public disks = this.use('Core/Disks', undefined, {
121
+ optional: true
122
+ }) as unknown as DisksManager | undefined;
118
123
 
119
124
  // Services
120
125
  public http: HTTP;
@@ -144,7 +149,6 @@ export default class ServerRouter<
144
149
 
145
150
  this.http = new HTTP(config.http, this);
146
151
  this.render = new DocumentRenderer(this);
147
-
148
152
  }
149
153
 
150
154
  /*----------------------------------
@@ -205,13 +209,8 @@ export default class ServerRouter<
205
209
  this.afterRegister();
206
210
  }
207
211
 
208
- // TODO: Generate TS type of the routes list
209
- public url<TRoutePath extends keyof Routes = keyof Routes>(
210
- path: TRoutePath,
211
- params: Routes[TRoutePath]["params"]
212
- ) {
213
- return this.http.publicUrl + path;
214
- }
212
+ public url = (path: string, params: {} = {}, absolute: boolean = true) =>
213
+ buildUrl(path, params, this.config.domains, absolute);
215
214
 
216
215
  /*----------------------------------
217
216
  - REGISTER
@@ -47,6 +47,10 @@ export default class ApiClientRequest extends RequestService implements ApiClien
47
47
  public delete = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
48
48
  this.createFetcher<TData>('DELETE', path, data, opts);
49
49
 
50
+ public set( newData: TObjetDonnees ) {
51
+ throw new Error("api.set is not available on server side.");
52
+ }
53
+
50
54
  /*----------------------------------
51
55
  - API CALLS FROM SERVER
52
56
  ----------------------------------*/
@@ -65,10 +69,11 @@ export default class ApiClientRequest extends RequestService implements ApiClien
65
69
 
66
70
  for (const id in fetchers) {
67
71
 
68
- if (!fetchers[id])
72
+ const fetcher = fetchers[id]
73
+ if (!fetcher)
69
74
  continue;
70
75
 
71
- const { method, path, data, options } = fetchers[id];
76
+ const { method, path, data, options } = fetcher;
72
77
  //this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
73
78
 
74
79
  // We don't fetch the already given data
@@ -245,17 +245,17 @@ export default class ServerResponse<
245
245
  }
246
246
 
247
247
  // TODO: https://github.com/adonisjs/http-server/blob/develop/src/Response/index.ts#L430
248
- public async file( fichier: string ) {
248
+ public async file( filename: string ) {
249
249
 
250
250
  // Securité
251
- if (fichier.includes('..'))
251
+ if (filename.includes('..'))
252
252
  throw new Forbidden("Disallowed");
253
253
 
254
254
  // // Force absolute path
255
- // if (!fichier.startsWith( this.app.path.root ))
256
- // fichier = fichier[0] === '/'
257
- // ? this.app.path.root + '/bin' + fichier
258
- // : this.app.path.data + '/' + fichier;
255
+ // if (!filename.startsWith( this.app.path.root ))
256
+ // filename = filename[0] === '/'
257
+ // ? this.app.path.root + '/bin' + filename
258
+ // : this.app.path.data + '/' + filename;
259
259
  // Disk not provided = file response disabled
260
260
  if (this.router.disks === undefined)
261
261
  throw new Anomaly("Router: Unable to return file response in router, because no disk has been given in the router config.");
@@ -264,14 +264,15 @@ export default class ServerResponse<
264
264
  const disk = this.router.disks.get('default');
265
265
 
266
266
  // Verif existance
267
- const fileExists = await disk.exists('data', fichier);
267
+ const fileExists = await disk.exists('data', filename);
268
268
  if (!fileExists) {
269
- console.log("File " + fichier + " was not found.");
269
+ console.log("File " + filename + " was not found.");
270
270
  throw new NotFound();
271
271
  }
272
272
 
273
- // envoi fichier
274
- this.data = await disk.readFile('data', fichier, {});
273
+ // envoi filename
274
+ const file = await disk.readFile('data', filename, {});
275
+ this.data = file;
275
276
  return this.end();
276
277
  }
277
278
 
@@ -279,7 +280,7 @@ export default class ServerResponse<
279
280
 
280
281
  debug && console.log("[routeur][response] Redirect", url);
281
282
  this.statusCode = code;
282
- this.headers['Location'] = url;
283
+ this.headers['Location'] = this.router.url( url );
283
284
  return this.end();
284
285
  }
285
286