@rws-framework/client 2.23.7 → 2.25.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rws-framework/client",
3
3
  "private": false,
4
- "version": "2.23.7",
4
+ "version": "2.25.0",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
7
7
  "docs": "typedoc --tsconfig ./tsconfig.json"
@@ -41,7 +41,6 @@
41
41
  "reflect-metadata": "^0.2.2",
42
42
  "resolve-url-loader": "^5.0.0",
43
43
  "socket.io-client": "^4.7.2",
44
- "upload": "^1.3.2",
45
44
  "url-router": "^13.0.0",
46
45
  "uuid": "^9.0.1",
47
46
  "v4": "^0.0.1",
@@ -58,8 +58,9 @@ abstract class RWSViewComponent extends FoundationElement implements IRWSViewCom
58
58
  static _externalAttrs: { [key: string]: string[] } = {};
59
59
  static _verbose: boolean = false;
60
60
 
61
- private static FORCE_INJECT_STYLES?: string[] = [];
62
- private static FORCE_INJECT_MODE?: CSSInjectMode = 'adopted';
61
+ static FORCE_INJECT_STYLES?: string[] = [];
62
+ static FORCE_INJECT_MODE?: CSSInjectMode = 'adopted';
63
+ static FORCE_INJECT_MODE_PER_LINK?: Record<string, CSSInjectMode> = {};
63
64
 
