create-weave-backend-app 2.6.0 → 2.7.0
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/dist/{create-app-viPT34gS.js → create-app-d0GGTvpm.js} +49 -23
- package/dist/create-app-d0GGTvpm.js.map +1 -0
- package/dist/create-app.js +1 -1
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/template/+express+azure-web-pubsub/fonts/Impact.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/Verdana-Bold.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/Verdana-BoldItalic.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/Verdana-Italic.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/Verdana.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/inter-bold.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/inter-italic-bold.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/inter-italic.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/inter-regular.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/sansita-bold.ttf +0 -0
- package/template/+express+azure-web-pubsub/fonts/sansita-regular.ttf +0 -0
- package/template/+express+azure-web-pubsub/nodemon.json +6 -0
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/getImage.ts +2 -2
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/getRoom.ts +33 -0
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/getRoomConnect.ts +1 -1
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/postExportToImage.ts +98 -0
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/postRemoveBackground.ts +1 -2
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/workers/exportToImage.ts +52 -0
- package/template/+express+azure-web-pubsub/src/api/v1/controllers/workers/types.ts +5 -0
- package/template/+express+azure-web-pubsub/src/api/v1/router.ts +6 -2
- package/template/+express+azure-web-pubsub/src/canvas/fonts.ts +167 -0
- package/template/+express+azure-web-pubsub/src/canvas/nodes/color-token/color-token.ts +155 -0
- package/template/+express+azure-web-pubsub/src/canvas/types.ts +13 -0
- package/template/+express+azure-web-pubsub/src/canvas/weave.ts +207 -0
- package/template/+express+azure-web-pubsub/src/store.ts +9 -3
- package/template/+express+azure-web-pubsub/src/utils.ts +23 -0
- package/template/+express+azure-web-pubsub/src/workers/workers.ts +40 -0
- package/template/+express+azure-web-pubsub/tsconfig.json +1 -1
- package/template/+express+websockets/example.gitignore +5 -5
- package/template/+express+websockets/fonts/Impact.ttf +0 -0
- package/template/+express+websockets/fonts/Verdana-Bold.ttf +0 -0
- package/template/+express+websockets/fonts/Verdana-BoldItalic.ttf +0 -0
- package/template/+express+websockets/fonts/Verdana-Italic.ttf +0 -0
- package/template/+express+websockets/fonts/Verdana.ttf +0 -0
- package/template/+express+websockets/fonts/inter-bold.ttf +0 -0
- package/template/+express+websockets/fonts/inter-italic-bold.ttf +0 -0
- package/template/+express+websockets/fonts/inter-italic.ttf +0 -0
- package/template/+express+websockets/fonts/inter-regular.ttf +0 -0
- package/template/+express+websockets/fonts/sansita-bold.ttf +0 -0
- package/template/+express+websockets/fonts/sansita-regular.ttf +0 -0
- package/template/+express+websockets/nodemon.json +6 -0
- package/template/+express+websockets/src/api/v1/controllers/getImage.ts +2 -2
- package/template/+express+websockets/src/api/v1/controllers/getRoom.ts +33 -0
- package/template/+express+websockets/src/api/v1/controllers/postExportToImage.ts +96 -0
- package/template/+express+websockets/src/api/v1/controllers/postRemoveBackground.ts +1 -2
- package/template/+express+websockets/src/api/v1/controllers/workers/exportToImage.ts +48 -0
- package/template/+express+websockets/src/api/v1/controllers/workers/types.ts +1 -0
- package/template/+express+websockets/src/api/v1/router.ts +7 -1
- package/template/+express+websockets/src/canvas/fonts.ts +163 -0
- package/template/+express+websockets/src/canvas/nodes/color-token/color-token.ts +151 -0
- package/template/+express+websockets/src/canvas/types.ts +13 -0
- package/template/+express+websockets/src/canvas/weave.ts +203 -0
- package/template/+express+websockets/src/server.ts +4 -0
- package/template/+express+websockets/src/store.ts +2 -2
- package/template/+express+websockets/src/utils.ts +23 -0
- package/template/+express+websockets/src/workers/workers.ts +36 -0
- package/template/+express+websockets/tsconfig.json +1 -1
- package/template/package.json +7 -1
- package/dist/create-app-viPT34gS.js.map +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { parentPort } from 'worker_threads'
|
|
6
|
+
import sharp from 'sharp'
|
|
7
|
+
import { WEAVE_EXPORT_FORMATS } from '@inditextech/weave-types'
|
|
8
|
+
import { renderWeaveRoom } from '../../../../canvas/weave.js'
|
|
9
|
+
|
|
10
|
+
parentPort?.on('message', async ({ config, roomData, nodes, options }) => {
|
|
11
|
+
const { instance, destroy } = await renderWeaveRoom(config, roomData)
|
|
12
|
+
|
|
13
|
+
const { composites, width, height } = await instance.exportNodesServerSide(
|
|
14
|
+
nodes,
|
|
15
|
+
(nodes) => nodes,
|
|
16
|
+
{
|
|
17
|
+
format: options.format,
|
|
18
|
+
padding: options.padding,
|
|
19
|
+
pixelRatio: options.pixelRatio,
|
|
20
|
+
backgroundColor: options.backgroundColor,
|
|
21
|
+
quality: options.quality
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
destroy()
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const composedImage = sharp({
|
|
29
|
+
create: {
|
|
30
|
+
width,
|
|
31
|
+
height,
|
|
32
|
+
channels: 4,
|
|
33
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
34
|
+
}
|
|
35
|
+
}).composite(composites)
|
|
36
|
+
|
|
37
|
+
let imagePipeline = composedImage
|
|
38
|
+
if (options.format === WEAVE_EXPORT_FORMATS.JPEG) {
|
|
39
|
+
imagePipeline = composedImage.jpeg({
|
|
40
|
+
quality: (options.quality ?? 0.8) * 100
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
if (options.format === WEAVE_EXPORT_FORMATS.PNG) {
|
|
44
|
+
imagePipeline = composedImage.png()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const imageBuffer = await imagePipeline.toBuffer()
|
|
48
|
+
parentPort?.postMessage(imageBuffer, [imageBuffer.buffer as ArrayBuffer])
|
|
49
|
+
} catch (error) {
|
|
50
|
+
parentPort?.postMessage((error as Error).message)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Express, Router } from 'express'
|
|
2
2
|
import multer from 'multer'
|
|
3
|
-
import { getAzureWebPubsubServer } from '@/store'
|
|
4
3
|
import { getHealthController } from './controllers/getHealth.js'
|
|
5
4
|
import { getRoomConnectController } from './controllers/getRoomConnect.js'
|
|
6
5
|
import { getImageController } from './controllers/getImage.js'
|
|
@@ -8,6 +7,8 @@ import { postUploadImageController } from './controllers/postUploadImage.js'
|
|
|
8
7
|
import { delImageController } from './controllers/delImage.js'
|
|
9
8
|
import { getImagesController } from './controllers/getImages.js'
|
|
10
9
|
import { postRemoveBackgroundController } from './controllers/postRemoveBackground.js'
|
|
10
|
+
import { postExportToImageController } from './controllers/postExportToImage.js'
|
|
11
|
+
import { getRoomController } from './controllers/getRoom.js'
|
|
11
12
|
|
|
12
13
|
const router: Router = Router()
|
|
13
14
|
|
|
@@ -25,7 +26,7 @@ export function setupApiV1Router(app: Express) {
|
|
|
25
26
|
router.get(`/health`, getHealthController())
|
|
26
27
|
|
|
27
28
|
// Room handling API
|
|
28
|
-
router.
|
|
29
|
+
router.get(`/rooms/:roomId`, getRoomController())
|
|
29
30
|
router.get(`/rooms/:roomId/connect`, getRoomConnectController())
|
|
30
31
|
|
|
31
32
|
// Images handling API
|
|
@@ -42,5 +43,8 @@ export function setupApiV1Router(app: Express) {
|
|
|
42
43
|
)
|
|
43
44
|
router.delete(`/rooms/:roomId/images/:imageId`, delImageController())
|
|
44
45
|
|
|
46
|
+
// Render Canvas API
|
|
47
|
+
router.post(`/export`, postExportToImageController())
|
|
48
|
+
|
|
45
49
|
app.use('/api/v1', router)
|
|
46
50
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { registerFont } from 'canvas'
|
|
7
|
+
import { FontLibrary } from 'skia-canvas'
|
|
8
|
+
import { CanvasFont, SkiaFont } from './types.js'
|
|
9
|
+
|
|
10
|
+
let registered = false
|
|
11
|
+
|
|
12
|
+
export const registerSkiaFonts = () => {
|
|
13
|
+
if (registered) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
FontLibrary.reset()
|
|
18
|
+
|
|
19
|
+
const fonts: SkiaFont[] = [
|
|
20
|
+
// Arial font family
|
|
21
|
+
{
|
|
22
|
+
family: 'Arial',
|
|
23
|
+
paths: [
|
|
24
|
+
path.resolve(process.cwd(), 'fonts/ARIALI.ttf'),
|
|
25
|
+
path.resolve(process.cwd(), 'fonts/ARIAL.ttf')
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
// NotoSansMono font family
|
|
29
|
+
{
|
|
30
|
+
family: 'NotoSansMono',
|
|
31
|
+
// paths: [path.resolve(process.cwd(), "fonts/NotoSansMono-Italic.ttf")],
|
|
32
|
+
paths: [path.resolve(process.cwd(), 'fonts/NotoSansMono-Regular.ttf')]
|
|
33
|
+
},
|
|
34
|
+
// Impact font family
|
|
35
|
+
{
|
|
36
|
+
family: 'Impact',
|
|
37
|
+
paths: [path.resolve(process.cwd(), 'fonts/Impact.ttf')]
|
|
38
|
+
},
|
|
39
|
+
// Verdana font family
|
|
40
|
+
{
|
|
41
|
+
family: 'Verdana',
|
|
42
|
+
paths: [
|
|
43
|
+
path.resolve(process.cwd(), 'fonts/Verdana-Italic.ttf'),
|
|
44
|
+
path.resolve(process.cwd(), 'fonts/Verdana-Bold.ttf'),
|
|
45
|
+
path.resolve(process.cwd(), 'fonts/Verdana-BoldItalic.ttf'),
|
|
46
|
+
path.resolve(process.cwd(), 'fonts/Verdana.ttf')
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
// Inter font family
|
|
50
|
+
{
|
|
51
|
+
family: 'Inter',
|
|
52
|
+
paths: [
|
|
53
|
+
path.resolve(process.cwd(), 'fonts/inter-bold.ttf'),
|
|
54
|
+
path.resolve(process.cwd(), 'fonts/inter-italic.ttf'),
|
|
55
|
+
path.resolve(process.cwd(), 'fonts/inter-italic-bold.ttf'),
|
|
56
|
+
path.resolve(process.cwd(), 'fonts/inter-regular.ttf')
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
// Sansita font family
|
|
60
|
+
{
|
|
61
|
+
family: 'Sansita',
|
|
62
|
+
paths: [
|
|
63
|
+
path.resolve(process.cwd(), 'fonts/sansita-bold.ttf'),
|
|
64
|
+
path.resolve(process.cwd(), 'fonts/sansita-regular.ttf')
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
for (const font of fonts) {
|
|
70
|
+
FontLibrary.use(font.family, font.paths)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
registered = true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const registerCanvasFonts = () => {
|
|
77
|
+
const fonts: CanvasFont[] = [
|
|
78
|
+
// Impact font family
|
|
79
|
+
{
|
|
80
|
+
path: path.resolve(process.cwd(), 'fonts/Impact.ttf'),
|
|
81
|
+
fontFace: {
|
|
82
|
+
family: 'Impact',
|
|
83
|
+
weight: '400',
|
|
84
|
+
style: 'normal'
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
// Verdana font family
|
|
88
|
+
{
|
|
89
|
+
path: path.resolve(process.cwd(), 'fonts/Verdana.ttf'),
|
|
90
|
+
fontFace: {
|
|
91
|
+
family: 'Verdana',
|
|
92
|
+
weight: '400',
|
|
93
|
+
style: 'normal'
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
path: path.resolve(process.cwd(), 'fonts/Verdana-Bold.ttf'),
|
|
98
|
+
fontFace: {
|
|
99
|
+
family: 'Verdana',
|
|
100
|
+
weight: '700',
|
|
101
|
+
style: 'normal'
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
path: path.resolve(process.cwd(), 'fonts/Verdana-Italic.ttf'),
|
|
106
|
+
fontFace: {
|
|
107
|
+
family: 'Verdana',
|
|
108
|
+
weight: '400',
|
|
109
|
+
style: 'italic'
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
path: path.resolve(process.cwd(), 'fonts/Verdana-BoldItalic.ttf'),
|
|
114
|
+
fontFace: {
|
|
115
|
+
family: 'Verdana',
|
|
116
|
+
weight: '700',
|
|
117
|
+
style: 'italic'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
// Inter font family
|
|
121
|
+
{
|
|
122
|
+
path: path.resolve(process.cwd(), 'fonts/inter-regular.ttf'),
|
|
123
|
+
fontFace: {
|
|
124
|
+
family: 'Inter',
|
|
125
|
+
weight: '400',
|
|
126
|
+
style: 'normal'
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
path: path.resolve(process.cwd(), 'fonts/inter-bold.ttf'),
|
|
131
|
+
fontFace: {
|
|
132
|
+
family: 'Inter',
|
|
133
|
+
weight: '700',
|
|
134
|
+
style: 'normal'
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
path: path.resolve(process.cwd(), 'fonts/inter-italic.ttf'),
|
|
139
|
+
fontFace: {
|
|
140
|
+
family: 'Inter',
|
|
141
|
+
weight: '400',
|
|
142
|
+
style: 'italic'
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
path: path.resolve(process.cwd(), 'fonts/inter-italic-bold.ttf'),
|
|
147
|
+
fontFace: {
|
|
148
|
+
family: 'Inter',
|
|
149
|
+
weight: '700',
|
|
150
|
+
style: 'italic'
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
// Sansita font family
|
|
154
|
+
{
|
|
155
|
+
path: path.resolve(process.cwd(), 'fonts/sansita-regular.ttf'),
|
|
156
|
+
fontFace: {
|
|
157
|
+
family: 'Sansita',
|
|
158
|
+
weight: '400',
|
|
159
|
+
style: 'normal'
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for (const font of fonts) {
|
|
165
|
+
registerFont(font.path, font.fontFace)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import Konva from 'konva'
|
|
6
|
+
import { WeaveNode } from '@inditextech/weave-sdk/server'
|
|
7
|
+
import {
|
|
8
|
+
WeaveElementAttributes,
|
|
9
|
+
WeaveElementInstance
|
|
10
|
+
} from '@inditextech/weave-types'
|
|
11
|
+
|
|
12
|
+
export const COLOR_TOKEN_NODE_TYPE = 'color-token'
|
|
13
|
+
|
|
14
|
+
export class ColorTokenNode extends WeaveNode {
|
|
15
|
+
protected nodeType = COLOR_TOKEN_NODE_TYPE
|
|
16
|
+
|
|
17
|
+
onRender(props: WeaveElementAttributes) {
|
|
18
|
+
const { id } = props
|
|
19
|
+
|
|
20
|
+
const colorTokenColor = props.colorToken ?? '#DEFFA0'
|
|
21
|
+
|
|
22
|
+
const colorTokenParams = {
|
|
23
|
+
...props
|
|
24
|
+
}
|
|
25
|
+
delete colorTokenParams.zIndex
|
|
26
|
+
|
|
27
|
+
const colorTokenNode = new Konva.Group({
|
|
28
|
+
...colorTokenParams,
|
|
29
|
+
width: colorTokenParams.width,
|
|
30
|
+
height: colorTokenParams.height,
|
|
31
|
+
name: 'node'
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.setupDefaultNodeAugmentation(colorTokenNode)
|
|
35
|
+
|
|
36
|
+
const internalRect = new Konva.Rect({
|
|
37
|
+
groupId: id,
|
|
38
|
+
nodeId: id,
|
|
39
|
+
id: `${id}-colorToken`,
|
|
40
|
+
x: 0,
|
|
41
|
+
y: 0,
|
|
42
|
+
fill: '#FFFFFFFF',
|
|
43
|
+
width: colorTokenParams.width,
|
|
44
|
+
height: colorTokenParams.height,
|
|
45
|
+
strokeEnabled: false
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
colorTokenNode.add(internalRect)
|
|
49
|
+
|
|
50
|
+
const internalRect2 = new Konva.Rect({
|
|
51
|
+
id: `${id}-colorToken-1`,
|
|
52
|
+
groupId: id,
|
|
53
|
+
nodeId: id,
|
|
54
|
+
x: 0,
|
|
55
|
+
y: 0,
|
|
56
|
+
fill: colorTokenColor,
|
|
57
|
+
strokeWidth: 0,
|
|
58
|
+
strokeEnabled: false,
|
|
59
|
+
width: colorTokenParams.width,
|
|
60
|
+
height: (colorTokenParams.height ?? 0) - 60,
|
|
61
|
+
listening: false,
|
|
62
|
+
draggable: false
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
colorTokenNode.add(internalRect2)
|
|
66
|
+
|
|
67
|
+
const internalText = new Konva.Text({
|
|
68
|
+
id: `${id}-colorToken-code`,
|
|
69
|
+
groupId: id,
|
|
70
|
+
nodeId: id,
|
|
71
|
+
x: 20,
|
|
72
|
+
y: 260,
|
|
73
|
+
fontSize: 20,
|
|
74
|
+
fontFamily: 'Inter, sans-serif',
|
|
75
|
+
fill: '#CCCCCCFF',
|
|
76
|
+
strokeEnabled: false,
|
|
77
|
+
stroke: '#000000FF',
|
|
78
|
+
strokeWidth: 1,
|
|
79
|
+
text: `${colorTokenColor}`,
|
|
80
|
+
width: (colorTokenParams.width ?? 0) - 40,
|
|
81
|
+
height: 20,
|
|
82
|
+
align: 'left',
|
|
83
|
+
listening: false,
|
|
84
|
+
draggable: false
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
colorTokenNode.add(internalText)
|
|
88
|
+
|
|
89
|
+
const border = new Konva.Rect({
|
|
90
|
+
groupId: id,
|
|
91
|
+
nodeId: id,
|
|
92
|
+
id: `${id}-colorToken-border`,
|
|
93
|
+
x: 0,
|
|
94
|
+
y: 0,
|
|
95
|
+
fill: 'transparent',
|
|
96
|
+
width: colorTokenParams.width,
|
|
97
|
+
height: colorTokenParams.height,
|
|
98
|
+
strokeScaleEnabled: true,
|
|
99
|
+
stroke: 'black',
|
|
100
|
+
strokeWidth: 1
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
colorTokenNode.add(border)
|
|
104
|
+
border.moveToTop()
|
|
105
|
+
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
;(colorTokenNode as any).getTransformerProperties = () => {
|
|
108
|
+
const baseConfig = this.defaultGetTransformerProperties({})
|
|
109
|
+
return {
|
|
110
|
+
...baseConfig,
|
|
111
|
+
resizeEnabled: false,
|
|
112
|
+
enabledAnchors: [] as string[],
|
|
113
|
+
borderStrokeWidth: 2,
|
|
114
|
+
padding: 0
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
;(colorTokenNode as any).allowedAnchors = () => {
|
|
120
|
+
return []
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.setupDefaultNodeEvents(colorTokenNode)
|
|
124
|
+
|
|
125
|
+
return colorTokenNode
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onUpdate(
|
|
129
|
+
nodeInstance: WeaveElementInstance,
|
|
130
|
+
nextProps: WeaveElementAttributes
|
|
131
|
+
) {
|
|
132
|
+
const { id, colorToken } = nextProps
|
|
133
|
+
|
|
134
|
+
const colorTokenNode = nodeInstance as Konva.Group
|
|
135
|
+
|
|
136
|
+
const nodeInstanceZIndex = nodeInstance.zIndex()
|
|
137
|
+
nodeInstance.setAttrs({
|
|
138
|
+
...nextProps,
|
|
139
|
+
zIndex: nodeInstanceZIndex
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const colorTokenColor = colorToken ?? '#DEFFA0'
|
|
143
|
+
|
|
144
|
+
const colorTokenNode1 = colorTokenNode.findOne(`#${id}-colorToken-1`)
|
|
145
|
+
if (colorTokenNode1) {
|
|
146
|
+
colorTokenNode1.setAttrs({
|
|
147
|
+
fill: colorTokenColor
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
const colorTokenCode = colorTokenNode.findOne(`#${id}-colorToken-code`)
|
|
151
|
+
if (colorTokenCode) {
|
|
152
|
+
colorTokenCode.setAttr('text', `${colorTokenColor}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { WeaveStoreStandalone } from '@inditextech/weave-store-standalone/server'
|
|
6
|
+
import {
|
|
7
|
+
Weave,
|
|
8
|
+
WeaveStageNode,
|
|
9
|
+
WeaveLayerNode,
|
|
10
|
+
WeaveGroupNode,
|
|
11
|
+
WeaveRectangleNode,
|
|
12
|
+
WeaveEllipseNode,
|
|
13
|
+
WeaveLineNode,
|
|
14
|
+
WeaveTextNode,
|
|
15
|
+
WeaveImageNode,
|
|
16
|
+
WeaveVideoNode,
|
|
17
|
+
WeaveStarNode,
|
|
18
|
+
WeaveArrowNode,
|
|
19
|
+
WeaveRegularPolygonNode,
|
|
20
|
+
WeaveFrameNode,
|
|
21
|
+
WeaveStrokeNode,
|
|
22
|
+
// setupSkiaBackend,
|
|
23
|
+
setupCanvasBackend
|
|
24
|
+
} from '@inditextech/weave-sdk/server'
|
|
25
|
+
import { ColorTokenNode } from './nodes/color-token/color-token.js'
|
|
26
|
+
import { isAbsoluteUrl, stripOrigin } from '../utils.js'
|
|
27
|
+
import { ServiceConfig } from '../types.js'
|
|
28
|
+
import {
|
|
29
|
+
// registerSkiaFonts,
|
|
30
|
+
registerCanvasFonts
|
|
31
|
+
} from './fonts.js'
|
|
32
|
+
|
|
33
|
+
export type RenderWeaveRoom = {
|
|
34
|
+
instance: Weave
|
|
35
|
+
destroy: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const renderWeaveRoom = (
|
|
39
|
+
config: ServiceConfig,
|
|
40
|
+
roomData: string
|
|
41
|
+
): Promise<RenderWeaveRoom> => {
|
|
42
|
+
let weave: Weave | undefined = undefined
|
|
43
|
+
|
|
44
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
45
|
+
return new Promise(async (resolve) => {
|
|
46
|
+
const destroyWeaveRoom = () => {
|
|
47
|
+
if (weave) {
|
|
48
|
+
weave.destroy()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Setup Skia backend
|
|
53
|
+
// registerSkiaFonts();
|
|
54
|
+
// await setupSkiaBackend();
|
|
55
|
+
|
|
56
|
+
// Setup Canvas backend
|
|
57
|
+
registerCanvasFonts()
|
|
58
|
+
await setupCanvasBackend()
|
|
59
|
+
|
|
60
|
+
const store = new WeaveStoreStandalone(
|
|
61
|
+
{
|
|
62
|
+
roomData
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
getUser: () => {
|
|
66
|
+
return {
|
|
67
|
+
id: 'user-dummy',
|
|
68
|
+
name: 'User Dummy',
|
|
69
|
+
email: 'user@mail.com'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
weave = new Weave(
|
|
76
|
+
{
|
|
77
|
+
store,
|
|
78
|
+
nodes: getNodes(config),
|
|
79
|
+
actions: [],
|
|
80
|
+
plugins: [],
|
|
81
|
+
fonts: [],
|
|
82
|
+
logger: {
|
|
83
|
+
level: 'info'
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
container: undefined,
|
|
88
|
+
width: 800,
|
|
89
|
+
height: 600
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
let roomLoaded = false
|
|
94
|
+
|
|
95
|
+
const checkIfRoomLoaded = () => {
|
|
96
|
+
if (!weave) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!weave.getStage()) {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (roomLoaded && weave.asyncElementsLoaded()) {
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
weave.addEventListener('onRoomLoaded', async (status: boolean) => {
|
|
112
|
+
if (!weave) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!weave.getStage()) {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (status) {
|
|
121
|
+
roomLoaded = true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (checkIfRoomLoaded()) {
|
|
125
|
+
resolve({ instance: weave, destroy: destroyWeaveRoom })
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
weave.addEventListener('onAsyncElementChange', () => {
|
|
130
|
+
if (!weave) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!weave.getStage()) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (checkIfRoomLoaded()) {
|
|
139
|
+
resolve({ instance: weave, destroy: destroyWeaveRoom })
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
weave.start()
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const getNodes = (config: ServiceConfig) => {
|
|
148
|
+
return [
|
|
149
|
+
new WeaveStageNode(),
|
|
150
|
+
new WeaveLayerNode(),
|
|
151
|
+
new WeaveGroupNode(),
|
|
152
|
+
new WeaveRectangleNode(),
|
|
153
|
+
new WeaveEllipseNode(),
|
|
154
|
+
new WeaveLineNode(),
|
|
155
|
+
new WeaveStrokeNode(),
|
|
156
|
+
new WeaveTextNode(),
|
|
157
|
+
new WeaveImageNode({
|
|
158
|
+
config: {
|
|
159
|
+
urlTransformer: (url: string) => {
|
|
160
|
+
const isAbsolute = isAbsoluteUrl(url)
|
|
161
|
+
|
|
162
|
+
let relativeUrl = url
|
|
163
|
+
if (isAbsolute) {
|
|
164
|
+
relativeUrl = stripOrigin(url)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const transformedUrl = relativeUrl.replace('/weavebff', '')
|
|
168
|
+
return `http://localhost:${config.service.port}${transformedUrl}`
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
new WeaveVideoNode({
|
|
173
|
+
config: {
|
|
174
|
+
urlTransformer: (url: string) => {
|
|
175
|
+
const isAbsolute = isAbsoluteUrl(url)
|
|
176
|
+
|
|
177
|
+
let relativeUrl = url
|
|
178
|
+
if (isAbsolute) {
|
|
179
|
+
relativeUrl = stripOrigin(url)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const transformedUrl = relativeUrl.replace('/weavebff', '')
|
|
183
|
+
return `http://localhost:${config.service.port}${transformedUrl}`
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}),
|
|
187
|
+
new WeaveStarNode(),
|
|
188
|
+
new WeaveArrowNode(),
|
|
189
|
+
new WeaveRegularPolygonNode(),
|
|
190
|
+
new WeaveFrameNode({
|
|
191
|
+
config: {
|
|
192
|
+
fontFamily: "'Inter', sans-serif",
|
|
193
|
+
fontStyle: 'normal',
|
|
194
|
+
fontSize: 14,
|
|
195
|
+
borderColor: '#9E9994',
|
|
196
|
+
fontColor: '#757575',
|
|
197
|
+
titleMargin: 5,
|
|
198
|
+
transform: {
|
|
199
|
+
rotateEnabled: false,
|
|
200
|
+
resizeEnabled: false,
|
|
201
|
+
enabledAnchors: [] as string[]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}),
|
|
205
|
+
new ColorTokenNode()
|
|
206
|
+
]
|
|
207
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import fs from 'fs/promises'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import { WeaveAzureWebPubsubServer } from '@inditextech/weave-store-azure-web-pubsub/server'
|
|
4
|
-
import { createFolder, existsFolder } from '@/utils'
|
|
5
|
+
import { createFolder, existsFolder } from '@/utils.js'
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
const __dirname = path.dirname(__filename)
|
|
5
9
|
|
|
6
10
|
const endpoint = process.env.WEAVE_AZURE_WEB_PUBSUB_ENDPOINT
|
|
7
11
|
const key = process.env.WEAVE_AZURE_WEB_PUBSUB_KEY
|
|
@@ -23,9 +27,8 @@ export const getAzureWebPubsubServer = () => {
|
|
|
23
27
|
|
|
24
28
|
export const setupStore = () => {
|
|
25
29
|
azureWebPubsubServer = new WeaveAzureWebPubsubServer({
|
|
26
|
-
|
|
30
|
+
pubSubConfig: {
|
|
27
31
|
endpoint,
|
|
28
|
-
key,
|
|
29
32
|
hubName
|
|
30
33
|
},
|
|
31
34
|
fetchRoom: async (docName: string) => {
|
|
@@ -48,6 +51,8 @@ export const setupStore = () => {
|
|
|
48
51
|
actualState: Uint8Array<ArrayBufferLike>
|
|
49
52
|
) => {
|
|
50
53
|
try {
|
|
54
|
+
console.log(`Persisting room ${docName}...`)
|
|
55
|
+
|
|
51
56
|
const roomsFolder = path.join(__dirname, 'rooms')
|
|
52
57
|
|
|
53
58
|
if (!(await existsFolder(roomsFolder))) {
|
|
@@ -68,6 +73,7 @@ export const setupStore = () => {
|
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
const roomsFile = path.join(roomsFolder, docName)
|
|
76
|
+
console.log(`Persisting room ${roomsFile}...`)
|
|
71
77
|
await fs.writeFile(roomsFile, actualState)
|
|
72
78
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
73
79
|
} catch (_) {
|
|
@@ -17,6 +17,20 @@ export const getFileContents = async (
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export const existFile = async (filePath: string) => {
|
|
21
|
+
try {
|
|
22
|
+
const stats = await fs.stat(filePath)
|
|
23
|
+
return stats.isFile()
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(
|
|
26
|
+
`Error reading file ${filePath}: ${
|
|
27
|
+
err instanceof Error ? err.message : err
|
|
28
|
+
}`
|
|
29
|
+
)
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
export const existsFolder = async (folderPath: string) => {
|
|
21
35
|
try {
|
|
22
36
|
const stats = await fs.stat(folderPath)
|
|
@@ -30,3 +44,12 @@ export const existsFolder = async (folderPath: string) => {
|
|
|
30
44
|
export const createFolder = async (folderPath: string): Promise<void> => {
|
|
31
45
|
await fs.mkdir(folderPath, { recursive: true })
|
|
32
46
|
}
|
|
47
|
+
|
|
48
|
+
export function isAbsoluteUrl(url: string): boolean {
|
|
49
|
+
return /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(url)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function stripOrigin(url: string): string {
|
|
53
|
+
const parsedUrl = new URL(url)
|
|
54
|
+
return parsedUrl.pathname + parsedUrl.search + parsedUrl.hash
|
|
55
|
+
}
|