@fullstackunicorn/create-root 1.0.27 → 1.0.29

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 (48) hide show
  1. package/assets/root.fullstackunicorn.dev/assets/config.cmd.sh +2 -2
  2. package/assets/root.fullstackunicorn.dev/backend/modules/loader.js +1 -0
  3. package/assets/root.fullstackunicorn.dev/backend/modules/pulse.js +22 -0
  4. package/assets/root.fullstackunicorn.dev/backend/server/router.js +10 -0
  5. package/assets/root.fullstackunicorn.dev/frontend/App.uri.js +10 -4
  6. package/assets/root.fullstackunicorn.dev/frontend/assets/index.webmanifest +3 -3
  7. package/assets/root.fullstackunicorn.dev/frontend/base/account/@use.js +9 -16
  8. package/assets/root.fullstackunicorn.dev/frontend/base/alerts/Alert.pod.js +2 -2
  9. package/assets/root.fullstackunicorn.dev/frontend/base/callout/Callout.pod.js +3 -3
  10. package/assets/root.fullstackunicorn.dev/frontend/base/cookies/@tools.js +2 -46
  11. package/assets/root.fullstackunicorn.dev/frontend/base/layout/Header.pod.js +2 -2
  12. package/assets/root.fullstackunicorn.dev/frontend/base/layout/Layout.pod.js +3 -3
  13. package/assets/root.fullstackunicorn.dev/frontend/base/layout/sideTwo/IconTwo.pod.js +1 -1
  14. package/assets/root.fullstackunicorn.dev/frontend/base/layout/sideTwo/SidebarTwo.pod.js +2 -2
  15. package/assets/root.fullstackunicorn.dev/frontend/base/loader/@tools.js +2 -1
  16. package/assets/root.fullstackunicorn.dev/frontend/base/modals/Modal.pod.js +2 -2
  17. package/assets/root.fullstackunicorn.dev/frontend/base/modals/ModalPopup.pod.js +3 -3
  18. package/assets/root.fullstackunicorn.dev/frontend/client/auth.js +52 -66
  19. package/assets/root.fullstackunicorn.dev/frontend/client/statics.js +3 -2
  20. package/assets/root.fullstackunicorn.dev/frontend/configure/env.json +1 -0
  21. package/assets/root.fullstackunicorn.dev/frontend/configure/env.prod.json +1 -0
  22. package/assets/root.fullstackunicorn.dev/frontend/imports/animated/Button.js +2 -2
  23. package/assets/root.fullstackunicorn.dev/frontend/imports/animated/Crossfader.js +1 -1
  24. package/assets/root.fullstackunicorn.dev/frontend/imports/animated/HoverCard.js +1 -1
  25. package/assets/root.fullstackunicorn.dev/frontend/imports/client.js +22 -10
  26. package/assets/root.fullstackunicorn.dev/frontend/imports/core/Img.js +62 -10
  27. package/assets/root.fullstackunicorn.dev/frontend/imports/core/Img.pod.js +4 -4
  28. package/assets/root.fullstackunicorn.dev/frontend/imports/core/Scrollable.js +10 -1
  29. package/assets/root.fullstackunicorn.dev/frontend/imports/core/Text.js +11 -2
  30. package/assets/root.fullstackunicorn.dev/frontend/imports/device.js +46 -0
  31. package/assets/root.fullstackunicorn.dev/frontend/imports/router.js +2 -0
  32. package/assets/root.fullstackunicorn.dev/frontend/imports/service.js +2 -2
  33. package/assets/root.fullstackunicorn.dev/frontend/index.html +5 -5
  34. package/assets/root.fullstackunicorn.dev/frontend/modules/base/ComingSoon.pod.js +2 -2
  35. package/assets/root.fullstackunicorn.dev/frontend/modules/base/PageTitle.pod.js +1 -1
  36. package/assets/root.fullstackunicorn.dev/frontend/modules/layout/Wrapper.pod.js +1 -1
  37. package/assets/root.fullstackunicorn.dev/frontend/modules/modals/select/SelectItem.pod.js +1 -1
  38. package/assets/root.fullstackunicorn.dev/frontend/modules/modals/select/SelectPicked.pod.js +1 -1
  39. package/assets/root.fullstackunicorn.dev/frontend/panels/slideshows/Slideshow.pod.js +4 -4
  40. package/assets/root.fullstackunicorn.dev/frontend/templates/cards.js +9 -9
  41. package/assets/root.fullstackunicorn.dev/frontend/templates/page.js +4 -4
  42. package/assets/root.fullstackunicorn.dev/frontend/templates/root.js +1 -1
  43. package/assets/root.fullstackunicorn.dev/manager/modules/pulse.js +22 -0
  44. package/assets/root.fullstackunicorn.dev/manager/server/router.js +11 -1
  45. package/assets/root.fullstackunicorn.dev/workspace/config/workers.json +14 -2
  46. package/index.cmd.help.js +33 -0
  47. package/index.cmd.js +20 -3
  48. package/package.json +2 -1
