useSpoolBuddyState.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { useEffect, useReducer, useCallback } from 'react';
  2. export interface MatchedSpool {
  3. id: number;
  4. tag_uid: string;
  5. material: string;
  6. subtype: string | null;
  7. color_name: string | null;
  8. rgba: string | null;
  9. brand: string | null;
  10. label_weight: number;
  11. core_weight: number;
  12. weight_used: number;
  13. }
  14. export interface SpoolBuddyState {
  15. weight: number | null;
  16. weightStable: boolean;
  17. rawAdc: number | null;
  18. matchedSpool: MatchedSpool | null;
  19. unknownTagUid: string | null;
  20. unknownTrayUuid: string | null;
  21. deviceOnline: boolean;
  22. deviceId: string | null;
  23. }
  24. type Action =
  25. | { type: 'WEIGHT_UPDATE'; weight: number; stable: boolean; rawAdc: number; deviceId: string }
  26. | { type: 'TAG_MATCHED'; spool: MatchedSpool; deviceId: string }
  27. | { type: 'UNKNOWN_TAG'; tagUid: string; trayUuid: string | null; deviceId: string }
  28. | { type: 'TAG_REMOVED'; deviceId: string }
  29. | { type: 'DEVICE_ONLINE'; deviceId: string }
  30. | { type: 'DEVICE_OFFLINE'; deviceId: string };
  31. const initialState: SpoolBuddyState = {
  32. weight: null,
  33. weightStable: false,
  34. rawAdc: null,
  35. matchedSpool: null,
  36. unknownTagUid: null,
  37. unknownTrayUuid: null,
  38. deviceOnline: false,
  39. deviceId: null,
  40. };
  41. function reducer(state: SpoolBuddyState, action: Action): SpoolBuddyState {
  42. switch (action.type) {
  43. case 'WEIGHT_UPDATE':
  44. return {
  45. ...state,
  46. weight: action.weight,
  47. weightStable: action.stable,
  48. rawAdc: action.rawAdc,
  49. deviceId: action.deviceId,
  50. deviceOnline: true,
  51. };
  52. case 'TAG_MATCHED':
  53. return {
  54. ...state,
  55. matchedSpool: action.spool,
  56. unknownTagUid: null,
  57. unknownTrayUuid: null,
  58. deviceId: action.deviceId,
  59. };
  60. case 'UNKNOWN_TAG':
  61. return {
  62. ...state,
  63. matchedSpool: null,
  64. unknownTagUid: action.tagUid,
  65. unknownTrayUuid: action.trayUuid ?? null,
  66. deviceId: action.deviceId,
  67. };
  68. case 'TAG_REMOVED':
  69. return {
  70. ...state,
  71. matchedSpool: null,
  72. unknownTagUid: null,
  73. unknownTrayUuid: null,
  74. };
  75. case 'DEVICE_ONLINE':
  76. return {
  77. ...state,
  78. deviceOnline: true,
  79. deviceId: action.deviceId,
  80. };
  81. case 'DEVICE_OFFLINE':
  82. return {
  83. ...state,
  84. deviceOnline: false,
  85. weight: null,
  86. weightStable: false,
  87. rawAdc: null,
  88. };
  89. default:
  90. return state;
  91. }
  92. }
  93. export function useSpoolBuddyState() {
  94. const [state, dispatch] = useReducer(reducer, initialState);
  95. const handleWeight = useCallback((e: Event) => {
  96. const detail = (e as CustomEvent).detail;
  97. dispatch({
  98. type: 'WEIGHT_UPDATE',
  99. weight: detail.weight_grams ?? detail.data?.weight_grams,
  100. stable: detail.stable ?? detail.data?.stable ?? false,
  101. rawAdc: detail.raw_adc ?? detail.data?.raw_adc ?? null,
  102. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  103. });
  104. }, []);
  105. const handleTagMatched = useCallback((e: Event) => {
  106. const detail = (e as CustomEvent).detail;
  107. const spool = detail.spool ?? detail.data?.spool;
  108. if (spool) {
  109. dispatch({
  110. type: 'TAG_MATCHED',
  111. spool: {
  112. id: spool.id,
  113. tag_uid: detail.tag_uid ?? detail.data?.tag_uid ?? '',
  114. material: spool.material ?? '',
  115. subtype: spool.subtype ?? null,
  116. color_name: spool.color_name ?? null,
  117. rgba: spool.rgba ?? null,
  118. brand: spool.brand ?? null,
  119. label_weight: spool.label_weight ?? 0,
  120. core_weight: spool.core_weight ?? 0,
  121. weight_used: spool.weight_used ?? 0,
  122. },
  123. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  124. });
  125. }
  126. }, []);
  127. const handleUnknownTag = useCallback((e: Event) => {
  128. const detail = (e as CustomEvent).detail;
  129. dispatch({
  130. type: 'UNKNOWN_TAG',
  131. tagUid: detail.tag_uid ?? detail.data?.tag_uid ?? '',
  132. trayUuid: detail.tray_uuid ?? detail.data?.tray_uuid ?? null,
  133. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  134. });
  135. }, []);
  136. const handleTagRemoved = useCallback((e: Event) => {
  137. const detail = (e as CustomEvent).detail;
  138. dispatch({
  139. type: 'TAG_REMOVED',
  140. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  141. });
  142. }, []);
  143. const handleOnline = useCallback((e: Event) => {
  144. const detail = (e as CustomEvent).detail;
  145. dispatch({
  146. type: 'DEVICE_ONLINE',
  147. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  148. });
  149. }, []);
  150. const handleOffline = useCallback((e: Event) => {
  151. const detail = (e as CustomEvent).detail;
  152. dispatch({
  153. type: 'DEVICE_OFFLINE',
  154. deviceId: detail.device_id ?? detail.data?.device_id ?? '',
  155. });
  156. }, []);
  157. useEffect(() => {
  158. window.addEventListener('spoolbuddy-weight', handleWeight);
  159. window.addEventListener('spoolbuddy-tag-matched', handleTagMatched);
  160. window.addEventListener('spoolbuddy-unknown-tag', handleUnknownTag);
  161. window.addEventListener('spoolbuddy-tag-removed', handleTagRemoved);
  162. window.addEventListener('spoolbuddy-online', handleOnline);
  163. window.addEventListener('spoolbuddy-offline', handleOffline);
  164. return () => {
  165. window.removeEventListener('spoolbuddy-weight', handleWeight);
  166. window.removeEventListener('spoolbuddy-tag-matched', handleTagMatched);
  167. window.removeEventListener('spoolbuddy-unknown-tag', handleUnknownTag);
  168. window.removeEventListener('spoolbuddy-tag-removed', handleTagRemoved);
  169. window.removeEventListener('spoolbuddy-online', handleOnline);
  170. window.removeEventListener('spoolbuddy-offline', handleOffline);
  171. };
  172. }, [handleWeight, handleTagMatched, handleUnknownTag, handleTagRemoved, handleOnline, handleOffline]);
  173. const remainingWeight = state.matchedSpool
  174. ? Math.max(0, state.matchedSpool.label_weight - state.matchedSpool.weight_used)
  175. : null;
  176. const netWeight = state.weight !== null && state.matchedSpool
  177. ? Math.max(0, state.weight - state.matchedSpool.core_weight)
  178. : null;
  179. return {
  180. ...state,
  181. remainingWeight,
  182. netWeight,
  183. };
  184. }