From a29ff0be1ab7a01e9abe0fc22aef5a9e4a61ab8d Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Mon, 4 May 2026 16:50:15 +0700 Subject: [PATCH] feat: recipe version selector - fix recipe not show on overview - fix recipe show late after select country - disable queue message on no connection ws - fix infinite topping(s) list if moving between pages Signed-off-by: pakintada@gmail.com --- ISSUES.txt | 5 +- .../recipe-details/recipe-detail.svelte | 2 +- .../recipelist-value-editor.svelte | 8 +- .../recipe-details/recipelist-value.svelte | 3 + .../components/recipe-editor-dialog.svelte | 29 +++-- src/lib/core/adb/adb.ts | 2 +- src/lib/core/client/server.ts | 51 +++++++- src/lib/core/handlers/adbPayloadHandler.ts | 21 ++++ src/lib/core/handlers/messageHandler.ts | 43 ++++++- src/lib/core/handlers/ws_messageSender.ts | 22 ++-- src/lib/core/stores/recipeStore.ts | 43 +------ src/lib/core/stores/websocketStore.ts | 18 +-- src/lib/core/types/outMessage.ts | 2 +- src/lib/data/recipeService.ts | 5 +- src/lib/models/recipe_version.model.ts | 4 + src/routes/(authed)/departments/+page.svelte | 5 +- .../(authed)/recipe/overview/+page.svelte | 118 ++++++++++++++++-- .../(authed)/sheet/overview/+page.svelte | 2 - src/routes/(authed)/tools/brew/+page.svelte | 39 +++--- 19 files changed, 314 insertions(+), 108 deletions(-) create mode 100644 src/lib/models/recipe_version.model.ts diff --git a/ISSUES.txt b/ISSUES.txt index 35f54c2..add4cd1 100644 --- a/ISSUES.txt +++ b/ISSUES.txt @@ -5,11 +5,10 @@ Idea, Issue, Work Tracking [Pending] -- [] #2: Send change value from editing in recipe to machine - [] #3: Save value to recipe -- [] #5: revert value on close dialog recipe - [] #6: display all recipes with materials from csv [material usages with product code] - [] #7: material & menu creation +- [x] #8: change recipe version [Rejected] - [] #4: From #1, will do sync value from server, so that user could save their current edit too @@ -17,5 +16,7 @@ Idea, Issue, Work Tracking [Done] - [x] #1: Topping value saving bug, fix by snapshot value +- [x] #2: Send change value from editing in recipe to machine +- [x] #5: revert value on close dialog recipe diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte index 4634d96..9344a8f 100644 --- a/src/lib/components/recipe-details/recipe-detail.svelte +++ b/src/lib/components/recipe-details/recipe-detail.svelte @@ -249,7 +249,7 @@ - {#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == ''} + {#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == '' || refPage == 'overview'} x.id === currentToppings[getToppingSlot()]['ListGroupID'][0] ); + + console.log('current selected topping list', row_uid, current_selected); + if (currentToppings[getToppingSlot()]['ListGroupID'][0] === 0) { return 'Empty'; } diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index 1b47cd8..5a2d5cd 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -130,19 +130,32 @@ }); } else { // topping part - latestRecipeToppingData.set(new_topping_value_for_save); - console.log('save change', get(latestRecipeToppingData)); - currentData['ToppingSet'] = latestRecipeToppingData; + // latestRecipeToppingData.set(new_topping_value_for_save); + // console.log('save change', get(latestRecipeToppingData)); + // currentData['ToppingSet'] = latestRecipeToppingData; - console.log('current data', currentData); + // console.log('current data', currentData); if (get(referenceFromPage) == 'brew') { // send change to machine + + // apply value + + let recipeDevSnapshot = get(recipeFromMachineQuery) ?? {}; + let recipe01Snap = recipeDevSnapshot['recipe']; + if (recipe01Snap) { + recipe01Snap[ready_to_send_brew[0]['productCode']] = ready_to_send_brew[0]; + + // save now + recipeDevSnapshot['recipe'] = recipe01Snap; + recipeFromMachineQuery.set(recipeDevSnapshot); + } + sendToAndroid({ type: 'save_recipe_machine', payload: { time: new Date().toLocaleTimeString(), - data: currentData + data: ready_to_send_brew[0] } }); } else if (get(referenceFromPage) == 'overview') { @@ -294,10 +307,10 @@ Edit Recipe {productCode} - {#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == ''} + {#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == '' || refPage == 'overview'} Ready {:else} - Brewing + Working {/if} @@ -317,7 +330,7 @@ on:saveRecipe={async () => { save_change = true; - console.log('save change, check state', callback_revert_value_if_not_save); + callback_revert_value_if_not_save(save_change); addNotification('INFO:Save recipe'); }} diff --git a/src/lib/core/adb/adb.ts b/src/lib/core/adb/adb.ts index 5fd830d..84b2d25 100644 --- a/src/lib/core/adb/adb.ts +++ b/src/lib/core/adb/adb.ts @@ -224,7 +224,7 @@ async function connectToAndroidServer() { return; } - await push('/sdcard/coffeevending/enable_adb_block_watch', '1'); + // await push('/sdcard/coffeevending/enable_adb_block_watch', '1'); const stream = await inst.transport.connect(env.PUBLIC_BREW_CONN_PORT); const writer = stream.writable.getWriter(); diff --git a/src/lib/core/client/server.ts b/src/lib/core/client/server.ts index 226a30f..98e2ea9 100644 --- a/src/lib/core/client/server.ts +++ b/src/lib/core/client/server.ts @@ -6,6 +6,7 @@ import { extractCookieOnNonBrowser } from '$lib/helpers/cookie'; import { browser } from '$app/environment'; import { permission } from '../stores/permissions'; import { addNotification } from '../stores/noti'; +import { recipeData, recipeOverviewData } from '../stores/recipeStore'; export async function getRecipes() { if (browser && !get(departmentStore)) { @@ -31,7 +32,10 @@ export async function getRecipes() { // construct path. fetch (GET) {server}/recipe/{countryTarget}/{version} let idToken = await get(auth)?.getIdToken(); - console.log('country target get recipe', country); + console.log('country target get recipe', countryTarget); + + recipeData.set([]); + recipeOverviewData.set([]); sendMessage({ type: 'recipe', @@ -46,3 +50,48 @@ export async function getRecipes() { return []; } + +export async function getRecipeWithVersion(version: string) { + if (browser && !get(departmentStore)) { + console.log('cannot get dep', get(departmentStore)); + return []; + } + + let countryTarget = get(departmentStore); + let country = ''; + + let countryPerm = `document.read.${countryTarget}`; + let user_perms = get(permission); + + if (!user_perms.includes(countryPerm)) { + addNotification('ERR:Invalid permission'); + return []; + } + + // if (!countryTarget && !browser) { + // countryTarget = extractCookieOnNonBrowser()['department']; + // } + + // construct path. fetch (GET) {server}/recipe/{countryTarget}/{version} + let idToken = await get(auth)?.getIdToken(); + + console.log('country target get recipe', countryTarget); + + recipeData.set([]); + recipeOverviewData.set([]); + + // NOTE: although version is provided, actual version field is still need to be latest + // Just in case version is not found + sendMessage({ + type: 'recipe', + payload: { + auth: idToken ?? '', + partial: false, + country: countryTarget ?? '', + version: -1, + parameters: 'use_legacy_version=true,version=' + version + } + }); + + return []; +} diff --git a/src/lib/core/handlers/adbPayloadHandler.ts b/src/lib/core/handlers/adbPayloadHandler.ts index 465e052..631c159 100644 --- a/src/lib/core/handlers/adbPayloadHandler.ts +++ b/src/lib/core/handlers/adbPayloadHandler.ts @@ -1,5 +1,6 @@ import { updateMachineStatus } from '../stores/machineInfoStore'; import { addNotification } from '../stores/noti'; +import { handleIncomingMessages } from './messageHandler'; type AdbPayload = { type: string; payload: any }; @@ -17,6 +18,26 @@ async function handleAdbPayload(raw_payload: string) { case 'response': if (payload.payload instanceof String) { // single message response + let raw_payload = payload.payload.toString(); + + if (raw_payload.startsWith('save_recipe_machine')) { + let res = raw_payload.split('/'); + + let pd = res[1] ?? ''; + let action = res[2] ?? ''; + let uiAction = res[3] ?? ''; + + handleIncomingMessages( + JSON.stringify({ + type: 'ui_action', + payload: { + action: uiAction, + from: 'brew', + ref: `${pd}.${action}` + } + }) + ); + } } break; case 'ACK': diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index 44099ef..36fbf18 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -1,6 +1,7 @@ import { get, writable } from 'svelte/store'; import { addNotification, notiStore } from '../stores/noti'; import { + currentRecipeVersionsSelector, materialFromServerQuery, recipeData, recipeDataError, @@ -12,6 +13,8 @@ import { } from '../stores/recipeStore'; import { buildOverviewFromServer } from '$lib/data/recipeService'; import { auth } from '../client/firebase'; +import { type RecipeVersion } from '$lib/models/recipe_version.model'; +import { goto } from '$app/navigation'; export const messages = writable([]); @@ -32,18 +35,30 @@ const handlers: Record void> = { let stream_id = p.stream_id; let total_size = p.total_size; let chunk_size = p.chunk_size; + let data_meta = p.metadata; if (stream_id) { addNotification('INFO:Start streaming data'); + + let meta_list = data_meta?.split(','); + let version = meta_list[0]?.split('=')[1] ?? ''; + let country = meta_list[1]?.split('=')[1] ?? ''; + // recipeLoading.set(true); recipeStreamMeta.set({ id: stream_id, total_size: total_size, chunk_size: chunk_size, - progress: 0 + progress: 0, + version, + country }); recipeData.set([]); recipeOverviewData.set([]); + + materialFromServerQuery.set([]); + toppingListFromServerQuery.set([]); + toppingGroupFromServerQuery.set([]); } }, stream_data_error: (p) => { @@ -56,6 +71,7 @@ const handlers: Record void> = { }, stream_data_chunk: (p) => { let current_meta = get(recipeStreamMeta); + // console.log('current meta', current_meta); if (current_meta) { let stream_id = current_meta.id; @@ -92,6 +108,7 @@ const handlers: Record void> = { // build overview for recipe from server // + // console.log('ending stream'); buildOverviewFromServer(); }, stream_data_extra: (p) => { @@ -120,7 +137,7 @@ const handlers: Record void> = { curr_mat_query.push(m); } - // console.log('current materials: ', JSON.stringify(curr_mat_query)); + // // console.log('current materials: ', JSON.stringify(curr_mat_query)); materialFromServerQuery.set(curr_mat_query); break; case 'topplist': @@ -168,6 +185,28 @@ const handlers: Record void> = { // broadcast to all addNotification(`${noti_level}:${msg}`); } + }, + ui_action: (p) => { + if (p.action == 'refreshNow' && p.from == 'brew') { + goto('/tools/brew'); + } + }, + version_selectors: (p) => { + if (p.versions.length > 0) { + currentRecipeVersionsSelector.set([]); + let result: RecipeVersion[] = []; + + for (let vstr of p.versions) { + let pure_version = vstr.split('_')[0]; + + result.push({ + display_version: pure_version, + actual_version_name: vstr + }); + } + + currentRecipeVersionsSelector.set(result); + } } }; diff --git a/src/lib/core/handlers/ws_messageSender.ts b/src/lib/core/handlers/ws_messageSender.ts index 2f60dca..0ba1fdf 100644 --- a/src/lib/core/handlers/ws_messageSender.ts +++ b/src/lib/core/handlers/ws_messageSender.ts @@ -41,25 +41,25 @@ export function sendCommandRequest(target: CommandRequest, values: any) { }); } -export function sendMessage(msg: OutMessage): boolean { +export function sendMessage(msg: OutMessage, ignore_queue_request: boolean = true): boolean { const socket = get(socketStore); const data = JSON.stringify(msg); // console.log('try sending ', data); if (!socket || socket.readyState !== WebSocket.OPEN) { - console.warn('WebSocket not connected, put to queue'); + // console.warn('WebSocket not connected, put to queue'); - let currentQueue = get(queue); - if (currentQueue.length >= 10) { - while (currentQueue.length >= 10) { - currentQueue.shift(); - } - } - currentQueue.push(data); - queue.set(currentQueue); + // let currentQueue = get(queue); + // if (currentQueue.length >= 10) { + // while (currentQueue.length >= 10) { + // currentQueue.shift(); + // } + // } + // currentQueue.push(data); + // queue.set(currentQueue); - addNotification('WARN:Queuing request'); + // if (!ignore_queue_request) addNotification('WARN:Queuing request'); return false; } diff --git a/src/lib/core/stores/recipeStore.ts b/src/lib/core/stores/recipeStore.ts index e7d9340..5c7e606 100644 --- a/src/lib/core/stores/recipeStore.ts +++ b/src/lib/core/stores/recipeStore.ts @@ -1,6 +1,7 @@ import { writable } from 'svelte/store'; import type { RecipeOverview } from '../../../routes/(authed)/recipe/overview/columns'; import type { Material } from '$lib/models/material.model'; +import type { RecipeVersion } from '$lib/models/recipe_version.model'; export const recipeData = writable(null); export const recipeLoading = writable(false); @@ -10,8 +11,12 @@ export const recipeStreamMeta = writable<{ total_size: number; chunk_size: number; progress: number; + version?: string; + country?: string; } | null>(null); +export const currentRecipeVersionsSelector = writable([]); + // from server export const recipeOverviewData = writable(null); export const materialData = writable(); @@ -45,41 +50,3 @@ export const materialFromMachineQuery = writable({}); export const referenceFromPage = writable(''); export const currentEditingRecipeProductCode = writable(''); - -let worker: Worker | null = null; -let initialized = false; - -export function loadRecipe(url: string) { - if (initialized) return; - initialized = true; - - recipeLoading.set(true); - - worker = new Worker(new URL('../../workers/data.worker.ts', import.meta.url), { - type: 'module' - }); - - worker.onmessage = (e) => { - const { type, payload } = e.data; - if (type === 'data') { - recipeData.set(payload); - recipeLoading.set(false); - } - - if (type === 'error') { - recipeDataError.set(payload); - recipeLoading.set(false); - } - }; - - worker.postMessage({ url }); -} - -export function getWorker() { - if (!worker) { - worker = new Worker(new URL('../../workers/data.worker.ts', import.meta.url), { - type: 'module' - }); - } - return worker; -} diff --git a/src/lib/core/stores/websocketStore.ts b/src/lib/core/stores/websocketStore.ts index 40f7c60..88bfee9 100644 --- a/src/lib/core/stores/websocketStore.ts +++ b/src/lib/core/stores/websocketStore.ts @@ -43,15 +43,15 @@ export function connectToWebsocket() { } // recover messages on connect, flushing - while (get(msgQueue).length) { - let queue = get(msgQueue); - let current = queue.shift(); - if (current && socket) { - socket.send(current); - // set next - msgQueue.set(queue); - } - } + // while (get(msgQueue).length) { + // let queue = get(msgQueue); + // let current = queue.shift(); + // if (current && socket) { + // socket.send(current); + // // set next + // msgQueue.set(queue); + // } + // } // heartbeat 10s setInterval(() => { diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts index 87b16c9..449745e 100644 --- a/src/lib/core/types/outMessage.ts +++ b/src/lib/core/types/outMessage.ts @@ -4,7 +4,7 @@ export type OutMessage = | { type: 'lock'; payload: { field: string } } | { type: 'general'; payload: string } | { - type: 'recipe'; + type: 'recipe' | 'recipe_versions'; payload: { auth: string; partial: boolean; diff --git a/src/lib/data/recipeService.ts b/src/lib/data/recipeService.ts index f5b4984..778698d 100644 --- a/src/lib/data/recipeService.ts +++ b/src/lib/data/recipeService.ts @@ -110,7 +110,10 @@ function buildOverviewFromServer() { let r01_query: any = {}; - if (server_recipe01) { + // console.log('server recipe 01: ', server_recipe01); + + if (server_recipe01 != null) { + // console.log('building overview from server'); recipeOverviewData.set([]); for (let rp of server_recipe01) { result.push({ diff --git a/src/lib/models/recipe_version.model.ts b/src/lib/models/recipe_version.model.ts new file mode 100644 index 0000000..e1e37a5 --- /dev/null +++ b/src/lib/models/recipe_version.model.ts @@ -0,0 +1,4 @@ +export interface RecipeVersion { + display_version: string; + actual_version_name: string; +} diff --git a/src/routes/(authed)/departments/+page.svelte b/src/routes/(authed)/departments/+page.svelte index 4c2a9db..869544a 100644 --- a/src/routes/(authed)/departments/+page.svelte +++ b/src/routes/(authed)/departments/+page.svelte @@ -10,7 +10,7 @@ import { addNotification } from '$lib/core/stores/noti'; import { browser } from '$app/environment'; import { setCookieOnNonBrowser } from '$lib/helpers/cookie'; - import { referenceFromPage} from '$lib/core/stores/recipeStore'; + import { referenceFromPage } from '$lib/core/stores/recipeStore'; let enabledAccessibleCountries: string[] = $state([]); @@ -23,7 +23,8 @@ addNotification(`INFO:Selected ${cnt}`); setTimeout(async () => { console.log(get(departmentStore)); - + departmentStore.set(cnt); + if (refPage === 'sheet') { await goto('/sheet/overview'); } else { diff --git a/src/routes/(authed)/recipe/overview/+page.svelte b/src/routes/(authed)/recipe/overview/+page.svelte index e3f0814..6866ad9 100644 --- a/src/routes/(authed)/recipe/overview/+page.svelte +++ b/src/routes/(authed)/recipe/overview/+page.svelte @@ -1,25 +1,30 @@