@@ -109,7 +109,7 @@ class Client {
109
109
  if (isFresh) return cachedResponse
110
110
 
111
111
  config.timeout = 10000
112
- if (etag) {
112
+ if (etag && cachedItem) {
113
113
  if (!config.headers) config.headers = {}
114
114
  config.headers['If-None-Match'] = etag
115
115
  }
@@ -121,6 +121,7 @@ class Client {
121
121
  : await axios[method](endpoint, data, config)
122
122
 
123
123
  if (cachedItem && response.status === 304) {
124
+ await cache.setMeta(key, response.headers['etag'] || etag)
124
125
  return cachedResponse
125
126
  }
126
127
 
@@ -139,21 +140,25 @@ class Client {
139
140
 
140
141
  const cacheStore = await cache.open(this.cacheNameImg)
141
142
  const cachedBlob = await cache.get(cacheStore, key, true)
142
- if (!cachedBlob) return null
143
- return { key, blob: cachedBlob }
143
+ if (!cachedBlob?.size) return null
144
+ return { key, blob: cachedBlob, isCached: true }
144
145
 
145
146
  }
146
147
 
147
148
  cacheImgSet = async ({ key, endpoint, config = {} }) => {
148
149
  const cacheStore = await cache.open(this.cacheNameImg)
149
150
  const cachedBlob = await cache.get(cacheStore, key, true)
150
- const cachedResponse = cachedBlob ? { key, blob: cachedBlob } : undefined
151
+ const cachedResponse = cachedBlob?.size ? { key, blob: cachedBlob, isCached: true } : undefined
151
152
  const { etag, timestamp } = await cache.getMeta(key)
152
153
 
154
+ const age = (Date.now() - timestamp) / 1000
155
+ const isFresh = cachedResponse && age < this.cacheStale
156
+ if (isFresh) return { ...cachedResponse, isFresh: true }
157
+
153
158
  config.responseType = 'arraybuffer'
154
159
  config.timeout = 10000
155
160
 
156
- if (etag) {
161
+ if (etag && cachedResponse) {
157
162
  if (!config.headers) config.headers = {}
158
163
  config.headers['If-None-Match'] = etag
159
164
  }
@@ -161,15 +166,19 @@ class Client {
161
166
  try {
162
167
  const response = await axios.get(endpoint, config)
163
168
 
164
- if (cachedBlob && response.status === 304) return cachedResponse
169
+ if (cachedResponse && response.status === 304) {
170
+ await cache.setMeta(key, response.headers['etag'] || etag)
171
+ return { ...cachedResponse, isRevalidated: true }
172
+ }
165
173
 
166
174
  const blob = cache.getBlob(response.data, response.headers['content-type'])
167
175
  await cache.set(cacheStore, key, blob, true)
168
176
  await cache.setMeta(key, response.headers['etag'])
169
177
 
170
- return { key, blob }
178
+ return { key, blob, isUpdated: true }
171
179
 
172
180
  } catch (error) {
181
+ if (error?.code === 'ERR_CANCELED' || config.signal?.aborted) return cachedResponse || { isCanceled: true }
173
182
  return await err.cache(error, cachedResponse)
174
183
  }
175
184
  }
@@ -178,7 +187,7 @@ class Client {
178
187
 
179
188
  const cacheStore = await cache.open(this.cacheNameFiles)
180
189
  const cachedBlob = await cache.get(cacheStore, key, true)
181
- const cachedResponse = cachedBlob ? { key, blob: cachedBlob } : undefined
190
+ const cachedResponse = cachedBlob?.size ? { key, blob: cachedBlob } : undefined
182
191
  const { etag, timestamp } = await cache.getMeta(key)
183
192
 
184
193
  const age = (Date.now() - timestamp) / 1000
@@ -188,7 +197,7 @@ class Client {
188
197
  config.responseType = 'arraybuffer'
189
198
  config.timeout = 10000
190
199
 
191
- if (etag) {
200
+ if (etag && cachedResponse) {
192
201
  if (!config.headers) config.headers = {}
193
202
  config.headers['If-None-Match'] = etag
194
203
  }
@@ -198,7 +207,10 @@ class Client {
198
207
  ? await axios.get(endpoint, config)
199
208
  : await axios[method](endpoint, data, config)
200
209
 
201
- if (cachedBlob && response.status === 304) return cachedResponse
210
+ if (cachedResponse && response.status === 304) {
211
+ await cache.setMeta(key, response.headers['etag'] || etag)
212
+ return cachedResponse
213
+ }
202
214
 
203
215
  const blob = cache.getBlob(response.data, response.headers['content-type'])
204
216
  await cache.set(cacheStore, key, blob, true)
@@ -6,6 +6,34 @@ import { api } from '#@/client/@api.js'
6
6
 
7
7
  export const ImgData = (props) => <ImgDataPod {...props} />
8
8
 
9
+ const inFlightImages = new Map()
10
+
11
+ const retainImageRequest = (src) => {
12
+ let entry = inFlightImages.get(src)
13
+
14
+ if (!entry) {
15
+ const controller = new AbortController()
16
+ const promise = api.statics.image(src, 'set', { signal: controller.signal })
17
+ .finally(() => inFlightImages.delete(src))
18
+
19
+ entry = { controller, promise, retainers: 0 }
20
+ inFlightImages.set(src, entry)
21
+ }
22
+
23
+ let released = false
24
+ entry.retainers += 1
25
+
26
+ const release = () => {
27
+ if (released) return
28
+ released = true
29
+
30
+ entry.retainers = Math.max(0, entry.retainers - 1)
31
+ if (entry.retainers === 0) entry.controller.abort()
32
+ }
33
+
34
+ return { promise: entry.promise, release }
35
+ }
36
+
9
37
  export const NoImg = (props) => {
10
38
  const statics = useGetStatics()
11
39
  return <NoImgPod {...props} svg={statics.visual.svg.picture} />
@@ -19,9 +47,12 @@ export const Img = ({ classes, inline, picture = {} }) => {
19
47
  const [isSlow, setIsSlow] = useState(false)
20
48
 
21
49
  useEffect(() => {
50
+ if (image.isNoImage || !image.src) return
51
+
22
52
  let mounted = true
23
53
  let objectUrl = null
24
54
  let slowTimer = null
55
+ let releaseRequest = null
25
56
 
26
57
  setSrc(undefined)
27
58
  setIsLoaded(false)
@@ -31,25 +62,45 @@ export const Img = ({ classes, inline, picture = {} }) => {
31
62
  if (mounted) setIsSlow(true)
32
63
  }, 150)
33
64
 
65
+ const setBlob = (blob) => {
66
+ clearTimeout(slowTimer)
67
+ if (objectUrl) URL.revokeObjectURL(objectUrl)
68
+ objectUrl = URL.createObjectURL(blob)
69
+ setSrc(objectUrl)
70
+ setIsLoaded(true)
71
+ setIsSlow(false)
72
+ }
73
+
74
+ const fetchImage = async ({ updateOnly = false } = {}) => {
75
+ const request = retainImageRequest(image.src)
76
+ releaseRequest = request.release
77
+
78
+ const fetched = await request.promise
79
+ releaseRequest()
80
+ releaseRequest = null
81
+
82
+ if (fetched?.isCanceled) return fetched
83
+ if (!mounted) return fetched
84
+ if (!fetched?.blob) return fetched
85
+ if (updateOnly && !fetched.isUpdated) return fetched
86
+
87
+ setBlob(fetched.blob)
88
+ return fetched
89
+ }
90
+
34
91
  const load = async () => {
35
92
  const cached = await api.statics.image(image.src, 'get')
36
93
 
37
94
  if (mounted && cached?.blob) {
38
- clearTimeout(slowTimer)
39
- objectUrl = URL.createObjectURL(cached.blob)
40
- setSrc(objectUrl)
41
- setIsLoaded(true)
42
- setIsSlow(false)
95
+ setBlob(cached.blob)
96
+ fetchImage({ updateOnly: true })
43
97
  return
44
98
  }
45
99
 
46
- const fetched = await api.statics.image(image.src, 'set')
100
+ const fetched = await fetchImage()
101
+ if (fetched?.isCanceled) return
47
102
 
48
103
  if (mounted && fetched?.blob) {
49
- clearTimeout(slowTimer)
50
- objectUrl = URL.createObjectURL(fetched.blob)
51
- setSrc(objectUrl)
52
- setIsLoaded(true)
53
104
  return
54
105
  }
55
106
 
@@ -68,6 +119,7 @@ export const Img = ({ classes, inline, picture = {} }) => {
68
119
  return () => {
69
120
  mounted = false
70
121
  clearTimeout(slowTimer)
122
+ if (releaseRequest) releaseRequest()
71
123
  if (objectUrl) URL.revokeObjectURL(objectUrl)
72
124
  }
73
125
  }, [image.src])
@@ -17,7 +17,7 @@ export const NoImgPod = ({ classes, inline, svg }) => {
17
17
  const { s, c, f, t, F, B, S, I, H } = useStyle()
18
18
 
19
19
  S.box = [...classes, c.base_NN_W700]
20
- S.svg = [s.XY, c.item_NN_W750]
20
+ S.svg = [s.XY100p, c.item_NN_W750]
21
21
 
22
22
  return (
23
23
  <View classes={S.box} inline={inline}>
@@ -37,9 +37,9 @@ export const ImgPod = ({
37
37
  B.imgTransition = isSlow ? { transition: '0.3s all ease-in-out', ...inline } : { ...inline }
38
38
  B.blurhashTransition = { transition: '0.3s all ease-in-out' }
39
39
 
40
- S.image = [s.relative, s.overflowXYh, s.row_cXcY, ...classes]
41
- S.blurhash = [s.absolute, s.XY, ...B.blurhashOpacity]
42
- S.box = [s.absolute, s.XY, s.row_cXcY]
40
+ S.image = [s.positionRelative, s.overflowHidden, s.row_cXcY, ...classes]
41
+ S.blurhash = [s.positionAbsolute, s.XY100p, ...B.blurhashOpacity]
42
+ S.box = [s.positionAbsolute, s.XY100p, s.row_cXcY]
43
43
  S.img = [...B.imgOpacity, ...classes]
44
44
 
45
45
  I.image = { objectFit: 'cover', aspectRatio: ratio, ...inline }
@@ -7,9 +7,18 @@ export const Scrollable = ({
7
7
  scrollRef,
8
8
  onScroll,
9
9
  isPage,
10
+ x,
11
+ y,
12
+ xy,
10
13
  }) => {
11
14
  const { s, c, f, t, F, B, S, I, H } = useStyle()
12
15
 
16
+ B.axis = xy || (x && y) ? 'xy' : x ? 'x' : 'y'
17
+ B.isXY = B.axis === 'xy' ? [s.overflowScroll] : []
18
+ B.isX = B.axis === 'x' ? [s.overflowXScroll, s.overflowYHidden] : []
19
+ B.isY = B.axis === 'y' ? [s.overflowYScroll] : []
20
+ B.scroll = [...B.isXY, ...B.isX, ...B.isY]
21
+
13
22
  const pageInline = isPage
14
23
  ? { paddingBottom: 'env(safe-area-inset-bottom, 0px)' }
15
24
  : {}
@@ -22,7 +31,7 @@ export const Scrollable = ({
22
31
  return (
23
32
  <div
24
33
  ref={scrollRef}
25
- className={[...classes, s.overflowYs].join(' ')}
34
+ className={[...classes, ...B.scroll].join(' ')}
26
35
  style={{ ...scrollInline, ...inline, ...pageInline }}
27
36
  onScroll={onScroll}
28
37
  >
@@ -37,9 +37,14 @@ export const Text = (props = {}) => {
37
37
 
38
38
  F.font = useGetFont(props.font, props.language)
39
39
  B.select = props.isNoSelect ? [s.noSelect] : []
40
+ B.mode = props.ellipsis || props.oneLine
41
+ ? [s.textline]
42
+ : props.wrap || props.preserveWhitespace
43
+ ? [s.textwrap]
44
+ : [s.textblock]
40
45
 
41
46
  const next = { ...props }
42
- next.classes = [...classes, ...B.select, s.textblock, F.font]
47
+ next.classes = [...classes, ...B.select, ...B.mode, F.font]
43
48
 
44
49
  if (props.tag) return <TextTag {...next} />
45
50
  else return <TextSpan {...next} />
@@ -62,6 +67,10 @@ export const Textline = (props = {}) => {
62
67
  else return <TextSpan {...next} />
63
68
  }
64
69
 
70
+ export const TextEllipsis = (props = {}) => {
71
+ return <Textline {...props} ellipsis />
72
+ }
73
+
65
74
  // use it for inline code where u need to pass the style to a span
66
75
  export const TextFont = (props = {}) => {
67
76
 
@@ -85,7 +94,7 @@ export const TextCodeView = ({
85
94
  F.font = useGetFont(f.m1)
86
95
 
87
96
  B.isInline = type === 'inline' ? [s.textblock] : []
88
- B.isBlock = type === 'block' ? [s.textwrap, s.overflowYs] : []
97
+ B.isBlock = type === 'block' ? [s.textwrap, s.overflowYScroll] : []
89
98
  B.isView = type === 'view' ? [s.textwrap] : []
90
99
 
91
100
  S.classes = [...classes, F.font, ...B.isInline, ...B.isBlock, ...B.isView].join(' ')
@@ -0,0 +1,46 @@
1
+ export const getSubmitMetadata = async ({ clientId, deviceId }) => {
2
+ const battery = navigator.getBattery ? await navigator.getBattery() : {}
3
+ const metadataObj = {
4
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
5
+ language: window.navigator.language,
6
+ pageTitle: document.title,
7
+ referrer: document.referrer,
8
+ pageURL: window.location.href,
9
+ platform: window.navigator.platform,
10
+ deviceMemory: navigator.deviceMemory,
11
+ screenWidth: window.screen.width,
12
+ screenHeight: window.screen.height,
13
+ colorDepth: window.screen.colorDepth,
14
+ maxScreenWidth: window.screen.width * window.devicePixelRatio,
15
+ maxScreenHeight: window.screen.height * window.devicePixelRatio,
16
+ pixelRatio: window.devicePixelRatio,
17
+ screenOrientation: screen?.orientation?.type,
18
+ hardwareConcurrency: navigator.hardwareConcurrency,
19
+ touchSupport: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
20
+ colorGamut: window.matchMedia("(color-gamut: srgb)").matches
21
+ ? "srgb"
22
+ : window.matchMedia("(color-gamut: p3)").matches
23
+ ? "p3"
24
+ : window.matchMedia("(color-gamut: rec2020)").matches
25
+ ? "rec2020"
26
+ : "unknown",
27
+ rtt: navigator?.connection?.rtt,
28
+ hasGamepad: navigator.getGamepads ? navigator.getGamepads().length > 0 : false,
29
+ keyboardEnabled: "onkeydown" in document,
30
+ cookieEnabled: navigator.cookieEnabled,
31
+ javaEnabled: navigator.javaEnabled ? navigator.javaEnabled() : false,
32
+ doNotTrack: navigator.doNotTrack,
33
+ networkType: navigator?.connection?.effectiveType,
34
+ downlinkSpeed: navigator?.connection?.downlink,
35
+ batteryLevel: battery?.level * 100,
36
+ batteryCharging: battery?.charging
37
+ }
38
+
39
+ return {
40
+ submit_timestamp: new Date().toISOString(),
41
+ submit_device_id: deviceId,
42
+ submit_client_id: clientId,
43
+ submit_user_agent: window.navigator.userAgent,
44
+ submit_metadata: Object.entries(metadataObj).map(([key, value]) => `${key}:${value ? value : 'unknown'}`).join(" | ")
45
+ }
46
+ }
@@ -7,6 +7,8 @@ export {
7
7
 
8
8
  export const useNavigateUrl = () => url => window.location.href = url
9
9
 
10
+ export const getCurrentHref = () => window.location.href
11
+
10
12
  import { useLocation } from 'react-router-dom'
11
13
 
12
14
  export const useGetUrl = () => {
@@ -20,8 +20,8 @@ class Service {
20
20
  const response = await store.match(key)
21
21
  if (!response) return true
22
22
  const lastDay = (await response.text()).trim()
23
- return true // lastDay < this.todayUTC()
23
+ return lastDay < this.todayUTC()
24
24
  }
25
25
  }
26
26
 
27
- export const service = new Service()
27
+ export const service = new Service()
@@ -12,13 +12,13 @@
12
12
  <meta http-equiv="Expires" content="0">
13
13
 
14
14
  <meta content="#ffffff" name="msapplication-TileColor" />
15
- <meta content="/icons/icon-512.png" name="msapplication-TileImage" />
15
+ <meta content="/get/media/icons/icon-512.png" name="msapplication-TileImage" />
16
16
  <meta content="Carlo" name="apple-mobile-web-app-title" />
17
- <link href="/icons/icon-180.png" rel="apple-touch-icon" sizes="180x180" />
17
+ <link href="/get/media/icons/icon-180.png" rel="apple-touch-icon" sizes="180x180" />
18
18
  <link href="/index.webmanifest" rel="manifest" />
19
19
 
20
- <link href="/favicon.ico" rel="shortcut icon" />
21
- <link href="/icons/favicon.png" rel="icon" type="image/png" />
20
+ <link href="/get/media/icons/favicon.ico" rel="shortcut icon" />
21
+ <link href="/get/media/icons/favicon.png" rel="icon" type="image/png" />
22
22
 
23
23
  <!-- {{metadata}} -->
24
24
  <!-- {{languages}} -->
@@ -33,4 +33,4 @@
33
33
  <script type="module" src="/App.js"></script>
34
34
  </body>
35
35
 
36
- </html>
36
+ </html>
@@ -6,8 +6,8 @@ export const ComingSoonPod = ({ picture }) => {
6
6
 
7
7
  const { s, c, f, t, F, B, S, I, H } = useStyle()
8
8
 
9
- S.box = [s.col_cXsY, s.XY, s.marginB8]
10
- S.image = [s.X, s.radiusB16]
9
+ S.box = [s.col_cXsY, s.XY100p, s.marginB8]
10
+ S.image = [s.X100p, s.radiusB16]
11
11
 
12
12
  return (
13
13
  <View classes={S.box}>
@@ -6,7 +6,7 @@ export const PageTitlePod = ({ title }) => {
6
6
 
7
7
  const { s, c, f, t, F, B, S, I, H } = useStyle()
8
8
 
9
- S.textbox = [s.row_cXcY, s.Y48, s.X]
9
+ S.textbox = [s.row_cXcY, s.Y48, s.X100p]
10
10
  S.text = t.root.page.title
11
11
 
12
12
  if (!title) return null
@@ -5,7 +5,7 @@ export const WrapperPod = ({ paddingX, children }) => {
5
5
 
6
6
  const { s, c, f, t, F, B, S, I, H } = useStyle()
7
7
 
8
- S.box = [s.X]
8
+ S.box = [s.X100p]
9
9
  S.wrap = []
10
10
 
11
11
  I.wrap = {
@@ -14,7 +14,7 @@ export const SelectItemPod = ({ onPress=()=>{}, slug, value, img, isHover, setIs
14
14
 
15
15
  S.item = [s.row_sXcY, s.Y48, s.radius8, s.marginB4, ...B.background, ...B.img]
16
16
  S.imgBox = [s.X26, s.Y20, s.marginX7, s.marginY18, s.radius5, s.border1, c.line_NN, c.item_NN_B400]
17
- S.img = [s.X24, s.Y18, s.overflowXYh, s.radius4]
17
+ S.img = [s.X24, s.Y18, s.overflowHidden, s.radius4]
18
18
  S.text = [...F.text, s.row_sXcY, s.paddingE12, c.item_NN_B400]
19
19
 
20
20
  return (
@@ -17,7 +17,7 @@ export const SelectPickedPod = ({ value, slug, img, isHover, setIsHover=()=>{},
17
17
 
18
18
  S.item = [s.row_sXcY, s.Y40, s.radius8, s.Xmin80, s.border1, c.line_NN_W500, ...B.hover, ...B.img]
19
19
  S.imgBox = [s.X26, s.Y20, s.marginX7, s.radius5, s.border1, c.line_NN, c.item_NN_B400]
20
- S.img = [s.X24, s.Y18, s.overflowXYh, s.radius4]
20
+ S.img = [s.X24, s.Y18, s.overflowHidden, s.radius4]
21
21
  S.textBox = [s.row_cY]
22
22
  S.text = [...F.text, s.Xr, c.item_NN_B400]
23
23
  S.icon = [s.XY40, B.icon]
@@ -11,8 +11,8 @@ export const SlideshowPod = ({ svg, onNext, onPrevious, aspectRatio, children })
11
11
 
12
12
  const { s, c, f, t, F, B, S, I, H } = useStyle()
13
13
 
14
- S.slider = [s.row_cXcY, s.X, s.relative]
15
- S.controls = [s.row, s.XY, s.Z3]
14
+ S.slider = [s.row_cXcY, s.X100p, s.positionRelative]
15
+ S.controls = [s.row, s.XY100p, s.Z3]
16
16
  S.one = [s.row_sXcY, s.XYr]
17
17
  S.iconOneBox = [s.XY64, s.col_cXcY, s.Z2]
18
18
  S.iconOne = [s.XY40, s.Z2, s.marginX8, c.item_NN_B400, s.radius8, c.base_WW_50]
@@ -46,7 +46,7 @@ export const SlideshowTextualPod = ({ subhead, title, description, href, fontSiz
46
46
 
47
47
  const { s, c, f, t, F, B, S, I, H } = useStyle()
48
48
 
49
- S.box = [s.row_sXcY, s.absolute, s.XY, s.top0]
49
+ S.box = [s.row_sXcY, s.positionAbsolute, s.XY100p, s.top0]
50
50
  S.textual = [s.col_sXsY]
51
51
  S.outline = [s.paddingX4, s.paddingY2, s.margin4, c.item_WW]
52
52
 
@@ -76,7 +76,7 @@ export const SlideshowItemPod = ({ isActive, picture, children, aspectRatio, tra
76
76
 
77
77
  const { s, c, f, t, F, B, S, I, H } = useStyle()
78
78
 
79
- S.image = [s.X]
79
+ S.image = [s.X100p]
80
80
  I.image = { aspectRatio }
81
81
 
82
82
  return (
@@ -5,18 +5,18 @@ export const getCards = (s, c, t) => {
5
5
 
6
6
  cards.squared = {}
7
7
  cards.squared.link = []
8
- cards.squared.box = [s.X, s.radius24, s.overflowXYh, c.base_WW]
9
- cards.squared.pictureWrap = [s.X, s.padding8]
10
- cards.squared.pictureBox = [s.X, s.ratio, s.overflowXYh, s.radius16]
11
- cards.squared.picture = [s.cover]
8
+ cards.squared.box = [s.X100p, s.radius24, s.overflowHidden, c.base_WW]
9
+ cards.squared.pictureWrap = [s.X100p, s.padding8]
10
+ cards.squared.pictureBox = [s.X100p, s.ratio, s.overflowHidden, s.radius16]
11
+ cards.squared.picture = [s.objectCover]
12
12
  cards.squared.content = [s.paddingX16, s.paddingY16, s.col]
13
13
 
14
14
  cards.portrait = {}
15
- cards.portrait.link = [s.Y]
16
- cards.portrait.box = [s.X, s.Y, s.col, s.padding24, s.radius24, s.overflowXYh, s.border1, c.base_WW, c.line_NN_W600]
17
- cards.portrait.pictureWrap = [s.X]
18
- cards.portrait.pictureBox = [s.X, s.overflowXYh, s.radius16]
19
- cards.portrait.picture = [s.X]
15
+ cards.portrait.link = [s.Y100p]
16
+ cards.portrait.box = [s.X100p, s.Y100p, s.col, s.padding24, s.radius24, s.overflowHidden, s.border1, c.base_WW, c.line_NN_W600]
17
+ cards.portrait.pictureWrap = [s.X100p]
18
+ cards.portrait.pictureBox = [s.X100p, s.overflowHidden, s.radius16]
19
+ cards.portrait.picture = [s.X100p]
20
20
  cards.portrait.content = [s.paddingT16, s.col, s.Yc]
21
21
  cards.portrait.title = []
22
22
  cards.portrait.price = [s.marginT16]
@@ -6,10 +6,10 @@ export const getPage = (s, c, t) => {
6
6
 
7
7
  page.home = [s.col, s.XYc]
8
8
  page.fluid = [s.col, s.XYc]
9
- page.fixed = [s.col_cXsY, s.Yc, s.Xx, s.Xmax540, s.paddingX16]
10
- page.fixedL = [s.col_cXsY, s.Yc, s.Xx, s.Xmax672, s.paddingX16]
11
- page.fixedXL = [s.col_cXsY, s.Yc, s.Xx, s.Xmax960, s.paddingX16]
12
- page.docs = [s.col_cXsY, s.Yc, s.Xx, s.Xmax540, s.paddingX16, s.marginT16]
9
+ page.fixed = [s.col_cXsY, s.Yc, s.Xx100p, s.Xmax540, s.paddingX16]
10
+ page.fixedL = [s.col_cXsY, s.Yc, s.Xx100p, s.Xmax672, s.paddingX16]
11
+ page.fixedXL = [s.col_cXsY, s.Yc, s.Xx100p, s.Xmax960, s.paddingX16]
12
+ page.docs = [s.col_cXsY, s.Yc, s.Xx100p, s.Xmax540, s.paddingX16, s.marginT16]
13
13
  page.card = [s.col_cX, s.Yc, s.paddingX16]
14
14
 
15
15
  return page
@@ -21,7 +21,7 @@ export const getRoot = (s, c, t) => {
21
21
  root.cards.text.title = [s.textS, s.fs16, s.lh24, s.fw700]
22
22
  root.cards.text.description = [s.textS, s.fs16, s.lh20, s.fw400]
23
23
  root.cards.text.label = [s.textS, s.fs16, s.lh24, s.fw600]
24
- root.cards.box = [s.col, s.X]
24
+ root.cards.box = [s.col, s.X100p]
25
25
  root.cards.top = [s.col, s.paddingT16, s.paddingB8]
26
26
  root.cards.title = [...root.cards.text.title, c.item_NN_B400]
27
27
  root.cards.description = [...root.cards.text.description, c.item_NN_B400]
@@ -0,0 +1,22 @@
1
+ class Pulse {
2
+
3
+ get = async () => this.formatDate(new Date())
4
+
5
+ formatDate = date => {
6
+ const parts = new Intl.DateTimeFormat('en-GB', {
7
+ weekday: 'short',
8
+ day: '2-digit',
9
+ month: 'short',
10
+ year: 'numeric',
11
+ hour: '2-digit',
12
+ minute: '2-digit',
13
+ hour12: false,
14
+ timeZone: 'UTC'
15
+ }).formatToParts(date)
16
+
17
+ const value = type => parts.find(part => part.type === type)?.value ?? ''
18
+ return `${value('weekday')} ${value('day')} ${value('month')} ${value('year')} ${value('hour')}:${value('minute')}`
19
+ }
20
+ }
21
+
22
+ export { Pulse }
@@ -1,11 +1,21 @@
1
1
  import { Router } from '#@/index.dep.js'
2
2
  import { middleware } from '#@/server/middleware.js'
3
3
  import { resolver } from '#@/server/resolvers.js'
4
+ import { Pulse } from '#@/modules/pulse.js'
4
5
  import { sync } from '#@/modules/sync.server.js'
5
6
  import { clone } from '#@/modules/clone.js'
6
7
 
7
8
  const rs = {}
8
9
 
10
+ // pulse
11
+
12
+ rs.pulse = {}
13
+ rs.pulse.method = 'get'
14
+ rs.pulse.name = 'pulse'
15
+ rs.pulse.path = '/pulse'
16
+ rs.pulse.resolve = async (...args) => await resolver.resolve(new Pulse().get, rs.pulse.path, ...args)
17
+ rs.pulse.funcs = [ rs.pulse.resolve ]
18
+
9
19
  rs.sync_current = {}
10
20
  rs.sync_current.method = 'post'
11
21
  rs.sync_current.name = 'sync_current'
@@ -55,4 +65,4 @@ rs.clone.funcs = [rs.clone.auth, rs.clone.resolve]
55
65
 
56
66
  const router = Router()
57
67
  for ( const r of Object.values(rs)) router[r.method](r.path, ...r.funcs)
58
- export { router, rs as routes }
68
+ export { router, rs as routes }
@@ -15,6 +15,12 @@
15
15
  "input": "media/trademarks",
16
16
  "output": "get/media/trademarks"
17
17
  },
18
+ "media/graphic": {
19
+ "worker": "media_graphic",
20
+ "input": "media/graphic",
21
+ "output": "get/media/graphic",
22
+ "file": "get/data/root/statics/@graphic.json"
23
+ },
18
24
  "media/images": {
19
25
  "worker": "media_images",
20
26
  "input": "media/images",
@@ -25,6 +31,11 @@
25
31
  "input": "media/covers",
26
32
  "output": "get/media/covers"
27
33
  },
34
+ "docs/pdf": {
35
+ "worker": "docs_pdf",
36
+ "input": "docs/pdf",
37
+ "output": "get/docs/pdf"
38
+ },
28
39
  "data/root": {
29
40
  "worker": "data_statics",
30
41
  "input": "data/root",
@@ -51,7 +62,8 @@
51
62
  "isPublisher": true,
52
63
  "worker": "tabulars",
53
64
  "input":"tabulars_data_content",
54
- "output": "get/data/content"
65
+ "output": "get/data/content",
66
+ "outputModules": "get/data/modules"
55
67
  }
56
68
  }
57
- }
69
+ }
@@ -0,0 +1,33 @@
1
+ export const help = `
2
+ @fullstackunicorn/create-root
3
+
4
+ Create a new FullstackUnicorn root scaffold.
5
+
6
+ Usage:
7
+ yarn create @fullstackunicorn/root <foldername>
8
+
9
+ Example:
10
+ yarn create @fullstackunicorn/root example.project.dev
11
+
12
+ What this command does:
13
+ - creates <foldername> in the current working directory
14
+ - copies the bundled root scaffold
15
+ - does not initialize dependencies
16
+ - does not copy client data
17
+ - does not run root upgrade
18
+
19
+ After create-root:
20
+ 1. copy the prepared client root.config.app.js into <foldername>/root.config.app.js
21
+ 2. copy prepared icons into <foldername>/workspace/volume/media/icons/
22
+ 3. copy prepared trademarks into <foldername>/workspace/volume/media/trademarks/
23
+ 4. cd <foldername>
24
+ 5. yarn initialize:all
25
+ 6. yarn root upgrade
26
+
27
+ AI safety rules:
28
+ - always ask for the target parent path before running this command
29
+ - always ask for the client data folder before initialization or root upgrade
30
+ - the client data folder must contain root.config.app.js, icons/, and trademarks/
31
+ - do not run this command with --help, -help, or help as a folder name
32
+ - options beginning with "-" are not valid folder names
33
+ `