64
65
  static _EVENTS = {
65
66
  component_define: 'rws:lifecycle:defineComponent',
@@ -271,6 +272,7 @@ abstract class RWSViewComponent extends FoundationElement implements IRWSViewCom
271
272
  protected async injectStyles(styleLinks: string[], mode: CSSInjectMode = 'adopted', maxDaysExp?: number) {
272
273
  // Create a bridge object that exposes the necessary properties
273
274
  const componentBridge = {
275
+ componentElement: this,
274
276
  shadowRoot: this.shadowRoot,
275
277
  indexedDBService: this.indexedDBService,
276
278
  $emit: this.$emit.bind(this)
@@ -1,7 +1,8 @@
1
1
  import { domEvents } from '../events';
2
2
  import IndexedDBService, { IndexedDBServiceInstance } from '../services/IndexedDBService';
3
+ import RWSViewComponent from './_component';
3
4
 
4
- type CSSInjectMode = 'adopted' | 'legacy' | 'both';
5
+ type CSSInjectMode = 'adopted' | 'legacy' | 'both' | 'style-element';
5
6
 
6
7
  const _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS = 1;
7
8
 
@@ -11,6 +12,7 @@ interface ICSSInjectionOptions {
11
12
  }
12
13
 
13
14
  interface ICSSInjectionComponent {
15
+ componentElement?: RWSViewComponent;
14
16
  shadowRoot: ShadowRoot | null;
15
17
  indexedDBService: IndexedDBServiceInstance;
16
18
  $emit(eventName: string): void;
@@ -44,7 +46,6 @@ export class CSSInjectionManager {
44
46
  styleLinks: string[],
45
47
  options: ICSSInjectionOptions = {}
46
48
  ): Promise<void> {
47
- const { mode = 'adopted', maxDaysExp } = options;
48
49
 
49
50
  if (!component.shadowRoot) {
50
51
  throw new Error('Component must have a shadow root for CSS injection');
@@ -63,11 +64,35 @@ export class CSSInjectionManager {
63
64
  transition: opacity 0.3s ease-in-out;
64
65
  }
65
66
  `);
67
+
66
68
  component.shadowRoot.adoptedStyleSheets = [
67
69
  transitionSheet,
68
70
  ...component.shadowRoot.adoptedStyleSheets,
69
71
  ];
70
72
 
73
+ const doneAdded = await CSSInjectionManager.addStyleSheets(component, styleLinks, options);
74
+
75
+ if (doneAdded) {
76
+ // Set opacity to 1 to fade in the component
77
+ const opacitySheet = new CSSStyleSheet();
78
+ await opacitySheet.replace(`
79
+ :host {
80
+ opacity: 1 !important;
81
+ }
82
+ `);
83
+ component.shadowRoot.adoptedStyleSheets = [
84
+ opacitySheet,
85
+ ...component.shadowRoot.adoptedStyleSheets,
86
+ ];
87
+
88
+ component.$emit(domEvents.loadedLinkedStyles);
89
+ }
90
+ }
91
+
92
+ private static async addStyleSheets(component: ICSSInjectionComponent, styleLinks: string[], options: ICSSInjectionOptions = {}): Promise<boolean>
93
+ {
94
+ const { mode = 'adopted', maxDaysExp } = options;
95
+
71
96
  let adoptedSheets: CSSStyleSheet[] = [];
72
97
  let doneAdded = false;
73
98
 
@@ -75,14 +100,21 @@ export class CSSInjectionManager {
75
100
  const cachedSheets: CSSStyleSheet[] = [];
76
101
  const uncachedLinks: string[] = [];
77
102
 
103
+ let hasCached = false;
104
+
78
105
  for (const styleLink of styleLinks) {
79
106
  if (CSSInjectionManager.CACHED_STYLES.has(styleLink)) {
80
107
  cachedSheets.push(CSSInjectionManager.CACHED_STYLES.get(styleLink)!);
108
+ hasCached = true;
81
109
  } else {
82
110
  uncachedLinks.push(styleLink);
83
111
  }
84
112
  }
85
113
 
114
+ if(hasCached){
115
+ CSSInjectionManager.setStylesOwner(component);
116
+ }
117
+
86
118
  // If we have cached styles, use them immediately
87
119
  if (cachedSheets.length > 0) {
88
120
  adoptedSheets.push(...cachedSheets);
@@ -92,9 +124,7 @@ export class CSSInjectionManager {
92
124
  // Only process uncached styles
93
125
  if (uncachedLinks.length > 0) {
94
126
  // Set this component as the owner if no owner exists yet
95
- if (!CSSInjectionManager.STYLES_OWNER_COMPONENT) {
96
- CSSInjectionManager.STYLES_OWNER_COMPONENT = component;
97
- }
127
+ CSSInjectionManager.setStylesOwner(component);
98
128
 
99
129
  const dbName = 'css-cache';
100
130
  const storeName = 'styles';
@@ -104,54 +134,41 @@ export class CSSInjectionManager {
104
134
  const maxAgeDays = maxAgeMs * maxDaysAge;
105
135
 
106
136
  for (const styleLink of uncachedLinks) {
107
- const loadPromise = new Promise<void>(async (resolve, reject) => {
108
- if (mode === 'legacy' || mode === 'both') {
109
- const link = document.createElement('link');
110
- link.rel = 'stylesheet';
111
- link.href = styleLink;
112
- component.shadowRoot!.appendChild(link);
113
-
114
- link.onload = () => {
115
- doneAdded = true;
116
-
117
- if(mode === 'legacy'){
137
+ const linkMode = Object.keys(RWSViewComponent.FORCE_INJECT_MODE_PER_LINK).includes(styleLink) ? RWSViewComponent.FORCE_INJECT_MODE_PER_LINK[styleLink] : mode;
138
+
139
+ const loadPromise = new Promise<void>(async (resolve) => {
140
+ try {
141
+ if (linkMode === 'legacy') {
142
+ await CSSInjectionManager.injectLegacyStyle(component, styleLink, () => {
143
+ doneAdded = true;
118
144
  resolve();
119
- }
120
- };
121
- }
122
-
123
- if (mode === 'adopted' || mode === 'both') {
124
- const entry = await component.indexedDBService.getFromDB(db, storeName, styleLink);
125
-
126
- let cssText: string | null = null;
127
-
128
- if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
129
- const expired = Date.now() - entry.timestamp > maxAgeDays;
130
- if (!expired) {
131
- cssText = entry.css;
132
- }
133
- }
134
-
135
- if (!cssText) {
136
- cssText = await fetch(styleLink).then(res => res.text());
137
- await component.indexedDBService.saveToDB(db, storeName, styleLink, {
138
- css: cssText,
139
- timestamp: Date.now()
140
145
  });
141
- console.log(`System saved stylesheet: ${styleLink} to IndexedDB`)
142
- }
143
-
144
- const sheet = new CSSStyleSheet();
145
- await sheet.replace(cssText);
146
-
147
- // Cache the stylesheet for future use
148
- CSSInjectionManager.CACHED_STYLES.set(styleLink, sheet);
149
-
150
- adoptedSheets.push(sheet);
151
-
152
- if(mode === 'adopted' || mode === 'both'){
146
+ } else if (linkMode === 'style-element') {
147
+ await CSSInjectionManager.injectStyleElement(component, styleLink, db, storeName, maxAgeDays);
148
+ doneAdded = true;
149
+ resolve();
150
+ } else if (linkMode === 'adopted') {
151
+ const sheet = await CSSInjectionManager.injectAdoptedStyle(component, styleLink, db, storeName, maxAgeDays);
152
+ adoptedSheets.push(sheet);
153
+ doneAdded = true;
154
+ resolve();
155
+ } else if (linkMode === 'both') {
156
+ // Handle both modes
157
+ const [sheet] = await Promise.all([
158
+ CSSInjectionManager.injectAdoptedStyle(component, styleLink, db, storeName, maxAgeDays),
159
+ new Promise<void>((resolveLegacy) => {
160
+ CSSInjectionManager.injectLegacyStyle(component, styleLink, () => {
161
+ resolveLegacy();
162
+ });
163
+ })
164
+ ]);
165
+ adoptedSheets.push(sheet);
166
+ doneAdded = true;
153
167
  resolve();
154
168
  }
169
+ } catch (error) {
170
+ console.error(`Failed to inject styles for ${styleLink}:`, error);
171
+ resolve();
155
172
  }
156
173
  });
157
174
 
@@ -170,23 +187,91 @@ export class CSSInjectionManager {
170
187
  doneAdded = true;
171
188
  }
172
189
 
173
- if (doneAdded) {
174
- // Set opacity to 1 to fade in the component
175
- const opacitySheet = new CSSStyleSheet();
176
- await opacitySheet.replace(`
177
- :host {
178
- opacity: 1 !important;
179
- }
180
- `);
181
- component.shadowRoot.adoptedStyleSheets = [
182
- opacitySheet,
183
- ...component.shadowRoot.adoptedStyleSheets,
184
- ];
190
+ return doneAdded;
191
+ }
185
192
 
186
- component.$emit(domEvents.loadedLinkedStyles);
193
+ private static async injectLegacyStyle(
194
+ component: ICSSInjectionComponent,
195
+ styleLink: string,
196
+ onLoad: () => void
197
+ ): Promise<void> {
198
+ const link = document.createElement('link');
199
+ link.rel = 'stylesheet';
200
+ link.href = styleLink;
201
+ link.onload = onLoad;
202
+ component.shadowRoot!.appendChild(link);
203
+ }
204
+
205
+ private static async injectStyleElement(
206
+ component: ICSSInjectionComponent,
207
+ styleLink: string,
208
+ db: IDBDatabase,
209
+ storeName: string,
210
+ maxAgeDays: number
211
+ ): Promise<void> {
212
+ const cssText = await CSSInjectionManager.getCachedOrFetchCSS(component, styleLink, db, storeName, maxAgeDays);
213
+
214
+ if (component.componentElement) {
215
+ const styleElement = document.createElement('style');
216
+ styleElement.textContent = cssText;
217
+ component.componentElement.appendChild(styleElement);
218
+ }
219
+ }
220
+
221
+ private static async injectAdoptedStyle(
222
+ component: ICSSInjectionComponent,
223
+ styleLink: string,
224
+ db: IDBDatabase,
225
+ storeName: string,
226
+ maxAgeDays: number
227
+ ): Promise<CSSStyleSheet> {
228
+ const cssText = await CSSInjectionManager.getCachedOrFetchCSS(component, styleLink, db, storeName, maxAgeDays);
229
+
230
+ const sheet = new CSSStyleSheet();
231
+ await sheet.replace(cssText);
232
+
233
+ // Cache the stylesheet for future use
234
+ CSSInjectionManager.CACHED_STYLES.set(styleLink, sheet);
235
+
236
+ return sheet;
237
+ }
238
+
239
+ private static async getCachedOrFetchCSS(
240
+ component: ICSSInjectionComponent,
241
+ styleLink: string,
242
+ db: IDBDatabase,
243
+ storeName: string,
244
+ maxAgeDays: number
245
+ ): Promise<string> {
246
+ const entry = await component.indexedDBService.getFromDB(db, storeName, styleLink);
247
+ let cssText: string | null = null;
248
+
249
+ if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
250
+ const expired = Date.now() - entry.timestamp > maxAgeDays;
251
+ if (!expired) {
252
+ cssText = entry.css;
253
+ }
254
+ }
255
+
256
+ if (!cssText) {
257
+ cssText = await fetch(styleLink).then(res => res.text());
258
+ await component.indexedDBService.saveToDB(db, storeName, styleLink, {
259
+ css: cssText,
260
+ timestamp: Date.now()
261
+ });
262
+ console.log(`System saved stylesheet: ${styleLink} to IndexedDB`);
263
+ }
264
+
265
+ return cssText;
266
+ }
267
+
268
+ private static setStylesOwner(component: ICSSInjectionComponent): void {
269
+ if (!CSSInjectionManager.STYLES_OWNER_COMPONENT) {
270
+ CSSInjectionManager.STYLES_OWNER_COMPONENT = component;
271
+ console.log({component});
187
272
  }
188
273
  }
189
274
  }
190
275
 
191
276
  export default CSSInjectionManager;
192
- export { CSSInjectMode, ICSSInjectionOptions, ICSSInjectionComponent };
277
+ export { CSSInjectMode, ICSSInjectionOptions, ICSSInjectionComponent };
package/src/index.ts CHANGED
@@ -25,12 +25,12 @@ import type IRWSUser from './types/IRWSUser';
25
25
  import type { IAssetShowOptions, IRWSViewComponent } from './components/_component';
26
26
  import type { RWSDecoratorOptions } from './components/_decorator';
27
27
  import type { DOMOutputType, TagsProcessorType } from './services/DOMService';
28
- import type { IBackendRoute, IHTTProute, IPrefixedHTTProutes } from './services/ApiService';
28
+ import type { IBackendRoute, IHTTProute, IPrefixedHTTProutes, UploadFunctionOptions } from './services/ApiService';
29
29
  import type IRWSConfig from './types/IRWSConfig';
30
30
  import type RWSNotify from './types/RWSNotify';
31
31
  import type { NotifyUiType, NotifyLogType } from './types/RWSNotify';
32
32
  import * as RWSEvents from './events';
33
-
33
+ import { CSSInjectMode } from './components/_css_injection';
34
34
  export default RWSClient;
35
35
 
36
36
  export {
@@ -75,7 +75,8 @@ export {
75
75
  RWSEvents
76
76
  };
77
77
 
78
- export type {
78
+ export type {
79
+ CSSInjectMode,
79
80
  NotifyUiType,
80
81
  NotifyLogType,
81
82
  IBackendRoute as IRWSBackendRoute,
@@ -86,5 +87,6 @@ export type {
86
87
  IRWSConfig,
87
88
  IRWSUser,
88
89
  TagsProcessorType,
89
- IRWSViewComponent
90
+ IRWSViewComponent,
91
+ UploadFunctionOptions as IRWSUploadFunctionOptions
90
92
  }
@@ -1,11 +1,10 @@
1
1
  import { ITypesResponse } from '../../../components/src/types/IBackendCore';
2
2
  import TheService from './_service';
3
+ import axios from 'axios';
3
4
 
4
5
  //@4DI
5
6
  import ConfigService, { ConfigServiceInstance } from './ConfigService';
6
7
 
7
- import { upload, UploadResponse } from 'upload';
8
-
9
8
  import { backend } from './_api/backend';
10
9
  import { calls } from './_api/calls';
11
10
 
@@ -44,12 +43,29 @@ interface IPrefixedHTTProutes<P = {[key: string]: any}> {
44
43
 
45
44
  type IBackendRoute = IHTTProute | IPrefixedHTTProutes;
46
45
 
46
+ interface UploadFunctionOptions {
47
+ headers?: Record<string, string>;
48
+ method?: 'POST' | 'PUT' | 'PATCH';
49
+ onProgress?: (progress: number) => void;
50
+ }
51
+
52
+ interface UploadResponse {
53
+ success: boolean;
54
+ data?: any;
55
+ error?: string;
56
+ }
47
57
 
48
58
 
49
59
  class ApiService extends TheService {
50
60
  static _DEFAULT: boolean = true;
51
61
  public token?: string;
52
62
 
63
+ private defaultUploadOptions: () => UploadFunctionOptions = () => ({
64
+ headers: this.token ? { Authorization: `Bearer ${this.token}` } : {},
65
+ method: 'POST' as const,
66
+ onProgress: (progress: number) => null,
67
+ });
68
+
53
69
  constructor(@ConfigService public config: ConfigServiceInstance) {
54
70
  super();
55
71
  }
@@ -69,20 +85,58 @@ class ApiService extends TheService {
69
85
  }
70
86
  }
71
87
 
72
- async uploadFile(url: string, file: File, onProgress: (progress: number) => void, payload: any = {}): Promise<UploadResponse>
73
- {
74
- return upload(
75
-
76
- url,
77
- {
78
- file,
79
- ...payload
80
- },
81
- {
82
- onProgress,
83
- headers: this.token ? { Authorization: `Bearer ${this.token}` } : null,
88
+ async uploadFile(url: string, files: Record<string, File>, payload: any = {}, uploadOptions: UploadFunctionOptions = this.defaultUploadOptions()): Promise<UploadResponse>
89
+ {
90
+ const formData = new FormData();
91
+
92
+ // Add files to FormData
93
+ Object.entries(files).forEach(([key, file]) => {
94
+ formData.append(key, file);
95
+ });
96
+
97
+ // Add payload data to FormData
98
+ Object.entries(payload).forEach(([key, value]) => {
99
+ if (value !== undefined && value !== null) {
100
+ formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
84
101
  }
85
- );
102
+ });
103
+
104
+ const options = {
105
+ ...this.defaultUploadOptions(),
106
+ ...uploadOptions
107
+ };
108
+
109
+ try {
110
+ const method = options.method || 'POST';
111
+
112
+ const axiosConfig = {
113
+ method: method.toLowerCase() as any,
114
+ url,
115
+ data: formData,
116
+ headers: {
117
+ 'Content-Type': 'multipart/form-data',
118
+ ...options.headers
119
+ },
120
+ onUploadProgress: (progressEvent: any) => {
121
+ if (options.onProgress && progressEvent.total) {
122
+ const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
123
+ options.onProgress(progress);
124
+ }
125
+ }
126
+ };
127
+
128
+ const result = await axios(axiosConfig);
129
+
130
+ return {
131
+ success: true,
132
+ data: result.data
133
+ };
134
+ } catch (error: any) {
135
+ return {
136
+ success: false,
137
+ error: error.response?.data?.message || error.message || 'Upload failed'
138
+ };
139
+ }
86
140
  }
87
141
 
88
142
  public pureGet = calls.pureGet;
@@ -96,7 +150,7 @@ class ApiService extends TheService {
96
150
  post: async <T, P extends object = object>(routeName: string, payload?: P, options?: IAPIOptions): Promise<T> => calls.post.bind(this)(backend.getBackendUrl.bind(this)(routeName, options?.routeParams, options?.queryParams), payload, options) as Promise<T>,
97
151
  put: async <T, P extends object = object>(routeName: string, payload: P, options?: IAPIOptions): Promise<T> => calls.put.bind(this)(backend.getBackendUrl.bind(this)(routeName, options?.routeParams, options?.queryParams), payload, options) as Promise<T>,
98
152
  delete: async <T>(routeName: string, options?: IAPIOptions): Promise<T> => calls.delete.bind(this)(backend.getBackendUrl.bind(this)(routeName, options?.routeParams, options?.queryParams), options) as Promise<T>,
99
- uploadFile: async (routeName: string, file: File, onProgress: (progress: number) => void, options: IAPIOptions = {}, payload: any = {}): Promise<UploadResponse> => this.uploadFile(backend.getBackendUrl.bind(this)(routeName, options?.routeParams), file, onProgress, payload),
153
+ uploadFile: async (routeName: string, files: Record<string, File>, payload: any = {}, uploadOptions: UploadFunctionOptions = this.defaultUploadOptions(), options: IAPIOptions = {}): Promise<UploadResponse> => this.uploadFile(backend.getBackendUrl.bind(this)(routeName, options?.routeParams), files, payload, uploadOptions),
100
154
  };
101
155
 
102
156
  async getResource(resourceName: string): Promise<ITypesResponse>
@@ -108,4 +162,4 @@ class ApiService extends TheService {
108
162
  }
109
163
 
110
164
  export default ApiService.getSingleton();
111
- export { IBackendRoute, RequestOptions, ApiService as ApiServiceInstance, IHTTProute, IPrefixedHTTProutes, IAPIOptions };
165
+ export { IBackendRoute, RequestOptions, ApiService as ApiServiceInstance, IHTTProute, IPrefixedHTTProutes, IAPIOptions, UploadFunctionOptions };
package/tsconfig.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "compilerOptions": {
2
+ "compilerOptions": {
3
3
  "experimentalDecorators": true,
4
4
  "emitDecoratorMetadata": true,
5
5
  "target": "ES2018",
@@ -7,13 +7,19 @@
7
7
  "moduleResolution": "node",
8
8
  "strict": true,
9
9
  "esModuleInterop": true,
10
- "sourceMap": true,
10
+ "sourceMap": true,
11
11
  "strictNullChecks": false,
12
12
  "allowSyntheticDefaultImports": true,
13
+ "types": [],
13
14
  "lib": [
14
15
  "DOM",
15
16
  "ESNext",
16
17
  "WebWorker"
17
18
  ]
18
- }
19
+ },
20
+ "exclude": [
21
+ "node_modules",
22
+ "**/*.d.ts",
23
+ "**/node_modules/**",
24
+ ]
19
25
  }