@nu-art/ts-focused-object-frontend 0.400.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.
@@ -0,0 +1,32 @@
1
+ import { ComponentSync } from '@nu-art/thunderstorm-frontend/index';
2
+ import { FocusData_Map, FocusedEntity } from '@nu-art/ts-focused-object-shared';
3
+ import { UniqueId } from '@nu-art/ts-common';
4
+ import { OnFocusedDataReceived } from '../modules/ModuleFE_FocusedObject.js';
5
+ import './Component_FocusedEntityRef.scss';
6
+ type Props = {
7
+ focusedEntities?: FocusedEntity[];
8
+ ignoreCurrentUser?: boolean;
9
+ };
10
+ type State = {
11
+ focusedEntities?: FocusedEntity[];
12
+ ignoreCurrentUser?: boolean;
13
+ accountIds: UniqueId[];
14
+ };
15
+ export declare class Component_FocusedEntityRef extends ComponentSync<Props, State> implements OnFocusedDataReceived {
16
+ __onFocusedDataReceived(map: FocusData_Map): void;
17
+ protected deriveStateFromProps(nextProps: Props, state: State): State;
18
+ /**
19
+ * Mount / Unmount logic handled in
20
+ * - ComponentWillUnmount
21
+ * - ComponentDidMount
22
+ * - ComponentDidUpdate
23
+ *
24
+ * It must be the case, in order for un-focusing and focusing to happen in the correct order
25
+ * no matter how the component is rendered or recycled.
26
+ */
27
+ componentWillUnmount(): void;
28
+ componentDidMount(): void;
29
+ componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any): void;
30
+ render(): import("react/jsx-runtime").JSX.Element;
31
+ }
32
+ export {};
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ComponentSync, LL_H_C, Show } from '@nu-art/thunderstorm-frontend/index';
3
+ import { compare, filterDuplicates } from '@nu-art/ts-common';
4
+ import { ModuleFE_FocusedObject } from '../modules/ModuleFE_FocusedObject.js';
5
+ import { Component_AccountThumbnail, ModuleFE_Account } from '@nu-art/user-account-frontend/index';
6
+ import './Component_FocusedEntityRef.scss';
7
+ export class Component_FocusedEntityRef extends ComponentSync {
8
+ // ######################## Lifecycle ########################
9
+ __onFocusedDataReceived(map) {
10
+ this.reDeriveState();
11
+ }
12
+ deriveStateFromProps(nextProps, state) {
13
+ state.focusedEntities = nextProps.focusedEntities;
14
+ state.ignoreCurrentUser = nextProps.ignoreCurrentUser;
15
+ state.accountIds = state.focusedEntities?.reduce((accountIds, focusedEntity) => {
16
+ const accountIdsForFocusedItem = ModuleFE_FocusedObject.getAccountIdsForFocusedItem(focusedEntity.dbKey, focusedEntity.itemId, state.ignoreCurrentUser);
17
+ return filterDuplicates([...accountIds, ...accountIdsForFocusedItem]);
18
+ }, []) || [];
19
+ return state;
20
+ }
21
+ /**
22
+ * Mount / Unmount logic handled in
23
+ * - ComponentWillUnmount
24
+ * - ComponentDidMount
25
+ * - ComponentDidUpdate
26
+ *
27
+ * It must be the case, in order for un-focusing and focusing to happen in the correct order
28
+ * no matter how the component is rendered or recycled.
29
+ */
30
+ componentWillUnmount() {
31
+ if (this.state.focusedEntities)
32
+ ModuleFE_FocusedObject.unfocus(this.state.focusedEntities);
33
+ }
34
+ componentDidMount() {
35
+ //If mounted with focus entities, focus on them
36
+ if (this.state.focusedEntities)
37
+ ModuleFE_FocusedObject.focus(this.state.focusedEntities);
38
+ }
39
+ componentDidUpdate(prevProps, prevState, snapshot) {
40
+ //Change in focused entities, set new focused
41
+ if (!compare(prevState.focusedEntities, this.state.focusedEntities)) {
42
+ //Unfocus previous entities
43
+ if (this.state.focusedEntities)
44
+ ModuleFE_FocusedObject.unfocus(this.state.focusedEntities);
45
+ //focus current entities
46
+ if (prevState.focusedEntities)
47
+ ModuleFE_FocusedObject.focus(prevState.focusedEntities);
48
+ }
49
+ }
50
+ // ######################## Render ########################
51
+ render() {
52
+ return _jsx(LL_H_C, { className: 'component--focused-object', children: this.state.accountIds.map(id => {
53
+ const account = ModuleFE_Account.cache.unique(id);
54
+ return _jsxs(Show, { children: [_jsx(Show.If, { condition: !!account, children: _jsx(Component_AccountThumbnail, { accountId: () => id }) }), _jsx(Show.Else, { children: _jsx("div", { children: "Bug" }) })] }, id);
55
+ }) });
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ .component--focused-object {
2
+
3
+ }
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './modules/ModuleFE_FocusedObject.js';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './modules/ModuleFE_FocusedObject.js';
@@ -0,0 +1,57 @@
1
+ import { Module, UniqueId } from '@nu-art/ts-common';
2
+ import { ApiDefCaller } from '@nu-art/thunderstorm-shared';
3
+ import { ThunderDispatcher } from '@nu-art/thunderstorm-frontend/index';
4
+ import { ApiStruct_FocusedObject, FocusData_Map, FocusedEntity } from '@nu-art/ts-focused-object-shared';
5
+ import { OnLoginStatusUpdated } from '@nu-art/user-account-frontend/index';
6
+ export interface OnFocusedDataReceived {
7
+ __onFocusedDataReceived: (map: FocusData_Map) => void;
8
+ }
9
+ export declare const dispatch_onFocusedDataReceived: ThunderDispatcher<OnFocusedDataReceived, "__onFocusedDataReceived", [map: FocusData_Map], void>;
10
+ export declare class ModuleFE_FocusedObject_Class extends Module implements OnLoginStatusUpdated {
11
+ readonly _v1: ApiDefCaller<ApiStruct_FocusedObject>['_v1'];
12
+ private focusFirebaseListener;
13
+ private focusDataMap;
14
+ private currentlyFocused;
15
+ private readonly apiDebounce;
16
+ private windowIsFocused;
17
+ private unfocusTimeout;
18
+ private keepAliveTimeout;
19
+ __onLoginStatusUpdated(): void;
20
+ constructor();
21
+ init(): void;
22
+ private initFirebaseListening;
23
+ private initWindowFocusListeners;
24
+ private initWindowCloseListeners;
25
+ private onRTDBChange;
26
+ /**
27
+ * Callback for when the current window is focused.
28
+ * we change the class property "windowIsFocused" to true so when the time comes to
29
+ * send a keepalive to BE, the request will be sent if the window is focused.
30
+ * Will also trigger keepalive timer if the time is not already set
31
+ */
32
+ private onWindowFocus;
33
+ /**
34
+ * Callback for when the current window is un-focused.
35
+ * we change the class property "windowIsFocused" to false so when the time comes to
36
+ * send a keepalive to BE, the request will not be sent if the window is not focused
37
+ */
38
+ private onWindowBlur;
39
+ private onUserLoggedOut;
40
+ private triggerKeepAlive;
41
+ private triggerUnfocus;
42
+ private clearKeepAlive;
43
+ private clearUnfocus;
44
+ private updateRTDB;
45
+ focus: (entities: FocusedEntity[]) => void;
46
+ unfocus: (entities: FocusedEntity[]) => void;
47
+ getFocusData: (dbKey: string, itemId: UniqueId) => {
48
+ [s: string]: {
49
+ [s: string]: {
50
+ [s: string]: number;
51
+ };
52
+ };
53
+ };
54
+ getAccountIdsForFocusedItem: (dbKey: string, itemId: UniqueId, ignoreCurrentUser?: boolean) => UniqueId[];
55
+ private translateCurrentlyFocusedToFocusedEntities;
56
+ }
57
+ export declare const ModuleFE_FocusedObject: ModuleFE_FocusedObject_Class;
@@ -0,0 +1,161 @@
1
+ import { _keys, debounce, filterDuplicates, Module, removeItemFromArray, Second } from '@nu-art/ts-common';
2
+ import { apiWithBody, ThunderDispatcher } from '@nu-art/thunderstorm-frontend/index';
3
+ import { ModuleFE_FirebaseListener } from '@nu-art/firebase-frontend/ModuleFE_FirebaseListener/ModuleFE_FirebaseListener';
4
+ import { ApiDef_FocusedObject, } from '@nu-art/ts-focused-object-shared';
5
+ import { LoggedStatus, ModuleFE_Account } from '@nu-art/user-account-frontend/index';
6
+ import { DefaultTTL_FocusedObject, getRelationalPath } from '@nu-art/ts-focused-object-shared/consts';
7
+ export const dispatch_onFocusedDataReceived = new ThunderDispatcher('__onFocusedDataReceived');
8
+ export class ModuleFE_FocusedObject_Class extends Module {
9
+ _v1;
10
+ focusFirebaseListener;
11
+ focusDataMap;
12
+ currentlyFocused = {};
13
+ apiDebounce;
14
+ windowIsFocused = true;
15
+ unfocusTimeout;
16
+ keepAliveTimeout;
17
+ __onLoginStatusUpdated() {
18
+ const status = ModuleFE_Account.getLoggedStatus();
19
+ if (status === LoggedStatus.LOGGED_OUT)
20
+ this.onUserLoggedOut();
21
+ }
22
+ constructor() {
23
+ super();
24
+ this.focusDataMap = {};
25
+ this._v1 = {
26
+ // updateFocusData: apiWithBody(ApiDef_FocusedObject._v1.updateFocusData),
27
+ // setFocusStatusByTabId: apiWithBody(ApiDef_FocusedObject._v1.setFocusStatusByTabId),
28
+ // releaseObject: apiWithBody(ApiDef_FocusedObject._v1.releaseObject),
29
+ // releaseByTabId: apiWithBody(ApiDef_FocusedObject._v1.releaseByTabId),
30
+ update: apiWithBody(ApiDef_FocusedObject._v1.update),
31
+ };
32
+ this.apiDebounce = debounce(this.updateRTDB, 2 * Second, 10 * Second);
33
+ }
34
+ init() {
35
+ this.initFirebaseListening();
36
+ this.initWindowFocusListeners();
37
+ this.initWindowCloseListeners();
38
+ }
39
+ // ######################## Init listeners ########################
40
+ initFirebaseListening = () => {
41
+ this.focusFirebaseListener = ModuleFE_FirebaseListener.createListener(getRelationalPath());
42
+ this.focusFirebaseListener.startListening(this.onRTDBChange);
43
+ };
44
+ initWindowFocusListeners() {
45
+ window.addEventListener('focus', this.onWindowFocus);
46
+ window.addEventListener('blur', this.onWindowBlur);
47
+ }
48
+ initWindowCloseListeners() {
49
+ window.addEventListener('beforeunload', async (event) => {
50
+ this._v1.update({ focusedEntities: [] }).execute();
51
+ // navigator.sendBeacon('/log', JSON.stringify({ type:'application/json' }));
52
+ });
53
+ }
54
+ // ######################## Listener Callbacks ########################
55
+ onRTDBChange = (snapshot) => {
56
+ this.focusDataMap = snapshot.val() ?? {};
57
+ this.logDebug('Received firebase focus data', this.focusDataMap);
58
+ // Update all the FocusedEntityRef components
59
+ dispatch_onFocusedDataReceived.dispatchAll(this.focusDataMap);
60
+ };
61
+ /**
62
+ * Callback for when the current window is focused.
63
+ * we change the class property "windowIsFocused" to true so when the time comes to
64
+ * send a keepalive to BE, the request will be sent if the window is focused.
65
+ * Will also trigger keepalive timer if the time is not already set
66
+ */
67
+ onWindowFocus = () => {
68
+ this.windowIsFocused = true;
69
+ //If the keep alive counter still exists, no need to trigger any extra further logic
70
+ if (this.keepAliveTimeout)
71
+ return;
72
+ this.apiDebounce();
73
+ };
74
+ /**
75
+ * Callback for when the current window is un-focused.
76
+ * we change the class property "windowIsFocused" to false so when the time comes to
77
+ * send a keepalive to BE, the request will not be sent if the window is not focused
78
+ */
79
+ onWindowBlur = () => {
80
+ this.windowIsFocused = false;
81
+ };
82
+ onUserLoggedOut = () => {
83
+ this.currentlyFocused = {};
84
+ };
85
+ // ######################## Timer Interactions ########################
86
+ triggerKeepAlive = () => {
87
+ this.clearKeepAlive();
88
+ //No need to set keepalive timeout if currentlyFocused has no data
89
+ if (!_keys(this.currentlyFocused).length)
90
+ return;
91
+ this.keepAliveTimeout = setTimeout(() => {
92
+ //No need to keepalive if window is not focused
93
+ if (!this.windowIsFocused)
94
+ return this.clearKeepAlive();
95
+ this.apiDebounce();
96
+ }, DefaultTTL_FocusedObject - 20 * Second);
97
+ };
98
+ triggerUnfocus = () => {
99
+ this.clearUnfocus();
100
+ this.unfocusTimeout = setTimeout(() => this.apiDebounce(), 20 * Second);
101
+ };
102
+ clearKeepAlive = () => {
103
+ clearTimeout(this.keepAliveTimeout);
104
+ delete this.keepAliveTimeout;
105
+ };
106
+ clearUnfocus = () => {
107
+ clearTimeout(this.unfocusTimeout);
108
+ delete this.unfocusTimeout;
109
+ };
110
+ // ######################## API Logic ########################
111
+ updateRTDB = () => {
112
+ //Call API
113
+ const focusedEntities = this.translateCurrentlyFocusedToFocusedEntities();
114
+ this._v1.update({ focusedEntities })
115
+ .executeSync()
116
+ .then()
117
+ .catch(e => {
118
+ this.logError('Update focused object failed', e);
119
+ })
120
+ .finally(() => {
121
+ this.clearUnfocus();
122
+ this.triggerKeepAlive();
123
+ });
124
+ };
125
+ // ######################## Logic ########################
126
+ focus = (entities) => {
127
+ entities.forEach(entity => {
128
+ if (!this.currentlyFocused[entity.dbKey])
129
+ this.currentlyFocused[entity.dbKey] = [];
130
+ this.currentlyFocused[entity.dbKey] = filterDuplicates([...this.currentlyFocused[entity.dbKey], entity.itemId]);
131
+ });
132
+ this.apiDebounce();
133
+ };
134
+ unfocus = (entities) => {
135
+ entities.forEach(entity => {
136
+ if (!this.currentlyFocused[entity.dbKey])
137
+ return;
138
+ this.currentlyFocused[entity.dbKey] = removeItemFromArray(this.currentlyFocused[entity.dbKey], entity.itemId);
139
+ });
140
+ this.triggerUnfocus();
141
+ };
142
+ getFocusData = (dbKey, itemId) => {
143
+ return this.focusDataMap[dbKey]?.[itemId];
144
+ };
145
+ getAccountIdsForFocusedItem = (dbKey, itemId, ignoreCurrentUser = true) => {
146
+ const data = this.getFocusData(dbKey, itemId);
147
+ const userIds = data ? _keys(data) : [];
148
+ const account = ModuleFE_Account.getCurrentlyLoggedAccount();
149
+ return ignoreCurrentUser ? userIds.filter(id => id !== account?._id) : userIds;
150
+ };
151
+ translateCurrentlyFocusedToFocusedEntities = () => {
152
+ const focusedEntities = [];
153
+ _keys(this.currentlyFocused).forEach(dbKey => {
154
+ this.currentlyFocused[dbKey].forEach(itemId => {
155
+ focusedEntities.push({ dbKey: dbKey, itemId });
156
+ });
157
+ });
158
+ return focusedEntities;
159
+ };
160
+ }
161
+ export const ModuleFE_FocusedObject = new ModuleFE_FocusedObject_Class();
@@ -0,0 +1,3 @@
1
+ import { Module } from '@nu-art/ts-common';
2
+ export declare const ModulePack_FocusedObjectFE: Module[];
3
+ export declare const ModulePackFE_FocusedObject: Module<any, any, import("@nu-art/ts-common").Validator<any> | import("@nu-art/ts-common").TypeValidator<any>>[];
@@ -0,0 +1,5 @@
1
+ import { ModuleFE_FocusedObject } from './ModuleFE_FocusedObject.js';
2
+ export const ModulePack_FocusedObjectFE = [
3
+ ModuleFE_FocusedObject
4
+ ];
5
+ export const ModulePackFE_FocusedObject = ModulePack_FocusedObjectFE;
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@nu-art/ts-focused-object-frontend",
3
+ "version": "0.400.5",
4
+ "description": "ts-focused-object - Express & Typescript based backend framework Frontend",
5
+ "keywords": [
6
+ "TacB0sS",
7
+ "infra",
8
+ "nu-art",
9
+ "thunderstorm",
10
+ "typescript",
11
+ "ts-focused-object"
12
+ ],
13
+ "homepage": "https://github.com/nu-art-js/thunderstorm",
14
+ "bugs": {
15
+ "url": "https://github.com/nu-art-js/thunderstorm/issues"
16
+ },
17
+ "publishConfig": {
18
+ "directory": "dist",
19
+ "linkDirectory": true
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+ssh://git@github.com:nu-art-js/thunderstorm.git"
24
+ },
25
+ "license": "Apache-2.0",
26
+ "author": "TacB0sS",
27
+ "files": [
28
+ "**/*"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc"
32
+ },
33
+ "dependencies": {
34
+ "@nu-art/ts-focused-object-shared": "0.400.5",
35
+ "@nu-art/firebase-frontend": "0.400.5",
36
+ "@nu-art/firebase-shared": "0.400.5",
37
+ "@nu-art/thunderstorm-frontend": "0.400.5",
38
+ "@nu-art/thunderstorm-shared": "0.400.5",
39
+ "@nu-art/ts-common": "0.400.5",
40
+ "@nu-art/user-account-frontend": "0.400.5",
41
+ "@nu-art/user-account-shared": "0.400.5",
42
+ "firebase": "^11.9.0",
43
+ "firebase-admin": "13.4.0",
44
+ "firebase-functions": "6.3.2",
45
+ "react": "^18.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/react": "^18.0.0",
49
+ "@types/chai": "^4.3.4",
50
+ "@types/mocha": "^10.0.1"
51
+ },
52
+ "unitConfig": {
53
+ "type": "typescript-lib"
54
+ },
55
+ "type": "module",
56
+ "exports": {
57
+ ".": {
58
+ "types": "./index.d.ts",
59
+ "import": "./index.js"
60
+ },
61
+ "./*": {
62
+ "types": "./*.d.ts",
63
+ "import": "./*.js"
64
+ }
65
+ }
66
+ }