useSpoolBuddyState.ts 5.7 KB

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