Compare commits

...

478 Commits

Author SHA1 Message Date
warren30815 73c11c9c49 ui wish and bug fix 2022-03-31 11:30:05 +08:00
warren30815 6b16f3e8b4 spike ui typo 2022-03-29 17:20:43 +08:00
warren30815 6758d98e03 spike ui bug fix 2022-03-29 16:57:30 +08:00
warren30815 6514626c02 [remove] filetable upload multiple sd 2022-03-29 11:35:29 +08:00
warren30815 82c154d360 filetable upload multiple sd (to be tested) 2022-03-28 18:08:18 +08:00
warren30815 76de87368b [add] spike ble data can also set start / end time 2022-03-28 15:53:44 +08:00
warren30815 ae6646273a [add] spike can set start / end time 2022-03-28 13:58:39 +08:00
warren30815 f30720cf36 fix spike bug 2022-03-28 10:25:09 +08:00
warren30815 5f8a47215c fix sorting bug 2022-03-23 18:18:12 +08:00
warren30815 a8018c84b8 ble replay move up / down bug fix 2022-03-23 14:49:24 +08:00
warren30815 c028814a1c [fix] offline download bug 2022-03-22 16:38:41 +08:00
warren30815 0ae013521c [add] record / stimulate all button 2022-03-22 15:11:20 +08:00
warren30815 2008b086f7 [modify] unified ble replay move up / down 2022-03-22 14:40:47 +08:00
warren30815 81fe11c7b6 [add] ble replay move right / left 2022-03-22 14:35:57 +08:00
warren30815 595c9bcb91 fill ble replay data loss with nan 2022-03-22 13:57:47 +08:00
warren30815 32b3000d59 reset replay download to open another page 2022-03-21 11:46:29 +08:00
warren30815 de76e098db reset download to open another page 2022-03-21 11:27:50 +08:00
warren30815 ab75887da2 delete 16k 2022-03-21 11:03:19 +08:00
warren30815 26f3d2abfc [add] neulive 3.1 16k sample rate 2022-03-21 10:40:37 +08:00
warren30815 3ffe0463c6 bug fix 2022-03-18 13:46:56 +08:00
warren30815 6536267b1c [add] sd timestamp offset 2022-03-18 13:43:43 +08:00
warren30815 60eb85d977 refresh spike overview window when reloading 2022-03-17 22:22:42 +08:00
warren30815 cbd5ad79a7 sd file size estimation adjustment 2022-03-17 16:59:53 +08:00
warren30815 b4483c9bc1 ui wish and bug fix 2022-03-17 16:39:21 +08:00
warren30815 29af33de31 [modify] replay filter ui flow wish 2022-03-17 11:35:54 +08:00
warren30815 0ea3731e71 ui wish 2022-03-17 10:51:15 +08:00
warren30815 c28ecabf65 Merge branch 'feature/spike_with_sd_data' into feature/testing_20220317 2022-03-17 10:27:57 +08:00
warren30815 d3b6530347 timestamp bug fix 2022-03-17 10:26:06 +08:00
warren30815 9aff6225d1 [add] download / replay consider multiple chip timestamp 2022-03-16 15:47:48 +08:00
warren30815 45bf01d178 replay filter ui wish 2022-03-16 15:11:09 +08:00
warren30815 edc6592f31 3.5ex ui wish 2022-03-16 15:05:16 +08:00
warren30815 60f3fe3089 [add] spike front-end 2022-03-16 14:11:01 +08:00
warren30815 c4a6de8235 filter consider scale rate (10, 100 or 1000:1) 2022-03-15 14:28:20 +08:00
warren30815 a140c9983b [fix] download timestamp float precision bug / sd status button disable rule 2022-03-15 13:34:23 +08:00
warren30815 5e0499d739 [modify] replay filter ui flow 2022-03-15 13:11:00 +08:00
warren30815 ef6d518377 sd download export bug fix 2022-03-15 10:42:47 +08:00
warren30815 27aff31a45 replay downsampling 2022-03-14 18:10:12 +08:00
warren30815 172ab999fe replay bug fix 2022-03-14 17:38:18 +08:00
warren30815 3723df9bf8 free unused memory 2022-03-14 16:47:19 +08:00
warren30815 bdbfeab245 Merge branch 'feature/replay_filter' into release/20220314_CHEN-PO-HSI 2022-03-14 10:45:55 +08:00
warren30815 99fcdc9b4b [add] change name function also apply to new meta table 2022-03-11 17:03:40 +08:00
warren30815 5bdc7cd67f [add] replay init / downloading status 2022-03-11 15:53:15 +08:00
warren30815 623196521b [init] sd data can be used (but need to optimize display performance) 2022-03-10 18:46:43 +08:00
warren30815 9be35f5541 Merge remote-tracking branch 'origin/feature/replay_filter' into feature/replay_filter 2022-03-09 10:25:37 +08:00
warren30815 bc6971926d modify filter ui flow 2022-03-09 10:24:30 +08:00
warren30815 b47836bc62 [add] replay global filter 2022-03-09 10:24:30 +08:00
warren30815 f3d55099a6 fix filetable bug 2022-03-08 17:38:51 +08:00
warren30815 4ac8dfb12f fix filetable bug 2022-03-08 17:38:18 +08:00
warren30815 876add4789 modify filter ui flow 2022-03-08 17:37:44 +08:00
warren30815 64bf6fe313 fix file table display bug 2022-03-08 13:45:11 +08:00
warren30815 1d6dbbaa66 [add] replay global filter 2022-03-07 22:50:28 +08:00
warren30815 9743290e69 ui wish 2022-03-07 15:57:10 +08:00
warren30815 bb7e8287fd ui wish 2022-03-07 14:44:30 +08:00
Jordan Hsu 2ccca9b389 sd enable param 2022-03-03 09:43:15 +08:00
Jordan Hsu 62a2bad4a6 fix download filename bug 2022-03-02 18:39:48 +08:00
Jordan Hsu f8ed7c7ffd fix sd replay bug 2022-03-02 18:27:14 +08:00
Jordan Hsu 600abf40e6 fix replay draw bug 2022-03-02 17:43:46 +08:00
Jordan Hsu ae70d6d6d2 [fix] replay download file bug 2022-03-02 16:18:17 +08:00
Jordan Hsu dfad50aad7 3.5 ui wish 2022-03-02 14:56:11 +08:00
Jordan Hsu ea7a284b65 [fix] info button bug 2022-03-02 12:21:16 +08:00
Jordan Hsu 5b994f982a [add] workaround for sd raw data not start from 0s 2022-03-02 11:27:48 +08:00
Jordan Hsu 763016b105 hide gimer route 2022-03-01 16:20:34 +08:00
Jordan Hsu d7eca79154 [modify] trigger / sdcard reader icon 2022-03-01 16:17:57 +08:00
Jordan Hsu 26e0a82aa8 download file table not to open another page for avoiding window os bug 2022-03-01 12:04:53 +08:00
Jordan Hsu 7e9775bd34 3.5ex get storage bug fix 2022-02-25 16:09:23 +08:00
Jordan Hsu d067b0da6b [modify] more friendly progress bar 2022-02-25 15:18:59 +08:00
Jordan Hsu d5dd7c2321 clear unused code 2022-02-25 14:22:36 +08:00
Jordan Hsu 2158f19310 3.5ex log 2022-02-25 13:43:55 +08:00
Jordan Hsu 1b68eaef71 [add] 3.1/3.5 chip sample rate 2022-02-25 13:41:06 +08:00
Jordan Hsu 201f8f80cf [add] 3.5ex chip sample rate 2022-02-25 13:22:24 +08:00
Jordan Hsu ba487da5f4 remove tedious code 2022-02-24 17:51:58 +08:00
Jordan Hsu 6ce33876a3 fix sd card reader ui bug 2022-02-24 17:27:03 +08:00
Jordan Hsu f2c18924ae [fix] progress bar estimated time bug 2022-02-24 16:05:43 +08:00
Jordan Hsu d80f1dc996 [modify] odd code 2022-02-24 14:54:56 +08:00
Jordan Hsu 1c90566e64 [add] get sd storage and estimate remaining time 2022-02-24 14:46:20 +08:00
Jordan Hsu 4b2caf0964 to be tested 2022-02-24 10:37:54 +08:00
Jordan Hsu f87ef1fb54 [modify] sd upload progress bar estimated time 2022-02-23 17:22:22 +08:00
Jordan Hsu bf83da0649 [add] sd mount / enable api integration 2022-02-23 16:39:42 +08:00
Jordan Hsu 5cb772a18a [fix] sd upload check bug 2022-02-23 10:12:47 +08:00
Jordan Hsu 77901c3667 replay param table ready for testing 2022-02-22 18:28:33 +08:00
Jordan Hsu 98aacadec9 3.5ex param table 2022-02-22 17:23:44 +08:00
Jordan Hsu fa572f3117 [add] neulive replay param table 2022-02-22 00:14:08 +08:00
Jordan Hsu 6f538bea59 tmp 2022-02-21 22:50:30 +08:00
Jordan Hsu 87908e086c [add] sd replay up / down click event 2022-02-21 16:14:15 +08:00
Jordan Hsu c45faf8ce1 [modify] replay layout 2022-02-18 12:01:38 +08:00
Jordan Hsu 8b0cd8c7af [modify] sd replay when move datazoom reset to closest data chunk 2022-02-17 17:18:52 +08:00
Jordan Hsu e17cdd1dea [modify] chunk index start from 1 2022-02-16 17:02:48 +08:00
Jordan Hsu 14a5474094 fix replay sd button bug by reset param 2022-02-15 17:55:18 +08:00
Jordan Hsu a7297e2e85 [add] trigger file table init condition 2022-02-15 15:26:25 +08:00
Jordan Hsu 787aaa2bf8 [fix] trigger download 2022-02-15 11:37:48 +08:00
Jordan Hsu 53bcef0f60 fix real-time tooltip bug 2022-02-14 18:01:23 +08:00
Jordan Hsu ea2b3a0472 replay bug fix 2022-02-14 16:13:46 +08:00
Jordan Hsu 7987f26021 replay bug fix 2022-02-14 15:00:30 +08:00
Jordan Hsu ea2e8b23d8 data time step bug fix 2022-02-14 14:50:10 +08:00
Jordan Hsu 9ceee6b3d1 remove redundant code 2022-02-14 14:30:13 +08:00
Jordan Hsu bc3b466312 bug fix 2022-02-14 14:04:43 +08:00
Jordan Hsu 0519f04acc [add] missing code 2022-02-13 16:21:58 +08:00
Jordan Hsu eff22d1494 [add] sample rate per chip info 2022-02-13 15:48:50 +08:00
Jordan Hsu 54c8d2ac48 [modify] sd timestamp calculated by sample rate and channel amount 2022-02-13 15:08:22 +08:00
Jordan Hsu 4836ffcde9 [fix] configuration refresh bug 2022-02-12 17:16:53 +08:00
Jordan Hsu 1ac306c419 Merge branch 'feature/configuration_1.0' into release/AS_20220112 2022-02-12 16:28:50 +08:00
Jordan Hsu 7c93cf6620 [add] sd card reader front-end 2022-02-12 16:20:29 +08:00
Jordan Hsu 64936c0cf1 [modify] sd replay show 1/10 data 2022-02-10 16:51:46 +08:00
Jordan Hsu 5868caaec2 [modify] trigger mqtt data format 2022-02-10 11:04:25 +08:00
Jordan Hsu c938a9b001 sd card log 2022-02-10 10:31:46 +08:00
Jordan Hsu 801babed35 [add] sd card download whole pipeline 2022-02-09 19:53:34 +08:00
Jordan Hsu 1459128428 front-end for new db format 2022-02-09 17:23:46 +08:00
Jordan Hsu d01a772a67 sd replay data ready 2022-02-08 16:56:19 +08:00
Jordan Hsu ef6e7197a8 sd download online/offline ready for testing 2022-02-08 14:16:42 +08:00
Jordan Hsu 34e8294288 Merge branch 'feature/trigger_download' into release/AS_20220112 2022-02-08 11:09:42 +08:00
Jordan Hsu 0a7b679aa9 tmp 2022-02-07 21:08:47 +08:00
Jordan Hsu f53d88f2cf remove unreasonable code 2022-01-29 11:19:32 +08:00
Jordan Hsu 1dc09f4afa remove unused code 2022-01-28 18:55:01 +08:00
Jordan Hsu e4e995e694 sd replay and download ready for real data testing 2022-01-28 18:51:36 +08:00
Jordan Hsu 9d6f99e96b [add] trigger download page 2022-01-26 16:38:45 +08:00
Jordan Hsu ff0dc2622b [add] fake replay sd card data (wait for api) 2022-01-22 18:38:01 +08:00
Jordan Hsu bae437c26b [modify] ble download button color to 'success' 2022-01-21 16:57:29 +08:00
Jordan Hsu 78b2d947fe replay code cleaning... 2022-01-21 16:45:43 +08:00
Jordan Hsu f61f6f178d [modify] download csv button color 2022-01-18 12:01:46 +08:00
Jordan Hsu 4295fc02e2 [modify] filetable sd card icon 2022-01-17 16:47:08 +08:00
Jordan Hsu 2ef4b4b393 [fix] sd card icon bug 2022-01-17 15:03:37 +08:00
Jordan Hsu 93c4692dc0 [add] 3.5ex missing code 2022-01-17 12:25:38 +08:00
Jordan Hsu f92f4ebb6c [add] 3.5ex sample rate & sti constraint 2022-01-17 11:42:23 +08:00
Jordan Hsu c984bebf6f [modify] separate chartKey and panelKey 2022-01-14 17:26:13 +08:00
Jordan Hsu 06766f1ac4 [add] can save chart source setting 2022-01-14 15:54:06 +08:00
Jordan Hsu 65ca86b38d Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-14 12:21:55 +08:00
Jordan Hsu 5d3da784cb [fix] 3.5ex not to download empty file, will have bug 2022-01-14 12:21:30 +08:00
Jordan Hsu 8d05136dcc Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-14 11:47:08 +08:00
Jordan Hsu 8c2c33fdd0 fix 3.5ex bug 2022-01-14 11:46:32 +08:00
Jordan Hsu 3c5596144f Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-13 17:17:58 +08:00
Jordan Hsu 02eae10c82 [add] 3.5 ex download filename 2022-01-13 17:16:57 +08:00
Jordan Hsu 9976903789 Merge branch 'testing/trigger' into release/AS_20220112 2022-01-13 16:58:41 +08:00
Jordan Hsu 7ce9b2408b Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-13 16:58:12 +08:00
Jordan Hsu 764d62dc9c [add] 3.5 extension board download format 2022-01-13 16:46:31 +08:00
Jordan Hsu 9981958160 typo 2022-01-12 18:06:11 +08:00
Jordan Hsu e1d6dbd38e fix chart data display bug 2022-01-12 18:01:06 +08:00
Jordan Hsu 96b38e5570 fix cache chart bug 2022-01-12 15:52:23 +08:00
Jordan Hsu dba3e118f4 Merge branch 'testing/security' into feature/AS_wish 2022-01-12 09:53:43 +08:00
Jordan Hsu ffd246124f tmp 2022-01-10 17:12:27 +08:00
Jordan Hsu 834bcbc4df add missing code 2022-01-10 15:56:25 +08:00
Jordan Hsu 8f6f437f14 [add] missing localforage package dependency 2022-01-10 15:49:17 +08:00
Jordan Hsu e5bc63e5d5 [add] real-time chart add / remove update with cache 2022-01-10 15:38:55 +08:00
Jordan Hsu 5d3dbbac4f [modify] real-time chart data object to vuex 2022-01-10 12:56:35 +08:00
Jordan Hsu 8c53573857 [add] trigger will save param when start 2022-01-06 16:42:18 +08:00
Jordan Hsu 9a77e6dcff [modify] neulive 3.5 sti constraint 2022-01-06 13:09:52 +08:00
Jordan Hsu c8644f320a [fix] display bug when F5 2022-01-05 16:42:35 +08:00
Jordan Hsu 8b6348e77e bug fix and modify trigger mode option default to output 2022-01-05 16:04:37 +08:00
Jordan Hsu 48555dd793 [modify] trigger will automatically open chart when starting 2022-01-05 12:51:48 +08:00
Jordan Hsu 02617bbb86 [fix] trigger display order bug 2022-01-05 12:50:19 +08:00
peterlu14 5328bd76ba [fix] sd data from api 2022-01-04 14:13:38 +08:00
Jordan Hsu 9d19fd3fc9 add neu 3.1 sample rate option 2022-01-04 11:14:46 +08:00
Alan Lin 1e2bf182bf neu31 update adc_clk ratio formula 2022-01-04 11:03:23 +08:00
Alan Lin 288f9b325c update download SD function 2021-12-27 18:22:03 +08:00
Jordan Hsu e4a8f1e8d3 bug fix 2021-12-27 17:41:29 +08:00
Jordan Hsu 48ce6e6342 [add] chart hide initially and adjust x axis 2021-12-24 16:39:28 +08:00
TommyXin 7f0564a3ca Merge remote-tracking branch 'origin/neulive_hide_gimer' into testing/trigger 2021-12-24 12:05:05 +08:00
Jordan Hsu aa9c8c21b7 [modify] trigger mqtt route 2021-12-24 11:40:23 +08:00
TommyXin 0924c20e9e Merge remote-tracking branch 'origin/neulive_hide_gimer' into testing/trigger 2021-12-22 18:21:59 +08:00
Jordan Hsu a239223361 [add] trigger chart 2021-12-22 17:33:03 +08:00
peterlu14 444663def3 Merge branch 'feature/dpv' into testing/security 2021-12-22 13:36:39 +08:00
Jordan Hsu e0341356c8 [add] trigger list ui with mqtt 2021-12-21 16:26:45 +08:00
Jordan Hsu b56c753063 [add] trigger list ui layout 2021-12-21 10:12:39 +08:00
Jordan Hsu 04741d8a77 [modify] triggerList to vuex 2021-12-21 10:10:34 +08:00
Jordan Hsu 3721e4a515 [add] device overview with trigger 2021-12-20 16:11:01 +08:00
Alan Lin 0afd9edd7b [fix] download & sd_card click 2021-12-15 17:32:51 +08:00
TommyXin 44d2ba6b41 fix google font error 2021-12-14 18:50:53 +08:00
TommyXin d567186240 fix sti3.5ex channel display 2021-12-07 13:38:46 +08:00
TommyXin cbb81add56 fix 3.5 sti ex ch error 2021-12-07 13:31:50 +08:00
TommyXin c8dded35f5 neulive3.5 sti ex 2021-12-06 18:26:50 +08:00
TommyXin d5dac7a9b3 Merge branch 'neulive_hide_gimer' into testing/security 2021-12-01 16:31:27 +08:00
TommyXin 1a183322ab fix device id not found 2021-12-01 10:55:54 +08:00
TommyXin 7e401621c7 fix default chart generator only consider RECORDING_CH 2021-12-01 10:45:10 +08:00
TommyXin 1485eba2b3 fix check ch number 2021-11-30 10:31:59 +08:00
TommyXin e908f73857 fix file can not show data 2021-11-29 18:40:30 +08:00
TommyXin f587f2b689 fix sd-card api 2021-11-29 18:20:32 +08:00
TommyXin 12eb41fcad fix sd-card icon 2021-11-29 17:38:34 +08:00
TommyXin 4a2b3f6a94 fix font error & auto get controller id 2021-11-29 17:03:34 +08:00
TommyXin 8e9dd79002 change id 2021-11-26 10:34:33 +08:00
TommyXin ea2f9c6748 Merge branch 'neulive_hide_gimer' into testing/security 2021-11-26 10:31:42 +08:00
TommyXin 4af2178225 Merge remote-tracking branch 'origin/feature/AS_wish' into testing/security 2021-11-26 10:31:26 +08:00
peterlu14 cc451f53cc [update] fix realtime filter channel & collapse open 2021-11-25 15:15:14 +08:00
Jordan Hsu 419b8e373f [add] sti pattern sketch map 2021-11-25 15:13:55 +08:00
peterlu14 ffd2b611d3 [update] fix realtime filter channel & collapse open 2021-11-25 14:43:57 +08:00
Jordan Hsu e05fa1c26b [add] chart pointer 2021-11-25 11:00:19 +08:00
Jordan Hsu 98d431fac0 [modify] filter popup without confirm button 2021-11-25 10:31:50 +08:00
Jordan Hsu c1fb4e3488 [add] select all / inverse select button 2021-11-24 18:17:55 +08:00
Jordan Hsu 3239728b0d [add] auto-resize real-time grid 2021-11-24 17:54:06 +08:00
Jordan Hsu 8941e827c1 [add] individual grid iir filter 2021-11-24 15:14:06 +08:00
Jordan Hsu d2eb180381 tmp 2021-11-23 18:14:59 +08:00
peterlu14 01a55781dd [update] sdcard filename change & modal style 2021-11-23 16:39:39 +08:00
warren30815 c2e8aaae06 fix EliteDataReplay overview bug 2021-11-23 02:04:50 +00:00
peterlu14 594fc9da13 [update] update message & api server fail not show 2021-11-22 18:38:06 +08:00
Jordan Hsu 721bab177b [fix] replay bug 2021-11-22 17:11:55 +08:00
peterlu14 6b7828a944 [update] sd card upload show message 2021-11-19 18:03:14 +08:00
Roy c388474952 update global chart lib 2021-11-19 11:42:52 +08:00
peterlu14 75db0a3c13 [update] new module vue notification 2021-11-18 20:42:15 +08:00
Alan Lin 802d6f7d3a swap stop all ins 2021-11-18 17:22:10 +08:00
peterlu14 830e93d9d1 [update] notify rebuild 2021-11-18 14:15:52 +08:00
Jordan Hsu 09c46a282b Merge remote-tracking branch 'origin/feature/download_offline' into neulive_hide_gimer 2021-11-18 12:04:44 +08:00
peterlu14 50693075f9 [update] add sercurity feature 2021-11-17 19:06:48 +08:00
Jordan Hsu 9d22f80c0e [add] elite replay can set window start and size 2021-11-16 16:32:05 +08:00
Jordan Hsu 41a7991255 Merge remote-tracking branch 'origin/feature/dpv' into feature/dpv 2021-11-16 16:31:06 +08:00
peterlu14 623ff914bf Merge branch 'feature/download_offline' into feature/dpv 2021-11-16 14:49:53 +08:00
peterlu14 99dc8d7996 [update] excel format fix 2021-11-16 14:47:36 +08:00
peterlu14 e30416e910 Merge branch 'feature/download_offline' into feature/dpv 2021-11-16 11:37:35 +08:00
peterlu14 72a9ded776 [update] recover controller id 2021-11-15 15:59:06 +08:00
peterlu14 dfbbcdedf1 [update] remove download time limit 2021-11-12 11:01:30 +08:00
Jordan Hsu 1bcbfa953d [add] dpv cycle & check E final 2021-11-11 16:53:39 +08:00
peterlu14 282e435217 [update] add user guide 2021-11-10 16:22:23 +08:00
peterlu14 23d45cfa2f [update] remove device store 2021-11-10 16:19:02 +08:00
peterlu14 da4049bf77 [update] remove guide doc 2021-11-10 16:08:52 +08:00
peterlu14 73ccc68220 [update] connection timeout 2021-11-10 13:24:36 +08:00
Jordan Hsu 25f36a54dd [fix] pdf path error 2021-11-10 11:59:31 +08:00
peterlu14 0bfba093fd [update] download 2021-11-10 10:49:16 +08:00
Jordan Hsu 5e0b14c8e0 [add] device guideline pdf 2021-11-09 20:23:50 +08:00
Jordan Hsu df6e05c8c0 fix trigger UI bug 2021-11-08 14:33:00 +08:00
peterlu14 36c72e27a6 Merge branch 'feature/download_offline' into neulive_hide_gimer 2021-11-05 18:40:24 +08:00
peterlu14 a141535fc9 Merge remote-tracking branch 'origin/feature/neulive31_35_sti_1104' into neulive_hide_gimer 2021-11-05 18:40:11 +08:00
peterlu14 50a2254d44 [update] hide gimer icon 2021-11-05 18:39:55 +08:00
Jordan Hsu 3aaefa6529 bug fix 2021-11-05 17:53:50 +08:00
peterlu14 74d8fdb077 [update] add package file-saver 2021-11-04 22:25:38 +08:00
peterlu14 5b655b2cd0 [update] offline download 2021-11-04 21:51:22 +08:00
Jordan Hsu 8e7f0ecb71 [modify] neulive 3.1/3.5 sti 2021-11-04 14:08:16 +08:00
peterlu14 c8aae625e5 [update] remove offline download directory 2021-11-04 13:58:17 +08:00
peterlu14 a1bb7517f7 [update] offline download excel format & memory & data loss 2021-11-03 10:12:29 +08:00
TommyXin fc65108051 fix bug repeat interval 2021-11-02 17:05:12 +08:00
TommyXin ff0be97176 hide repeatNumber when unlimit cycle 2021-11-02 17:00:29 +08:00
TommyXin 6cdd0f772e fix check recording channels 2021-11-02 16:29:34 +08:00
TommyXin b61f51cab2 cycle unlimited no setStiTimeout & check recording one channel at least 2021-11-02 15:43:26 +08:00
Jordan Hsu b00649f03e bug fix 2021-11-01 18:34:45 +08:00
Jordan Hsu 755525dc28 [fix] elite replay cycle bug 2021-11-01 17:36:34 +08:00
Jordan Hsu 91929c0a34 Merge branch 'release/AS_1027' into feature/dpv 2021-11-01 17:04:30 +08:00
Jordan Hsu 389040a98a [add] elite replay param table 2021-11-01 16:49:50 +08:00
Jordan Hsu 2908f2c510 [modify] replay UI layout 2021-11-01 16:04:32 +08:00
Jordan Hsu 08c732ace8 toggle color to green 2021-10-29 18:54:19 +08:00
Jordan Hsu 73fbaf92ab adjust trigger UI 2021-10-29 18:50:49 +08:00
Jordan Hsu 80d8bdf7f9 [add] trigger block 2021-10-29 17:15:53 +08:00
Jordan Hsu 40c09de1dd [modify] UI disable mode color 2021-10-29 16:00:17 +08:00
Jordan Hsu 1541a57b1b [modify] dpv UI and switch 2021-10-29 15:32:19 +08:00
Jordan Hsu 2f5ef791e4 [modify] dpv rule 2021-10-29 15:18:25 +08:00
Jordan Hsu cc64f62335 Merge branch 'feature/elite_trigger' into feature/dpv 2021-10-28 14:52:33 +08:00
Jordan Hsu 66c19ab51b [modify] dpv slider two pins 2021-10-28 14:51:48 +08:00
Jordan Hsu fa7b51143b [update] neulive 3.5 repeatIntervalCheck with cycle 2021-10-28 09:54:45 +08:00
peterlu14 7c300a4ce2 [update] neulive3.5 repeatIntervalCheck 2021-10-28 00:04:32 +08:00
TommyXin e6a9a3fbd6 Merge branch 'neulive_sunpower' into release/AS_1027 2021-10-27 22:16:35 +08:00
TommyXin f523c189dc remove console.log 2021-10-27 22:15:42 +08:00
TommyXin bc86b2f541 Merge branch 'neulive_sunpower' into release/gimer_1026 2021-10-27 22:14:05 +08:00
peterlu14 5ea023df43 [update] remove filetable get sd card uuidList 2021-10-27 22:08:57 +08:00
TommyXin ebf912a971 clear stiTimeOut 2021-10-27 21:59:15 +08:00
TommyXin d440dfbe39 clear stiTimeOut 2021-10-27 21:51:52 +08:00
Jordan Hsu 8257a30118 [modify] elite replay layout 2021-10-27 16:29:50 +08:00
Jordan Hsu 68cec7d482 [fix] abs toggle bug 2021-10-27 16:28:42 +08:00
TommyXin e61d4340a7 gimer id 2021-10-27 16:12:33 +08:00
TommyXin 810e8c46e9 update 2021-10-27 16:12:12 +08:00
TommyXin 1c32850990 remove gimer routes 2021-10-27 16:02:46 +08:00
TommyXin 21bb7440f1 Merge remote-tracking branch 'origin/release/gimer_1026' into release/gimer_1026 2021-10-27 15:50:32 +08:00
TommyXin 505a2a9e03 Merge branch 'neulive_sunpower' into release/gimer_1026 2021-10-27 15:48:13 +08:00
TommyXin f6a6c4101d update gimer icon 2021-10-27 15:47:34 +08:00
TommyXin c4fc506f9f ch number for filter 2021-10-27 15:28:20 +08:00
TommyXin 3a8315339a Merge remote-tracking branch 'origin/neulive_sunpower' into neulive_sunpower 2021-10-27 15:27:06 +08:00
TommyXin 08b3904cb9 channel number & amplitude change 2021-10-27 15:26:55 +08:00
peterlu14 b2ba658e6e [update] add library name list 2021-10-27 15:12:21 +08:00
peterlu14 76a16cae1a [update] ampgain diff from library 2021-10-27 15:01:21 +08:00
peterlu14 04d564afd8 [update] offline download 2021-10-27 14:49:47 +08:00
TommyXin 9c42c05621 [bug] sti ex time = interval * number 2021-10-27 14:46:33 +08:00
Jordan Hsu 42925bec2d Merge branch 'release/neulive_elite_1012' into feature/dpv 2021-10-27 13:56:51 +08:00
Jordan Hsu 5be30fc910 [modify] elite replay abs button style 2021-10-27 13:55:36 +08:00
Jordan Hsu c26a72749e [fix] dpv bug 2021-10-27 13:21:23 +08:00
Jordan Hsu ca41619338 [modify] elite replay UI layout 2021-10-27 12:10:28 +08:00
Jordan Hsu 4e119b5eaf [modify] dpv UI layout 2021-10-27 11:16:56 +08:00
TommyXin 468c5a2567 regular 3.5 check sti done 2021-10-27 11:01:43 +08:00
TommyXin 70651f55c0 [update] neulive regular 3.5 UI 2021-10-27 10:33:19 +08:00
Jordan Hsu 02aff9f02c [fix] typo 2021-10-27 10:21:42 +08:00
Jordan Hsu c1dbf93d65 [add] missing formula 2021-10-27 00:29:26 +08:00
Jordan Hsu 6effc08904 [init] trigger UI 2021-10-27 00:03:39 +08:00
TommyXin f237efc430 Merge remote-tracking branch 'origin/feature/sd_card_check_and_init' into release/gimer_1026 2021-10-26 18:45:34 +08:00
TommyXin ab15129100 Merge branch 'neulive_sunpower' into release/gimer_1026 2021-10-26 18:43:46 +08:00
TommyXin 38f440ea67 add gimer icon to app sidebar 2021-10-26 18:32:18 +08:00
peterlu14 6c9fb2e513 [update] update transmission_type status 2021-10-26 17:15:05 +08:00
peterlu14 92e8c15308 [update] restruct download 2021-10-26 17:13:42 +08:00
TommyXin 7c6a05ecc4 amplitude * 10 (for UI) 2021-10-26 16:15:54 +08:00
TommyXin f63fca5fc4 remove console.log 2021-10-26 14:46:21 +08:00
TommyXin b59262643f Merge remote-tracking branch 'origin/feature/sd_card_check_and_init' into neulive_sunpower 2021-10-26 14:36:20 +08:00
TommyXin 2650ba10fb Merge branch 'feature/dpv' into neulive_sunpower 2021-10-26 14:35:56 +08:00
TommyXin 578578a312 multi filter & check stimulation 2021-10-26 14:35:04 +08:00
peterlu14 6e700e7201 [update] disable device_update_usb 2021-10-26 11:31:01 +08:00
TommyXin 99ef7bfe04 Merge branch 'neulive_sunpower' into feature/dpv 2021-10-26 11:07:52 +08:00
TommyXin 0d88866001 sti check exTime 2021-10-26 11:07:25 +08:00
TommyXin 0150eebb0c fix amp gain bug 2021-10-26 10:36:31 +08:00
Jordan Hsu f885812258 [add] missing code 2021-10-26 09:21:40 +08:00
Jordan Hsu 21f5c75836 [init] dpv front end with formula 2021-10-25 16:34:38 +08:00
TommyXin bf20a50107 realtime filter [notch] 2021-10-25 15:36:28 +08:00
TommyXin d72e59bb81 amp gain change 500&300 2021-10-22 22:00:48 +08:00
TommyXin 69058bd0f0 deviceList vuex 2021-10-22 21:25:45 +08:00
TommyXin 6639560dd4 Merge remote-tracking branch 'origin/release/neulive_elite_1012' into neulive_sunpower 2021-10-22 17:26:42 +08:00
Jordan Hsu 3ed4253b13 [update] gimer / 3.5 t range constraint rule 2021-10-22 11:27:25 +08:00
Jordan Hsu c6555cb4f3 Merge branch 'neulive_sunpower' into release/neulive_elite_1012 2021-10-21 14:18:55 +08:00
TommyXin 62d4b634c2 set power enable while record 2021-10-21 14:09:35 +08:00
Jordan Hsu a8e2ec509e [add] repeat interval rule 2021-10-21 13:51:24 +08:00
Jordan Hsu 5c35bff99f [add] gimer t range rule 2021-10-20 18:15:01 +08:00
TommyXin 2e76071ea6 Merge branch 'iir_filter' into neulive_sunpower 2021-10-20 15:21:38 +08:00
TommyXin ce9cac1f02 gimer ui 2021-10-20 15:20:18 +08:00
Jordan Hsu feb9c84488 [add] abs toggle 2021-10-19 19:37:04 +08:00
TommyXin ac0aef6109 [TODO] filter by chart and detect device from ch (get fs) 2021-10-19 18:47:47 +08:00
Jordan Hsu 1b424bcfd2 [fix] elite replay param panel mode bug 2021-10-19 15:15:05 +08:00
Jordan Hsu 05b33bd9bf [fix] neulive replay missing code 2021-10-19 15:14:12 +08:00
Jordan Hsu 62424e3438 [modify] replay axis log scale 2021-10-19 14:46:57 +08:00
TommyXin 16799a85bd Merge branch 'neulive_sunpower' into iir_filter 2021-10-19 14:11:35 +08:00
TommyXin 7f295cc86e Merge remote-tracking branch 'origin/feature/sd_card_check_and_init' into iir_filter 2021-10-19 14:11:16 +08:00
TommyXin 5651cb68a5 fix button 256 2021-10-19 08:57:43 +08:00
TommyXin 80e1841776 repeat num 255 2021-10-18 19:17:26 +08:00
TommyXin af337bdde6 check stimulating & auto close save power node while recording 2021-10-18 16:27:20 +08:00
TommyXin 9e59da8fd1 gimer start condition 2021-10-18 14:23:45 +08:00
TommyXin df2815eb0e sti check recording status 2021-10-15 19:05:11 +08:00
TommyXin 70750de23d taskDeviceList doesn't destroy deviceList 2021-10-15 18:12:57 +08:00
TommyXin 89f45520d3 fix hide device panel bug 2021-10-15 17:31:17 +08:00
peterlu14 8b08cd899b [update] usb & rename api & check sd usb when connect 2021-10-15 16:02:11 +08:00
TommyXin e808352244 electrode site support double color button 2021-10-15 16:00:19 +08:00
TommyXin 4e777fff1b check device status 2021-10-14 19:00:19 +08:00
peterlu14 3e9db25dad [update] recover controller id 2021-10-14 11:03:51 +08:00
peterlu14 033088ddde [update] sdcard check and init 2021-10-13 22:01:50 +08:00
peterlu14 47712e5ff1 [update] fix spike first timemark 2021-10-13 21:59:26 +08:00
Alan Lin 5e883ef672 Merge branch 'neulive_sunpower' of gitlab.com:bioproscientific/bioprovue into neulive_sunpower 2021-10-13 16:17:07 +08:00
Alan Lin a7591860cf update T2, T4 counter 2021-10-13 16:16:44 +08:00
Jordan Hsu 68510b3c42 [fix] import path error 2021-10-13 16:11:21 +08:00
TommyXin 1f102c3816 route task Gimer 2021-10-13 15:27:16 +08:00
Alan Lin 1b1ee5f58a update Giner STI.vue 2021-10-13 13:55:20 +08:00
Jordan Hsu a20f24c45f [fix] t range rounding and GM channel bug 2021-10-13 11:55:17 +08:00
peterlu14 e29e0caee6 Merge branch 'feature/spike_UI_enhance' into release/neulive_elite_1012 2021-10-12 17:38:41 +08:00
peterlu14 715f1ef1d0 [update] align controller_id 2021-10-12 17:30:40 +08:00
Jordan Hsu 537b668d1e click stop to click native stop 2021-10-12 16:03:12 +08:00
Jordan Hsu a658203c0a Protect recording channels button add parasend event 2021-10-12 15:50:12 +08:00
Alan Lin 63e00b91f6 update t3t4 counter formula 2021-10-12 15:08:52 +08:00
peterlu14 cc7e2adc81 Merge branch 'feature/add_sd_icon' into feature/spike_UI_enhance 2021-10-12 12:16:05 +08:00
peterlu14 98738bcb10 [update] remove export info 2021-10-12 12:09:02 +08:00
peterlu14 e34605c488 [update] spike update 2021-10-12 11:29:19 +08:00
TommyXin 887ebc0907 real time iir filter 2021-10-08 18:08:11 +08:00
Jordan Hsu 91e4acbcb0 [fix] real time chart mode 2021-10-08 17:11:57 +08:00
Jordan Hsu 4bb59d5c87 [add] elite replay circle dot 2021-10-08 16:54:23 +08:00
peterlu14 ed3d8a81ff Merge branch 'feature/neulive3.5-sti-amp' into feature/spike_UI_enhance 2021-10-08 10:14:55 +08:00
peterlu14 87c9cba03c [update] spike data mqtt add 2021-10-08 10:09:25 +08:00
Jordan Hsu 1a08d7ff28 [modify] 3.5 & gm t range 2021-10-08 10:08:18 +08:00
TommyXin 641b001b88 fix realtime lose data 2021-10-07 19:44:02 +08:00
Jordan Hsu aeb265b206 [fix] eis param bug 2021-10-07 18:39:54 +08:00
Jordan Hsu 932b8c8025 [fix] freq start and end bug 2021-10-07 17:42:10 +08:00
Alan Lin f040150ac3 updata repeat time formula 2021-10-07 17:02:45 +08:00
Alan Lin 956f84476f Merge branch 'neulive_sunpower' of gitlab.com:bioproscientific/bioprovue into neulive_sunpower 2021-10-07 16:46:25 +08:00
TommyXin c028d14ade repeat number & interval 2021-10-07 16:44:54 +08:00
Alan Lin 5108c7ffd9 Merge branch 'neulive_sunpower' of gitlab.com:bioproscientific/bioprovue into neulive_sunpower 2021-10-07 15:38:33 +08:00
TommyXin 33f78b0f83 save power mode bool to number 2021-10-07 15:36:36 +08:00
Alan Lin c12ded4834 Merge branch 'neulive_sunpower' of gitlab.com:bioproscientific/bioprovue into neulive_sunpower 2021-10-07 15:34:06 +08:00
TommyXin ad3553a68e fix save power mode 2021-10-07 15:33:50 +08:00
Alan Lin 6608ee9e86 Merge branch 'neulive_sunpower' of gitlab.com:bioproscientific/bioprovue into neulive_sunpower 2021-10-07 15:17:53 +08:00
Alan Lin 2388e82c5d T3, T4 counter use correct formula 2021-10-07 15:17:42 +08:00
TommyXin 72d87712b3 save power mode & protect channels 2021-10-07 15:15:42 +08:00
Alan Lin 2e1c3237f4 add new parameter 2021-10-07 14:08:10 +08:00
Jordan Hsu dec21dfee0 [modify] eis ui 2021-10-07 14:06:02 +08:00
Jordan Hsu 7bde032cf6 [fix] eis param typo 2021-10-07 13:49:21 +08:00
Jordan Hsu f83ad1efbf Merge branch 'feature/eis_replay' into release/elite_1007 2021-10-07 13:47:24 +08:00
TommyXin b04d9ff29e fix Trange_CH0 cannot refresh 2021-10-06 15:57:46 +08:00
TommyXin 86e3cee7ec add save power mode / protect channel 2021-10-06 15:03:46 +08:00
TommyXin 6a96620549 fix bug 2021-10-05 18:15:20 +08:00
TommyXin 1d5813c8fa window 2021-10-05 18:00:39 +08:00
TommyXin b717bf8906 electrode site 2021-10-05 16:42:17 +08:00
peterlu14 de8fbc9bbe [update] spike button eventproof 2021-10-05 15:33:33 +08:00
peterlu14 403be30139 Merge branch 'release/merge_SD_card_UI_0924_2' into feature/neulive3.5-sti-amp 2021-10-05 11:09:59 +08:00
Jordan Hsu c307735dc3 [done] eis replay 2021-10-04 18:39:01 +08:00
TommyXin f88e28616e sti pos 2021-10-04 18:00:22 +08:00
TommyXin a102567dea recording site 2021-10-04 17:51:23 +08:00
peterlu14 46017134d8 [update] spike optimize 2021-10-01 14:21:12 +08:00
peterlu14 e2298a6ccc [update] download excel bug fix 2021-09-27 20:21:01 +08:00
peterlu14 5a61e24495 [update] icon usb to sd card & remove ! 2021-09-24 19:37:38 +08:00
peterlu14 c4007b7930 Merge branch 'feature/SD_card_UI' into release/merge_SD_card_UI_0924 2021-09-24 12:52:21 +08:00
peterlu14 2fe1cd6e00 [update] add progress bar & controller sd_uploading check 2021-09-24 12:50:29 +08:00
peterlu14 4cc7b9e12f [update] mac address change & get sync to async 2021-09-22 14:33:06 +08:00
peterlu14 8b4f210f95 Merge commit '1af50d0bfc7ec75a6aaf1aa493b31b29fc20882a' into feature/SD_card_UI 2021-09-22 14:31:46 +08:00
peterlu14 1af50d0bfc [update] databatch 2021-09-17 13:26:36 +08:00
TommyXin 99b2b9dfce remove check param 2021-09-16 17:05:24 +08:00
TommyXin 1004c7075e [update] EIS unit ch4&8, window by elite lib ver. 2021-09-16 17:05:16 +08:00
peterlu14 8f4cbc0eeb Merge remote-tracking branch 'origin/feature/neulive_replay_wish' into release/neulive_elite_0913 2021-09-15 19:07:12 +08:00
TommyXin f741a2d018 fix x-axis unit 2021-09-15 19:06:46 +08:00
TommyXin 008897830d fix source X 2021-09-15 19:06:41 +08:00
TommyXin 5c297687f1 multi channel & unit 2021-09-15 19:06:32 +08:00
Jordan Hsu 213caf9796 [fix] replay window can't zoom bug 2021-09-15 17:10:21 +08:00
peterlu14 0fda2141dd [update] add record_in_sd option 2021-09-15 02:36:16 +08:00
peterlu14 3761493ec1 [update] remove sd card delete 2021-09-15 01:04:18 +08:00
peterlu14 87300e7a43 Merge remote-tracking branch 'origin/feature/SD_card_UI' into release/neulive_elite_0913 2021-09-15 00:59:47 +08:00
peterlu14 85103f5f12 [update] expand action width & change order 2021-09-15 00:56:58 +08:00
Jordan Hsu 1059a98c27 [add] sd button confirm modal before uploading 2021-09-14 15:54:57 +08:00
Jordan Hsu 8150afc624 Merge remote-tracking branch 'origin/feature/SD_card_UI' into feature/SD_card_UI 2021-09-14 14:13:30 +08:00
Jordan Hsu b0de357d89 [add] replay check if sd uploaded 2021-09-14 14:12:53 +08:00
peterlu14 44c1a41d99 Merge branch 'feature/SD_card_UI' into release/neulive_elite_0913 2021-09-14 14:00:28 +08:00
peterlu14 a6a9b64da2 [update] getSDuuidList 2021-09-14 13:05:05 +08:00
Jordan Hsu a4ccee71a8 [fix] sd card UI bug 2021-09-14 11:47:45 +08:00
Jordan Hsu 2cbdab696c Merge branch 'feature/neulive_replay_wish' into release/neulive_elite_0913 2021-09-13 18:48:35 +08:00
Jordan Hsu 3fe5e4af0c adjust UI 2021-09-13 18:42:51 +08:00
Jordan Hsu 577cf09951 [add] replay custom window start and size 2021-09-13 17:48:11 +08:00
peterlu14 0c18ce1829 Merge remote-tracking branch 'origin/feature/elite_replay_down_scale' into release/neulive_0913 2021-09-13 14:52:03 +08:00
peterlu14 7c336d4bd3 Merge remote-tracking branch 'origin/feature/neulive_replay_wish' into release/neulive_0913 2021-09-13 14:51:31 +08:00
Jordan Hsu 95955e23e0 [add] lock button 2021-09-13 14:07:44 +08:00
peterlu14 d387f83f87 [update] setting pic update & display fix 2021-09-10 20:03:13 +08:00
Jordan Hsu 0e1695bb4e [modify] elite replay will detect resample rate and auto down rate 2021-09-10 20:02:53 +08:00
peterlu14 fba42100d8 Merge branch 'feature/setting_update' into devlop/neulive_merge_sd_card 2021-09-10 12:39:56 +08:00
peterlu14 acd1b300d7 Merge branch 'feature/SD_card_UI' into devlop/neulive_merge_sd_card 2021-09-10 12:39:26 +08:00
Jordan Hsu 3f4d5de662 [modify] nprogress color to red 2021-09-10 12:09:49 +08:00
Jordan Hsu 56f4f0274c [remove] elite sd card button 2021-09-10 11:51:31 +08:00
Jordan Hsu e6aeb9203d [fix] sd card UI won't re-render after changing page bug 2021-09-09 21:02:41 +08:00
Jordan Hsu da7581de63 [update] shell script readme 2021-09-09 18:29:14 +08:00
Jordan Hsu 6d9294775d clean up sd card code 2021-09-09 18:08:14 +08:00
Jordan Hsu 3a7d685d31 [fix] monophasic checkpoint bug 2021-09-09 17:50:51 +08:00
peterlu14 16df8514ba [update] setting pic update & display fix 2021-09-09 13:39:21 +08:00
Jordan Hsu a5085cbbe6 sd card UI 2021-09-08 21:29:55 +08:00
peterlu14 1fbf8c0022 [update] download replay data 2021-09-08 20:07:28 +08:00
peterlu14 0f5cbaf778 [update] sd_data download & replay 2021-09-08 16:26:49 +08:00
Jordan Hsu 2b5b42dd61 tmp for api debug 2021-09-08 13:52:59 +08:00
Jordan Hsu 4221a35f00 Merge remote-tracking branch 'origin/develop/neulive_0901' into feature/SD_card_UI 2021-09-08 11:06:41 +08:00
peterlu14 c3fc287b0f [update] add sdcard api & get sd data 2021-09-08 11:02:44 +08:00
Jordan Hsu 047a72e9c6 [add] SD card init func 2021-09-07 16:56:29 +08:00
Jordan Hsu b79a4a2091 [init] SD card UI 2021-09-07 15:44:44 +08:00
Jordan Hsu 4a8be436ce [fix] monophasic bug 2021-09-06 15:53:52 +08:00
peterlu14 102d39b4b5 [update] add sample rate 2021-09-06 14:11:14 +08:00
peterlu14 e8479f80ca [update] support past replay & data:axis 75:1 2021-09-06 13:18:17 +08:00
peterlu14 3aa5db1455 [update] sti channel support 16 channel 2021-09-06 11:44:47 +08:00
peterlu14 2e33ef7abb [update] fix download bug & add 0 ~ 11 device 2021-09-06 11:41:28 +08:00
Jordan Hsu 78ec2fd492 [modify] spike UI with lttb algo. 2021-09-03 21:09:09 +08:00
peterlu14 d12084324a [update] spike ui save 2021-09-03 19:14:18 +08:00
Jordan Hsu b3580bfa76 [modify] neulive replay unit uV and clear redundant code 2021-09-03 16:15:18 +08:00
peterlu14 5b57e40575 [update] remove 3.5 rf & led 2021-09-02 20:26:38 +08:00
peterlu14 14679182df [update] move mem RF & RF & led to setting 2021-09-02 20:20:44 +08:00
peterlu14 db0111d1db [update] amplitude rebuild & fix STI_T4 2021-09-01 10:57:35 +08:00
peterlu14 21cb382a1f [update] replace ADC_CLOCK to SAMPLE_RATE & 3.5 RECORDING_CH 2021-09-01 10:55:58 +08:00
peterlu14 1d8ef94ec6 Merge remote-tracking branch 'origin/feature/spike_UI' into release/0824_test 2021-08-27 19:08:52 +08:00
peterlu14 e49b6fb791 [update] export fix & subplot message & download 2021-08-27 19:07:57 +08:00
peterlu14 52e3d70dc8 [update] neulive ui switch & STI cycle amplitude T1-T4 fix 2021-08-26 16:59:39 +08:00
Roy a33bbd0581 fix current input value 2021-08-26 11:58:43 +08:00
Roy cd97260230 fix volt input value 2021-08-26 11:38:38 +08:00
Roy b9242764ab Merge branch 'e_0824_func_gen_bug' into release/0824_test 2021-08-26 10:46:36 +08:00
Jordan Hsu e8d75b9877 [init] spike ui 2021-08-26 02:03:30 +08:00
TommyXin 433e567e74 neulive 3.5 UI 2021-08-25 20:44:28 +08:00
TommyXin fbbebb3423 fix grid uV 2021-08-25 16:33:40 +08:00
Roy 6f2aab5276 OCP mode add current range button 2021-08-25 16:12:44 +08:00
TommyXin d82b1cb735 grid support input 'auto' 2021-08-25 01:17:07 +08:00
Roy 8aa87d587f update elite1.5 button 2021-08-24 17:40:32 +08:00
TommyXin 38503e198e EIS volt range 2021-08-24 04:35:02 +08:00
Jordan Hsu 8b78cdcdbd Merge remote-tracking branch 'origin/release/0812_test' into feature/spike_UI 2021-08-20 16:08:37 +08:00
peterlu14 94feff568a Merge remote-tracking branch 'origin/release/0812_test' into release/0812_test 2021-08-20 15:49:07 +08:00
peterlu14 06336bbd61 [update] add api spikeDetect 2021-08-20 15:48:18 +08:00
TommyXin 33b041a31b Merge remote-tracking branch 'origin/release/0812_test' into release/0812_test 2021-08-19 10:40:27 +08:00
TommyXin 14dda58fac neulive uV 2021-08-19 10:40:12 +08:00
Jordan Hsu f9bf41980b [modify] neulive replay function name 2021-08-18 14:55:45 +08:00
Jordan Hsu 6b20902c74 [fix] neulive stimulation phasic bug 2021-08-18 14:16:45 +08:00
peterlu14 ced0362910 Merge remote-tracking branch 'origin/release/0812_test' into release/0812_test 2021-08-17 16:59:13 +08:00
peterlu14 467016140f [update] real-time axis setting & edf format 2021-08-17 16:58:40 +08:00
Jordan Hsu f6e642cd5f Merge remote-tracking branch 'origin/release/0812_test' into release/0812_test 2021-08-13 16:00:48 +08:00
Jordan Hsu 7c5aed4dbc [fix] sti t range check bug 2021-08-13 15:54:55 +08:00
peterlu14 c069354415 [fix] EIS download 2021-08-13 13:14:10 +08:00
Jordan Hsu 738b58fded [modify] speed up neulive replay 2021-08-12 18:59:19 +08:00
Jordan Hsu f7c89585ac [modify] elite chart sampling method to lttb algo. and fix overview scale bug 2021-08-12 18:06:34 +08:00
peterlu14 36ed59816d Merge branch '0809_release_version' into release/0812_test 2021-08-12 17:01:34 +08:00
Jordan Hsu 516d20333c Merge remote-tracking branch 'origin/0809_release_version' into 0809_release_version 2021-08-12 17:00:56 +08:00
Jordan Hsu 74b8472c1d [modify] replay resampling method 2021-08-12 17:00:34 +08:00
peterlu14 ecb9e02610 [update] about to setting 2021-08-12 16:54:44 +08:00
peterlu14 74dea74476 Merge remote-tracking branch 'origin/0809_release_version' into release/0812_test 2021-08-12 14:40:47 +08:00
TommyXin 588a791bc1 Merge remote-tracking branch 'origin/0809_release_version' into 0809_release_version 2021-08-12 14:20:48 +08:00
TommyXin cd3e6f08f5 time 500ms&neulive uV&device panel height 2021-08-12 14:20:06 +08:00
peterlu14 1370e5659e [update] axis data divide 100 2021-08-12 13:42:48 +08:00
peterlu14 e18fa916b5 Merge branch 'download_rebuild' into 0809_release_version 2021-08-12 13:11:04 +08:00
peterlu14 b5a920338a [update] remove old download routes 2021-08-12 13:10:19 +08:00
peterlu14 6c4660966c [update] adjust tooltip formatter 2021-08-12 12:57:27 +08:00
peterlu14 0db13d3404 [update] remove old download pages 2021-08-12 12:55:45 +08:00
TommyXin 8b96a7308c fix stop button 2021-08-12 11:06:05 +08:00
peterlu14 d7e69e250e Merge remote-tracking branch 'origin/0809_release_version' into download_rebuild 2021-08-12 10:30:25 +08:00
TommyXin 14217aa1ea fix EIS freq max 2021-08-12 09:50:50 +08:00
peterlu14 506080c518 [update] replay download url fix 2021-08-12 04:23:53 +08:00
peterlu14 794b71cc9f [update] rebuild download structure 2021-08-12 04:16:40 +08:00
Jordan Hsu 7e355448e0 [fix] replay change scale bug 2021-08-10 19:53:37 +08:00
peterlu14 279a1056f4 Merge branch '0809_release_version' into feature/edf_format 2021-08-10 13:31:09 +08:00
peterlu14 35c436ee67 [update] data timestamp align 2021-08-05 18:21:39 +08:00
341 changed files with 27920 additions and 8816 deletions
+54
View File
@@ -1168,6 +1168,16 @@
"minimist": "^1.2.0"
}
},
"@fontsource/source-code-pro": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-4.5.0.tgz",
"integrity": "sha512-IKB+oQfwjj9DzEM2sc5rO70BZV7+ksmZbB+1KzyiZp2VroPw7Lm1ovXBU2JYWIWXmNY+gDudvyZXZHE+6cG47Q=="
},
"@fontsource/source-sans-pro": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-4.5.1.tgz",
"integrity": "sha512-JYEldSfnqxoDVUL6QgqT8Fiyjh0p7WlKcqKNevzDylpWabdejVRyFzPhTk2ZceKZd02KEOaL+K462U6u0/PQiA=="
},
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@@ -7536,6 +7546,11 @@
"schema-utils": "^2.5.0"
}
},
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -8580,6 +8595,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@@ -10360,6 +10380,14 @@
"type-check": "~0.3.2"
}
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
"requires": {
"immediate": "~3.0.5"
}
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
@@ -10924,6 +10952,14 @@
}
}
},
"localforage": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
"requires": {
"lie": "3.1.1"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@@ -12106,6 +12142,11 @@
"set-blocking": "~2.0.0"
}
},
"nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E="
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@@ -17196,6 +17237,14 @@
"mqtt": "^2.18.3"
}
},
"vue-multiple-progress": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/vue-multiple-progress/-/vue-multiple-progress-1.6.0.tgz",
"integrity": "sha512-TPk8B/vh9YrzuNQN9PcnMsMiNTaItsVia75+j23V7/VJuPCsRz+Omkwb/83p5CzQ97QT9ApEvdG7hupG+xb3aA==",
"requires": {
"vue": "^2.3.3"
}
},
"vue-resize": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.4.5.tgz",
@@ -17250,6 +17299,11 @@
"resolved": "https://registry.npmjs.org/vue-toasted/-/vue-toasted-1.1.28.tgz",
"integrity": "sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw=="
},
"vue-toastification": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-1.7.14.tgz",
"integrity": "sha512-khZR8t3NWZ/JJ2MZxXLbesHrRJ8AKa75PY5Zq8yMifF9x8lHq8ljYkC0d2PD9yahooygQB5tcFyRDkbbIPx8hw=="
},
"vue-worker": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/vue-worker/-/vue-worker-1.2.1.tgz",
+7
View File
@@ -13,6 +13,8 @@
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"@fontsource/source-code-pro": "^4.5.0",
"@fontsource/source-sans-pro": "^4.5.1",
"@types/jest": "^24.0.11",
"amcharts3": "^3.21.14",
"ammap3": "^3.21.14",
@@ -27,14 +29,17 @@
"element-resize-detector": "^1.1.15",
"epic-spinners": "^1.1.0",
"extend": "3.0.2",
"file-saver": "^2.0.5",
"flag-icon-css": "^3.4.5",
"font-awesome": "^4.7.0",
"google-maps": "^3.3.0",
"ionicons": "^4.6.3",
"leaflet-map": "^0.2.1",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"medium-editor": "^5.23.3",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"popper.js": "^1.16.0",
"regenerator-runtime": "^0.13.3",
"register-service-worker": "^1.6.2",
@@ -52,8 +57,10 @@
"vue-epic-bus": "^0.1.2",
"vue-gtm": "^2.2.0",
"vue-mqtt": "^2.0.3",
"vue-multiple-progress": "^1.6.0",
"vue-router": "^3.1.3",
"vue-slicksort": "^1.2.0",
"vue-toastification": "^1.7.14",
"vue-worker": "^1.2.1",
"vue-yandex-maps": "^0.9.0",
"vuedraggable": "^2.24.3",
Binary file not shown.
-3
View File
@@ -3,9 +3,6 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>BioPro Scientific</title>
+2 -1
View File
@@ -8,7 +8,8 @@ burn_to_raspberry_util.sh : 給burn_to_raspberry.sh用的,裡面的指令為
# Usage
burn_to_raspberry.sh
```shell
要先安裝sshpass: https://www.codeleading.com/article/75275615628/
sh ./burn_to_raspberry.sh <HOST> <PASSWORD>
e.g. sh ./burn_to_raspberry.sh pi@192.168.3.53 raspberry
e.g. sh ./burn_to_raspberry.sh pi@192.168.2.1 raspberry
```
+16 -2
View File
@@ -18,6 +18,9 @@ import 'echarts/lib/chart/line'
import 'echarts/lib/component/dataZoom'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/legend/ScrollableLegendModel.js'
import 'echarts/lib/component/legend/ScrollableLegendView.js'
import 'echarts/lib/component/legend/scrollableLegendAction.js'
import 'echarts/lib/component/axisPointer'
import 'echarts/lib/component/grid'
import 'echarts/lib/component/markArea'
@@ -28,10 +31,19 @@ import VueWorker from 'vue-worker'
import axios from 'axios'
import VueAxios from 'vue-axios'
import 'nprogress/nprogress.css'
import Toast from 'vue-toastification'
import Progress from 'vue-multiple-progress'
import 'vue-toastification/dist/index.css'
import localforage from 'localforage'
import '../metrics'
import '../registerServiceWorker'
localforage.config({ name: 'bioprovue' })
if (process.env.VUE_APP_BUILD_VERSION) {
// eslint-disable-next-line
const message = `%c${'Build_information:'}\n %c${'Version'}: %c${VERSION},\n %c${'Timestamp'}: %c${TIMESTAMP},\n %c${'Commit'}: %c${COMMIT}`
@@ -48,11 +60,13 @@ Vue.use(VueClipboard)
Vue.component('chart', ECharts)
Vue.use(VueWorker)
Vue.use(VueAxios, axios)
Vue.use(Toast)
Vue.use(Progress)
// Vue.use(VueMqtt, 'ws://52.194.17.114:8083')
// Vue.use(VueMqtt, 'ws://' + location.href.split('/')[2] + ':8083')
Vue.use(VueMqtt, 'ws://' + location.href.split('/')[2] + ':8083')
// Vue.use(VueMqtt, 'ws://' + '192.168.151.125' + ':8083')
Vue.use(VueMqtt, 'ws://' + '192.168.151.125' + ':8083')
// Vue.use(VueMqtt, 'ws://' + '192.168.3.211' + ':8083')
Vue.prototype.GLOBAL = global_
+1
View File
@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sd-card" class="svg-inline--fa fa-sd-card fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#6c7fee" d="M320 0H128L0 128v320c0 35.3 28.7 64 64 64h256c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64zM160 160h-48V64h48v96zm80 0h-48V64h48v96zm80 0h-48V64h48v96z"></path></svg>

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

@@ -44,15 +44,6 @@
contact@bioproweb.com
</a>
</span>
<!-- <va-button
href="https://github.com/epicmaxco/vuestic-admin"
color="#000000"
class="app-navbar__button"
icon="fa fa-github"
target="_blank"
>
{{$t('navbar.repository')}}
</va-button> -->
</div>
<app-navbar-actions
class="app-navbar__actions md5 lg4"
@@ -1,13 +1,15 @@
<template>
<div class="app-navbar-actions">
<!-- <color-dropdown class="app-navbar-actions__item"/> -->
<!-- <message-dropdown class="app-navbar-actions__item"/>
<notification-dropdown class="app-navbar-actions__item"/> -->
<!-- <settings-dropdown
<!-- <color-dropdown class="app-navbar-actions__item"/>
<message-dropdown class="app-navbar-actions__item"/>
<notification-dropdown class="app-navbar-actions__item"/>
<settings-dropdown
:is-top-bar.sync="isTopBarProxy"
class="app-navbar-actions__item"
/> -->
<!-- <language-dropdown class="app-navbar-actions__item"/> -->
/>
<language-dropdown class="app-navbar-actions__item"/> -->
<license-check-button :ref="'license_check_button_ref'" @updateLicense="updateLicense"/>
<license-update-modal :title="'Update'" :ref="'license_update_modal_ref'" @refresh="refresh"/>
<profile-dropdown class="app-navbar-actions__item app-navbar-actions__item--profile">
<span>{{userName}}</span>
</profile-dropdown>
@@ -16,11 +18,13 @@
<script>
// import LanguageDropdown from './dropdowns/LanguageDropdown'
import ProfileDropdown from './dropdowns/ProfileDropdown'
// import NotificationDropdown from './dropdowns/NotificationDropdown'
// import MessageDropdown from './dropdowns/MessageDropdown'
// import ColorDropdown from './dropdowns/ColorDropdown'
// import SettingsDropdown from './dropdowns/SettingsDropdown'
import ProfileDropdown from './dropdowns/ProfileDropdown'
import LicenseUpdateModal from './modals/LicenseUpdateModal'
import LicenseCheckButton from './buttons/LicenseCheckButton'
import { ColorThemeMixin } from '../../../../services/vuestic-ui'
export default {
@@ -33,6 +37,8 @@ export default {
// MessageDropdown,
// NotificationDropdown,
// LanguageDropdown,
LicenseCheckButton,
LicenseUpdateModal,
ProfileDropdown,
},
props: {
@@ -55,6 +61,16 @@ export default {
},
},
},
methods: {
updateLicense () {
this.$refs.license_update_modal_ref.show()
this.$refs.license_update_modal_ref.licenseUpdate()
},
refresh () {
this.$refs.license_check_button_ref.refresh()
this.$refs.license_update_modal_ref.hide()
},
},
}
</script>
@@ -0,0 +1,96 @@
<template>
<div v-if="!licenseCheck">
<va-button color="warning" class="mb-2" @click="licenseUpdateModalShow" small>
Update
</va-button>
</div>
</template>
<script>
import api from '@/data/api/index'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'license-check-button',
components: {
},
computed: {
...mapFields('', [
'licenseCheck',
]),
},
data () {
return {
popoverShow: true,
licenseCheckObj: {
pass: true,
outdated: false,
fail: false,
},
authText: {
outdated: 'New version released. Update by pressing the button above.',
fail: 'System file is missing, contact us by following as: contact@bioproweb.com.',
},
toastOptions: {
theme: 'none',
position: 'bottom-center',
className: 'vuestic-toast',
iconPack: 'fontawesome',
duration: 2500,
},
}
},
methods: {
licenseUpdateModalShow () {
this.$emit('updateLicense')
},
showNotify (licenseCheckResponse) {
this.$toast(this.authText[licenseCheckResponse], {
closeButtonClassName: 'my-button-class',
containerClassName: 'my-container-class',
toastClassName: 'my-custom-toast-class',
timeout: false,
icon: 'fa fa-exclamation',
})
},
closeNotify () {
this.$toast.clear()
},
async refresh () {
this.closeNotify()
const licenseCheckResponse = await api.auth.licenseCheck()
this.licenseCheck = this.licenseCheckObj[licenseCheckResponse.data]
if (this.licenseCheck === false) {
this.showNotify(licenseCheckResponse.data)
}
},
},
async mounted () {
await this.refresh()
},
destroyed () {
},
}
</script>
<style>
.Vue-Toastification__toast--default.my-custom-toast-class {
background-color: rgba(255, 214, 82, 1);
color: black;
}
.Vue-Toastification__container.top-right {
top: 3rem !important;
}
.my-button-class {
opacity: 1 !important;
color: black !important;
}
.custom-toast {
background-color: rgba(255, 214, 82, 1) !important;
transform: translateX(50px) translateY(-75px) !important;
color: black !important;
}
</style>
@@ -0,0 +1,74 @@
<template>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showModal"
:title="title"
noOutsideDismiss
hideDefaultActions
>
<div v-if="loading" class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
<div v-else class="row flex sm12 xl12 md12 xs12">
{{ modalMessage }}
<div class="row flex-center sm12 xl12 md12 xs12 mt-2">
<va-button small @click="hide">close</va-button>
</div>
</div>
</va-modal>
</template>
<script>
import api from '@/data/api/index'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'license-check-modal',
components: {
FulfillingBouncingCircleSpinner,
},
props: {
title: {
type: String,
default: '',
},
},
data () {
return {
showModal: false,
hideDefaultActions: false,
loading: true,
modalMessage: '',
}
},
methods: {
show () {
this.showModal = true
},
hide () {
this.showModal = false
},
async licenseUpdate () {
this.hideDefaultActions = true
this.loading = true
const updateResponse = await api.auth.licenseUpdate()
console.log('updateResponse', updateResponse)
setTimeout(() => {
if (updateResponse.status === 200) {
this.loading = false
this.$emit('refresh')
} else if (updateResponse.status === 202 || updateResponse.status === 203) {
this.loading = false
this.modalMessage = 'An unknown error occurred while updating, please contact us by following as: contact@bioproweb.com'
}
}, 5000)
},
},
}
</script>
@@ -40,6 +40,13 @@ export const navigationRoutes = {
// },
// ],
},
// {
// name: 'task-content-g-m',
// displayName: 'Gimer',
// meta: {
// iconClass: 'vuestic-iconset vuestic-iconset-statistics',
// },
// },
{
name: 'file',
displayName: 'File',
@@ -47,6 +54,20 @@ export const navigationRoutes = {
iconClass: 'vuestic-iconset vuestic-iconset-files',
},
},
{
name: 'trigger-file',
displayName: 'TriggerFile',
meta: {
iconClass: 'vuestic-iconset vuestic-iconset-tables',
},
},
{
name: 'sdcard-reader',
displayName: 'SDcardReader',
meta: {
iconClass: 'vuestic-iconset vuestic-iconset-ui-elements',
},
},
{
name: 'settings',
displayName: 'Settings',
@@ -11,12 +11,50 @@
:to="to"
:target="target"
>
<va-icon
v-if="icon"
class="app-sidebar-link__item-icon"
:style="computedIconStyles"
:name="icon"
/>
<template v-if="title == 'Gimer'">
<img
v-if="this.isHovered || this.isActive"
class="app-sidebar-link__item-icon"
src="@/assets/img/gimer_white.png"
>
<img
v-else
class="app-sidebar-link__item-icon"
src="@/assets/img/gimer_gray.png"
>
</template>
<template v-else-if="title == 'TriggerFile'">
<img
v-if="this.isHovered || this.isActive"
class="app-sidebar-link__item-icon"
src="@/assets/img/trigger_white.png"
>
<img
v-else
class="app-sidebar-link__item-icon"
src="@/assets/img/trigger_gray.png"
>
</template>
<template v-else-if="title == 'SDcardReader'">
<img
v-if="this.isHovered || this.isActive"
class="app-sidebar-link__item-icon"
src="@/assets/img/sdcard_reader_white.png"
>
<img
v-else
class="app-sidebar-link__item-icon"
src="@/assets/img/sdcard_reader_gray.png"
>
</template>
<template v-else>
<va-icon
v-if="icon"
class="app-sidebar-link__item-icon"
:style="computedIconStyles"
:name="icon"
/>
</template>
<div class="app-sidebar-link__item-title" v-if="title">
<slot>
{{title}}
@@ -140,6 +178,21 @@ export default {
color: 'white',
}
},
computedGimerIcon () {
if (this.contextConfig.invertedColor) {
if (this.isHovered || this.isActive) {
return 'gimer_white.png'
}
return 'gimer_gray.png'
}
if (this.isHovered || this.isActive) {
return 'gimer_white.png'
}
return 'gimer_white.png'
},
},
methods: {
updateHoverState (isHovered) {
@@ -9,7 +9,7 @@
<span slot="header">Control Panel</span>
<div slot="body">
<div>
<top-control-panel :ref="'top_control_panel'" @refreshChart="setOption" @changeAxis="changeAxis"/>
<top-control-panel :ref="'top_control_panel'" @refreshChart="setOption" @changeAxis="changeAxis" @changeOverviewDataZoom="changeOverviewDataZoom"/>
</div>
</div>
</va-collapse>
@@ -22,7 +22,7 @@
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<elite-replay-overview-chart v-show="showOverviewChart" :ref="'chart_overview'" class="mb-1" :cardheight="'7vh'" :data="chartOverviewData" @dataZoomEvent="dataZoomEvent($event)"/>
<elite-replay-chart :ref="'chart_detail'" @dblclick.native="distribute()" :cardheight="'88vh'" :chartheight="detailchartheight" :data="chartDetailData" />
<elite-replay-chart :ref="'chart_detail'" :cardheight="'88vh'" :chartheight="detailchartheight" :data="chartDetailData" />
<change-channel-value-min-max :ref="'set_min_max'"/>
</div>
</div>
@@ -51,14 +51,27 @@ export default {
ChangeChannelValueMinMax,
},
computed: {
displayType () {
return {
x: this.axisXLabelScale.description === 'Log' ? 'log' : 'value',
y: this.axisYLabelScale.description === 'Log' ? 'log' : 'value',
}
},
...mapFields('replay', [
'metaList',
'chartData',
'chartOverviewData',
'chartDetailData',
'currentScale',
'startTime',
'endTime',
'windowLength',
'axisXDataType',
'axisYDataType',
'axisXLabelScale',
'axisYLabelScale',
'absToggleStatus',
'startTimeCurrent',
'endTimeCurrent',
'sampleMethod',
@@ -90,8 +103,6 @@ export default {
detailchartheight: '88vh',
dataInfo: {},
maxTime: 0,
startTime: 0,
endTime: 0,
zoomTime: 0,
initDone: false,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl9', 'pa-0'],
@@ -156,7 +167,7 @@ export default {
})
let _gridNumber = 0
const register = this.metaList.map(meta => {
const setting = this.GLOBAL.getDefaultChartTypeByMode(meta.parameter_set.MODE)
const setting = this.GLOBAL.getDefaultChartTypeByMode(meta.parameter_set.MODE, meta.device.library_name)
const xType = setting[0] // Elite or Time
const yType = setting[2] // Elite or Time
const xValue = setting[1] // 1: I, 2: V, 3: R
@@ -220,21 +231,75 @@ export default {
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.startTime = this.maxTime * dataZoom.start / 100
this.endTime = this.maxTime * dataZoom.end / 100
this.startTime = parseInt(this.maxTime * dataZoom.start / 100)
this.endTime = parseInt(this.maxTime * dataZoom.end / 100)
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = dataZoom.start
this.chartOverviewData.dataZoom[0].end = dataZoom.end
// console.log(this.startTime, this.endTime)
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.startTime = this.maxTime * e.start / 100
this.endTime = this.maxTime * e.end / 100
this.startTime = parseInt(this.maxTime * e.start / 100)
this.endTime = parseInt(this.maxTime * e.end / 100)
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = e.start
this.chartOverviewData.dataZoom[0].end = e.end
// console.log(this.startTime, this.endTime)
}
}
this.zoomTime = 0
},
changeOverviewDataZoom () {
this.chartOverviewData.dataZoom[0].start = this.startTime * 100 / this.maxTime
this.chartOverviewData.dataZoom[0].end = this.endTime * 100 / this.maxTime
this.$refs.chart_overview.mergeOptions(this.chartOverviewData)
},
async computeResampleScale (meta, duration) {
const sampleRate = meta.parameter_set.SAMPLE_RATE / 10
const dataPointNum = (parseInt(duration) / 1e6) * sampleRate // us -> s: /1e6
let scale
if (dataPointNum > 2e6) { // 2000 (points) * 1000 (resample) = 2e6
scale = '1000'
} else if (dataPointNum > 2e5) { // 2000 (points) * 100 (resample) = 2e5
scale = '100'
} else if (dataPointNum > 2e4) { // 2000 (points) * 10 (resample) = 2e4
scale = '10'
} else {
scale = '1'
}
let dataExist = false
while (dataExist === false) {
if (scale === '1') {
break
} else {
let status
for (const ch in Object.keys(meta.mini_data)) {
let earlyStopFlag = false
const dataIDArray = meta.mini_data[ch][scale]
for (let i = 0; i < dataIDArray.length; i++) {
const dataID = dataIDArray[i]
const result = await download.getMiniExistOrNotByID(dataID, ch)
status = result.data
if (status === false) {
earlyStopFlag = true
scale = String(parseInt(scale) / 10)
break
}
}
if (earlyStopFlag === true) {
break
}
}
if (status === true) {
dataExist = true // break
}
}
}
return scale
},
drawOverviewData: async function () {
this.showChartLoading('overview')
for (const meta in this.dataInfo) {
@@ -244,23 +309,9 @@ export default {
metaInfo = _meta
}
})
let scale = '100'
let scale = '1'
if (this.axisXDataType.description === 'Time') {
const sampleRate = metaInfo.parameter_set.SAMPLE_RATE / 10
scale = (((parseInt(metaInfo.time_duration) / 100000) * sampleRate) / 4000) * 0.1
console.log(scale)
// console.log(metaInfo.time_duration)
if (scale > 500) {
scale = '1000'
} else if (scale > 200) {
scale = '100'
} else if (scale > 30) {
scale = '10'
} else {
scale = '1'
}
} else {
scale = '1' // [TODO] select by user
scale = await this.computeResampleScale(metaInfo, metaInfo.time_duration)
}
console.log('overview scale:', scale)
// download data
@@ -268,13 +319,6 @@ export default {
for (const ch in _this.dataInfo[meta]) {
const dataList = _this.dataInfo[meta][ch][scale]
for (let i = 0; i < dataList.length; i++) {
// elite的sample rate不像neulive這麼高,因此試試scale = 100 or 1000時不要再sampling一次了,不然有些形狀(e.g. peak)會不一樣
if (scale === '100' || scale === '1000') {
_this.updateChartSamplingChart({ chartID: _this.chartOverviewData.chartID, seriesIndex: ch, method: null })
} else {
_this.updateChartSamplingChart({ chartID: _this.chartOverviewData.chartID, seriesIndex: ch, method: function (frame) { return frame[0] } })
}
if (dataList[i].data.length === 0) {
_this.intoDataRec({
index: i,
@@ -291,6 +335,7 @@ export default {
// push downloaded data to chart series
let seriesIndex = 0
for (const ch in this.dataInfo[meta]) {
this.chartOverviewData.series[seriesIndex].data.length = 0
const dataList = this.dataInfo[meta][ch][scale]
for (let i = 0; i < dataList.length; i++) {
this.chartOverviewData.series[seriesIndex].data.push(...dataList[i].data)
@@ -312,29 +357,21 @@ export default {
}
})
let scale = '100'
let scale = '1'
if (this.axisXDataType.description === 'Time') {
const sampleRate = metaInfo.parameter_set.SAMPLE_RATE / 10
scale = (((timeDur / 100000) * sampleRate) / 4000) * 0.1
console.log(scale)
// console.log(timeDur)
if (scale > 500) {
scale = '1000'
} else if (scale > 200) {
scale = '100'
} else if (scale > 30) {
scale = '10'
} else {
scale = '1'
}
} else {
scale = '1' // [TODO] select by user
scale = await this.computeResampleScale(metaInfo, timeDur)
}
// elite的sample rate不像neulive這麼高,因此試試scale = 100 or 1000時不要再sampling一次了,不然有些形狀(e.g. peak)會不一樣
if (scale === '100' || scale === '1000') {
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: _gridNumber, method: null })
} else {
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: _gridNumber, method: function (frame) { return frame[0] } })
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: _gridNumber, method: 'lttb' })
}
if (metaInfo.device.library_name === 'EliteEIS' && metaInfo.parameter_set.MODE === 0) {
this.chartDetailData.series[this.chartDetailData.series.length - 1].showSymbol = true
this.chartDetailData.series[this.chartDetailData.series.length - 1].showAllSymbol = true
this.chartDetailData.series[this.chartDetailData.series.length - 1].symbolSize = 6
}
this.currentScale = scale
@@ -347,10 +384,10 @@ export default {
const channelY = parseInt(this.axisYDataType.id)
const dataListY = this.dataInfo[meta][channelY][scale]
const isThisModeHasCycle = this.GLOBAL.hasCycleMode(metaInfo.parameter_set.MODE)
let startTime
let endTime
if (isThisModeHasCycle === true && this.axisXDataType.description !== 'Time') { // if有cycle的mode且目前x軸不為時間,抓出第一圈所在的時間段,初始化時先畫第一圈就好,加快初始時間
const isThisModeHasCycle = this.GLOBAL.hasCycleMode(metaInfo.parameter_set.MODE, metaInfo.device.library_name)
let _startTime
let _endTime
if (isThisModeHasCycle === true) {
const cycleDataID = 3 // recorded cycle 所在的channel index
const dataCycleList = this.dataInfo[meta][cycleDataID][this.currentScale]
const cycleDataList = []
@@ -363,21 +400,21 @@ export default {
}
}
const cycleDict = this.getCycleDictRec()(cycleDataList)
startTime = cycleDict[1][0] // [1]: 第一圈
endTime = cycleDict[1][1] // [1]: 第一圈
_startTime = cycleDict[1][0] // [1]: 第一圈
_endTime = cycleDict[1][1] // [1]: 第一圈
} else {
startTime = this.startTimeCurrent
endTime = this.endTimeCurrent
_startTime = this.startTimeCurrent
_endTime = this.endTimeCurrent
}
let startIndex = 0
let endIndex = 0
// get the subfile index
for (let i = 0; i < dataListY.length; i++) {
if (startTime >= parseInt(dataListY[i].start_time) && startTime <= parseInt(dataListY[i].end_time)) {
if (_startTime >= parseInt(dataListY[i].start_time) && _startTime <= parseInt(dataListY[i].end_time)) {
startIndex = i
}
if (endTime >= parseInt(dataListY[i].start_time) && endTime <= parseInt(dataListY[i].end_time)) {
if (_endTime >= parseInt(dataListY[i].start_time) && _endTime <= parseInt(dataListY[i].end_time)) {
endIndex = i
}
}
@@ -390,20 +427,26 @@ export default {
}
for (let j = 0, len = data.length; j < len; j++) {
// check which channel and push processed data (i.e. merge two channel data, now data is (time, value) format)
if (parseInt(data[j][0]) >= startTime && parseInt(data[j][0]) <= endTime) {
const processedData = JSON.parse(JSON.stringify(data[j]))
if (parseInt(data[j][0]) >= _startTime && parseInt(data[j][0]) <= _endTime) {
const processedData = data[j]
if (dataX !== undefined) {
processedData[0] = dataX[j][1]
}
// console.log(data)
if (this.displayType.x === 'log' || this.absToggleStatus.x === true) {
processedData[0] = Math.abs(processedData[0])
}
if (this.displayType.y === 'log' || this.absToggleStatus.y === true) {
processedData[1] = Math.abs(processedData[1])
}
this.chartDetailData.series[_gridNumber].data.push(processedData)
}
}
}
if (this.axisXDataType.description === 'Time') {
this.chartDetailData.xAxis[_gridNumber].min = this.startTimeCurrent
this.chartDetailData.xAxis[_gridNumber].max = this.endTimeCurrent
this.chartDetailData.xAxis[_gridNumber].min = _startTime
this.chartDetailData.xAxis[_gridNumber].max = _endTime
} else {
this.chartDetailData.xAxis[_gridNumber].min = 'dataMin'
this.chartDetailData.xAxis[_gridNumber].max = 'dataMax'
@@ -422,6 +465,7 @@ export default {
this.turnOffAxisEventHandler('click', this.showMinMaxPanel, { axis: 'x' })
this.turnOnAxisEventHandler('click', 'yAxis.axisLabel', this.showMinMaxPanel, { axis: 'y' })
}
this.hideChartLoading()
},
checkDataZoom: function () {
@@ -431,18 +475,22 @@ export default {
this.startTimeCurrent = this.startTime
this.endTimeCurrent = this.endTime
this.drawDetailData()
this.$refs.top_control_panel.resetScale()
}
}
},
changeAxis (gridIndex) {
if (this.initDone) {
const libName = this.metaList[gridIndex].device.library_name
let xType
let xValue // 1: I, 2: V, 3: R
let xValue
if (this.axisXDataType.description === 'Time') {
xType = 'Time'
} else {
xType = 'Elite'
if (libName === 'EliteZM15') {
xType = 'Elite'
} else if (libName === 'EliteEIS') {
xType = 'EliteEIS'
}
xValue = this.axisXDataType.id + 1
}
this.updateRegisteredChart({
@@ -452,52 +500,26 @@ export default {
deviceNameX: xType,
deviceIDX: this.metaList[gridIndex].name,
channelX: xValue,
deviceNameY: 'Elite',
deviceNameY: libName === 'EliteEIS' ? 'EliteEIS' : 'Elite',
deviceIDY: '',
channelY: this.axisYDataType.id + 1,
})
}
if (this.axisXDataType.description === 'Time') {
this.drawOverviewData()
}
this.drawDetailData() // reDraw
},
distribute: function () {
// if (this.chartDetailData.grid.length === 1) {
// this.chartDetailData.xAxis[0].axisLabel.show = false
// this.updateGridListChart({
// chartAllData: this.chartData,
// chartID: this.chartDetailData.chartID,
// gridLength: this.chartDetailData.series.length,
// })
// this.chartDetailData.series.forEach((series, index) => {
// series.xAxisIndex = index
// series.yAxisIndex = index
// })
// this.chartDetailData.xAxis[this.chartDetailData.xAxis.length - 1].axisLabel.show = true
// } else {
// this.updateGridListChart({
// chartAllData: this.chartData,
// chartID: this.chartDetailData.chartID,
// gridLength: 1,
// })
// this.chartDetailData.series.forEach(series => {
// series.xAxisIndex = 0
// series.yAxisIndex = 0
// })
// this.chartDetailData.xAxis[0].axisLabel.show = true
// }
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
...mapActions('replay', [types.meta.init]),
...mapActions('replay', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
...mapMutations('replay', [types.chart.updateRegistered, types.chart.updateAxisSplitNumber, types.chart.updateChartSampling]),
...mapMutations('replay', [types.chart.updateRegistered, types.chart.updateAxisSplitNumber, types.chart.updateChartSampling, types.chart.setOverviewDataZoomStartAndEnd]),
...mapMutations('replay', [types.meta.updateByIndex]),
...mapGetters('replay', [types.meta.getAll]),
...mapGetters('replay', [types.rec.getAll, types.rec.getCycleDict]),
},
// use watch because va-select with @change will have bug
watch: {
},
async mounted () {
this.showChartLoading()
const metaIDList = this.$route.params.metaList.split('-')
@@ -508,7 +530,7 @@ export default {
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.drawOverviewData()
this.setOverviewDataZoomStartAndEndChart({ start: 0, end: 100 })
this.$refs.top_control_panel.init() // will call changeAxis() here
this.$refs.set_min_max.init()
this.turnOnAxisEventHandler('click', 'yAxis.axisLabel', this.showMinMaxPanel, { axis: 'y' })
@@ -529,6 +551,9 @@ export default {
delete this.chartData[id]
}
this.clearChart({ chartAllData: this.chartData })
this.startTime = 0
this.endTime = 0
this.windowLength = 0
},
destroyed () {
clearInterval(this.dataZoomTimer)
@@ -9,7 +9,6 @@
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/replay/types'
// import { chartData } from '../../../../data/task/ChartsManagement'
export default {
name: 'elite-replay-chart',
@@ -7,7 +7,6 @@
</template>
<script>
// import { chartData } from '../../../../data/task/ChartsManagement'
export default {
name: 'elite-replay-overview-chart',
@@ -54,6 +53,9 @@ export default {
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
@@ -1,24 +1,71 @@
<template>
<div>
<va-card>
<div class="flex-container">
<edit-file-name class="flex-child" :ref="'edit_file_name'"/>
<download-file class="flex-child2"/>
<delete-file class="flex-child2"/>
<div class="flex-container mb-1">
<div class="flex sm8 xl8 md8 xs8 px-0 py-0">
<edit-file-name :ref="'edit_file_name'"/>
</div>
<div class="flex sm2 xl2 md2 xs2 px-0 py-0">
<download-file style="float: right;" />
</div>
<div class="flex sm2 xl2 md2 xs2 px-0 py-0">
<delete-file />
</div>
</div>
<div class="flex-container">
<change-axis class="flex-child" :ref="'change_axis_x'" deviceName="elite" axis="x" @changeAxis="changeAxis" @changeUnit="changeUnit" @clearCycle="clearCycle" @resetScale="resetScale"/>
<change-unit class="flex-child2" :ref="'change_unit_x'" deviceName="elite" axis="x" @refreshChart="refreshChart"/>
<change-scale class="flex-child1" :ref="'change_scale_x'" deviceName="elite" axis="x" @refreshChart="refreshChart" @clearCycle="clearCycle"/>
<div class="flex-container mb-2">
<div class="flex sm2 xl2 md2 xs2 px-0 py-0">
<p class="display-6"> X-axis <p/>
</div>
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-axis :ref="'change_axis_x'" axis="x" @changeAxis="changeAxis" @changeUnit="changeUnit" @clearCycle="clearCycle"/>
</div>
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-unit :ref="'change_unit_x'" axis="x" @refreshChart="refreshChart"/>
</div>
</div>
<div class="flex-container">
<change-axis class="flex-child" :ref="'change_axis_y'" deviceName="elite" axis="y" @changeAxis="changeAxis" @changeUnit="changeUnit" @clearCycle="clearCycle" @resetScale="resetScale"/>
<change-unit class="flex-child2" :ref="'change_unit_y'" deviceName="elite" axis="y" @refreshChart="refreshChart"/>
<change-scale class="flex-child1" :ref="'change_scale_y'" deviceName="elite" axis="y" @refreshChart="refreshChart" @clearCycle="clearCycle"/>
<div class="flex-container mb-2">
<div class="flex sm2 xl2 md2 xs2 px-0 py-0" />
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-scale :ref="'change_scale_x'" axis="x" @refreshChart="refreshChart" @clearCycle="clearCycle" @changeAxis="changeAxis"/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 py-0">
<abs-toggle :ref="'abs_x'" axis="x" @changeAxis="changeAxis"/>
</div>
</div>
<change-cycle v-show="showCyclePanel" style="margin: 3px;" :ref="'change_cycle'" @refreshChart="refreshChart"/>
<show-resample-rate style="margin: 3px;"/>
<show-parameter style="margin: 3px;" :ref="'show_param'" deviceName="elite"/>
<div class="flex-container mb-2">
<div class="flex sm2 xl2 md2 xs2 px-0 py-0">
<p class="display-6"> Y-axis <p/>
</div>
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-axis :ref="'change_axis_y'" axis="y" @changeAxis="changeAxis" @changeUnit="changeUnit" @clearCycle="clearCycle"/>
</div>
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-unit :ref="'change_unit_y'" axis="y" @refreshChart="refreshChart"/>
</div>
</div>
<div class="flex-container mb-2">
<div class="flex sm2 xl2 md2 xs2 px-0 py-0" />
<div class="flex sm5 xl5 md5 xs5 pl-1 pr-0 py-0">
<change-scale :ref="'change_scale_y'" axis="y" @refreshChart="refreshChart" @clearCycle="clearCycle" @changeAxis="changeAxis"/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 py-0">
<abs-toggle :ref="'abs_y'" axis="y" @changeAxis="changeAxis"/>
</div>
</div>
<div class="flex-container mb-2">
<div class="flex sm10 xl10 md10 xs10 px-0 py-0">
<change-window-start-and-size :ref="'change_window_start_and_size'" @changeOverviewDataZoom="changeOverviewDataZoom"/>
</div>
<div class="flex sm1 xl1 md1 xs1 px-0 py-0">
<lock-data-zoom :chartID="0"/>
</div>
</div>
<change-cycle v-show="showCyclePanel" :ref="'change_cycle'" @refreshChart="refreshChart"/>
<show-resample-rate/>
<show-parameter :ref="'show_param'"/>
</va-card>
</div>
</template>
@@ -29,10 +76,13 @@ import ChangeAxis from '@/components/analysis/replay/toolbox/ChangeAxis.vue'
import ChangeUnit from '@/components/analysis/replay/toolbox/ChangeUnit.vue'
import ChangeScale from '@/components/analysis/replay/toolbox/ChangeScale.vue'
import ChangeCycle from '@/components/analysis/replay/toolbox/ChangeCycle.vue'
import AbsToggle from '@/components/analysis/replay/toolbox/AbsToggle.vue'
import DownloadFile from '@/components/analysis/replay/toolbox/DownloadFile.vue'
import DeleteFile from '@/components/analysis/replay/toolbox/DeleteFile.vue'
import EditFileName from '@/components/analysis/replay/toolbox/EditFileName.vue'
import ShowParameter from '@/components/analysis/replay/toolbox/ShowParameter.vue'
import LockDataZoom from '@/components/analysis/replay/toolbox/LockDataZoom.vue'
import ChangeWindowStartAndSize from '@/components/analysis/replay/toolbox/ChangeWindowStartAndSize.vue'
import { mapFields } from 'vuex-map-fields'
export default {
@@ -43,13 +93,17 @@ export default {
ChangeUnit,
ChangeScale,
ChangeCycle,
AbsToggle,
DownloadFile,
DeleteFile,
EditFileName,
ShowParameter,
LockDataZoom,
ChangeWindowStartAndSize,
},
computed: {
...mapFields('replay', [
'metaList',
'showCyclePanel',
]),
},
@@ -59,6 +113,7 @@ export default {
},
data () {
return {
deviceName: '',
showCycle: false,
}
},
@@ -66,15 +121,22 @@ export default {
},
methods: {
init () {
this.$refs.change_axis_x.init()
this.$refs.change_axis_y.init()
this.$refs.change_unit_x.init()
this.$refs.change_unit_y.init()
this.$refs.change_scale_x.init()
this.$refs.change_scale_y.init()
this.deviceName = this.metaList[0].device.library_name
this.$refs.change_axis_x.init(this.deviceName)
this.$refs.change_axis_y.init(this.deviceName)
this.$refs.change_unit_x.init(this.deviceName)
this.$refs.change_unit_y.init(this.deviceName)
this.$refs.change_scale_x.init(this.deviceName)
this.$refs.change_scale_y.init(this.deviceName)
this.$refs.abs_x.init(this.deviceName)
this.$refs.abs_y.init(this.deviceName)
this.$refs.change_cycle.init()
this.$refs.edit_file_name.init()
this.$refs.show_param.init()
this.$refs.show_param.init(this.deviceName)
this.$refs.change_window_start_and_size.init()
},
changeOverviewDataZoom () {
this.$emit('changeOverviewDataZoom')
},
refreshChart (options) {
this.$emit('refreshChart', options)
@@ -92,13 +154,6 @@ export default {
clearCycle () {
this.$refs.change_cycle.clear()
},
resetScale () {
this.$refs.change_scale_x.reset()
this.$refs.change_scale_y.reset()
},
},
// use watch because va-select with @change will have bug
watch: {
},
mounted () {
},
@@ -106,29 +161,8 @@ export default {
</script>
<style lang="scss">
.box_inline-block {
padding: 2px;
margin: 2px;
display: inline-block;
}
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child1 {
flex: 1;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
align-items: center;
}
</style>
@@ -6,10 +6,15 @@
<div class="flex sm12 xl12 md12 xs12">
<va-accordion>
<va-collapse class="mb-2" :isOpenDefault="true">
<span slot="header">Control Panel</span>
<span slot="header">CONTROL PANEL</span>
<div slot="body">
<div>
<top-control-panel :ref="'top_control_panel'" @refreshChart="setOption"/>
<top-control-panel
:ref="'top_control_panel'"
@changeOverviewDataZoom="changeOverviewDataZoom"
@redrawAllChart="drawInitData()"
@refreshChart="drawDetailData()"
/>
</div>
</div>
</va-collapse>
@@ -21,9 +26,8 @@
<div :class="chartAreaClass">
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<neulive-replay-overview-chart :ref="'chart_overview'" class="mb-1" :cardheight="'7vh'" :data="chartOverviewData" @dataZoomEvent="dataZoomEvent($event)"/>
<neulive-replay-chart :ref="'chart_detail'" @dblclick.native="distribute()" :cardheight="'80vh'" :data="chartDetailData" @dataZoomEvent="updateMarkArea($event)" />
<change-channel-value-min-max :ref="'set_min_max'"/>
<neulive-replay-overview-chart :ref="'chart_overview'" class="my-1 py-0" :cardheight="'7vh'" :data="chartOverviewData" @dataZoomEvent="dataZoomEvent($event)"/>
<neulive-replay-chart :ref="'chart_detail'" class="my-1 py-0" @dblclick.native="distribute()" :cardheight="'84vh'" :data="chartDetailData" @dataZoomEvent="updateMarkArea($event)" />
</div>
</div>
</div>
@@ -36,11 +40,12 @@ import { mapActions, mapGetters, mapMutations } from 'vuex'
import NeuliveReplayChart from './NeuliveReplayChart.vue'
import NeuliveReplayOverviewChart from './NeuliveReplayOverviewChart.vue'
import TopControlPanel from './TopControlPanel.vue'
import ChangeChannelValueMinMax from '@/components/analysis/replay/toolbox/ChangeChannelValueMinMax.vue'
// import ChangeChannelValueMinMax from '@/components/analysis/replay/toolbox/ChangeChannelValueMinMax.vue'
import api from '@/data/api/index'
import { download } from '@/data/download/Download'
import types from '@/store/modules/analysis/replay/types'
import { mapFields } from 'vuex-map-fields'
import { download } from '@/data/download/Download'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
export default {
name: 'neulive-data-replay',
@@ -48,17 +53,37 @@ export default {
NeuliveReplayChart,
NeuliveReplayOverviewChart,
TopControlPanel,
ChangeChannelValueMinMax,
// ChangeChannelValueMinMax,
},
computed: {
displayType () {
return {
x: this.axisXLabelScale.description === 'Log' ? 'log' : 'value',
y: this.axisYLabelScale.description === 'Log' ? 'log' : 'value',
}
},
...mapFields('replay', [
'chartData',
'chartKey',
'chartOverviewData',
'chartDetailData',
'currentScale',
'startTime',
'endTime',
'windowLength',
'axisXLabelScale',
'axisYLabelScale',
'startTimeCurrent',
'endTimeCurrent',
'sampleMethod',
'useSdCardData',
'currentChunkDataSplitIndex',
'maxChunkDataSplitNum',
'sdDataChunkDict',
'useFilter',
'isInitDone',
'isDownloading',
]),
},
props: {
@@ -86,13 +111,12 @@ export default {
metaList: null,
dataInfo: {},
maxTime: 0,
startTime: 0,
endTime: 0,
zoomTime: 0,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl9', 'pa-0'],
dataZoomStart: 0,
dataZoomEnd: 100,
inDistributeMode: false,
userClickDirection: '',
}
},
mqtt: {
@@ -101,12 +125,6 @@ export default {
turnOnAxisEventHandler (eventName, query, handler, attr) {
this.$refs.chart_detail.turnOnAxisEventHandler(eventName, query, handler, attr)
},
showMinMaxPanel (params) {
this.$refs.set_min_max.showPanel(params)
},
setOption (options) {
this.$refs.chart_detail.mergeOptions(options)
},
showChartLoading: function (chart) {
const loadStyle = {
text: '',
@@ -131,17 +149,6 @@ export default {
this.$refs.chart_overview.hideLoading()
this.$refs.chart_detail.hideLoading()
},
getData: async function (scale, sampleMethod, id, channel, meta) {
if (scale === 1) {
return await download.rawByIDChannels(id, channel, meta)
} else {
if (sampleMethod === 'mean') {
return await download.miniMeanByIDChannel(id, channel, meta)
} else if (sampleMethod === 'random') {
return await download.miniRandomByIDChannel(id, channel, meta)
}
}
},
register: async function () {
let chNumber = 0
this.metaList.forEach(meta => {
@@ -157,12 +164,12 @@ export default {
const register = this.metaList.map(meta => {
this.metaList.map(meta => {
JSON.parse(meta.channels).map(ch => {
JSON.parse(meta.channels).sort((a, b) => a > b ? 1 : -1).map(ch => {
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridIndex: _chNumber,
deviceNameX: 'Time',
deviceNameX: 'Category',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: ch,
@@ -185,10 +192,7 @@ export default {
}
})
await Promise.all(register)
this.chartDetailData.series.forEach((series, index) => {
this.chartDetailData.xAxis[series.xAxisIndex].min = 0
this.chartDetailData.xAxis[series.xAxisIndex].max = this.maxTime
})
this.chartOverviewData.series.forEach((series, index) => {
this.chartOverviewData.xAxis[series.xAxisIndex].min = 0
this.chartOverviewData.xAxis[series.xAxisIndex].max = this.maxTime
@@ -204,31 +208,31 @@ export default {
})
},
updateMarkArea: function (e) {
console.log(e)
if (e.batch != null) {
// console.log(e.batch)
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.dataZoomStart = dataZoom.start
this.dataZoomEnd = dataZoom.end
const startTime = (this.endTime - this.startTime) * dataZoom.start / 100
const endTime = (this.endTime - this.startTime) * dataZoom.end / 100
// console.log(e)
if (!this.useSdCardData) {
if (e.batch != null) {
// console.log(e.batch)
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.dataZoomStart = dataZoom.start
this.dataZoomEnd = dataZoom.end
const startTime = (this.endTime - this.startTime) * dataZoom.start / 100
const endTime = (this.endTime - this.startTime) * dataZoom.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.dataZoomStart = e.start
this.dataZoomEnd = e.end
const startTime = (this.endTime - this.startTime) * e.start / 100
const endTime = (this.endTime - this.startTime) * e.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
// console.log(this.startTime, this.endTime)
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.dataZoomStart = e.start
this.dataZoomEnd = e.end
const startTime = (this.endTime - this.startTime) * e.start / 100
const endTime = (this.endTime - this.startTime) * e.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
// console.log(this.startTime, this.endTime)
}
}
},
@@ -239,137 +243,401 @@ export default {
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.startTime = this.maxTime * dataZoom.start / 100
this.endTime = this.maxTime * dataZoom.end / 100
// console.log(this.startTime, this.endTime)
this.startTime = parseInt(this.maxTime * dataZoom.start / 100)
this.endTime = parseInt(this.maxTime * dataZoom.end / 100)
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = dataZoom.start
this.chartOverviewData.dataZoom[0].end = dataZoom.end
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.startTime = this.maxTime * e.start / 100
this.endTime = this.maxTime * e.end / 100
// console.log(this.startTime, this.endTime)
this.startTime = parseInt(this.maxTime * e.start / 100)
this.endTime = parseInt(this.maxTime * e.end / 100)
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = e.start
this.chartOverviewData.dataZoom[0].end = e.end
}
}
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomStart / 100))
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomEnd / 100))
this.zoomTime = 0
},
drawOverviewData: async function () {
changeOverviewDataZoom () {
this.setOverviewDataZoomStartAndEndChart({
start: this.startTime * 100 / this.maxTime,
end: this.endTime * 100 / this.maxTime,
})
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomStart / 100))
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomEnd / 100))
this.chartKey++
},
computeResampleScale (meta, duration) {
const sampleRate = (meta.parameter_set.SAMPLE_RATE === 0) ? parseInt(8e5 / meta.parameter_set.ADC_CLOCK) : meta.parameter_set.SAMPLE_RATE // Hz
const dataPointNum = (parseInt(duration) / 1e6) * sampleRate // us -> s: /1e6
let scale
if (dataPointNum > 2e6) { // 2000 (points) * 1000 (resample) = 2e6
scale = '1000'
} else if (dataPointNum > 2e5) { // 2000 (points) * 100 (resample) = 2e5
scale = '100'
} else if (dataPointNum > 2e4) { // 2000 (points) * 10 (resample) = 2e4
scale = '10'
} else {
scale = '1'
}
return scale
},
drawInitData: async function () {
this.showChartLoading('overview')
this.setOverviewDataZoomStartAndEndChart({ start: 0, end: 100 })
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = 0
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.metaList[0].time_duration
let metaIndex = 0
for (const meta in this.dataInfo) {
let metaInfo
this.metaList.forEach(_meta => {
if (parseInt(_meta.id) === parseInt(meta)) {
metaInfo = _meta
}
})
const sampleRate = (800000 / metaInfo.parameter_set.ADC_CLOCK)
let scale = (((metaInfo.time_duration / 100000) * sampleRate) / 4000) * 0.1
if (scale > 500) {
scale = '1000'
} else if (scale > 200) {
scale = '100'
} else if (scale > 30) {
scale = '10'
} else {
scale = '1'
}
console.log('overview scale:', scale)
const metaInfo = this.metaList[metaIndex]
const scale = this.computeResampleScale(metaInfo, metaInfo.time_duration)
const allChannels = JSON.parse(metaInfo.channels)
allChannels.sort((a, b) => a > b ? 1 : -1)
this.currentScale = scale
const getAllData = async function (_this) {
for (const ch in _this.dataInfo[meta]) {
_this.updateChartSamplingChart({ chartID: _this.chartOverviewData.chartID, seriesIndex: ch, method: function (frame) { return frame[0] } })
let seriesIndex = 0
for (const ch of allChannels) {
let _channels
if (metaInfo.device.library_name === 'Neulive3.5ex') {
if (ch >= 0 && ch <= 31) {
_channels = allChannels.filter(c => c >= 0 && c <= 31)
} else if (ch >= 32 && ch <= 63) {
_channels = allChannels.filter(c => c >= 32 && c <= 63)
}
} else {
_channels = allChannels
}
const idealTimeStep = parseFloat((1e6 / metaInfo.parameter_set.SAMPLE_RATE) * _channels.length)
_this.chartOverviewData.series[seriesIndex].data.length = 0
_this.chartDetailData.xAxis[seriesIndex].data.length = 0
_this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = _this.dataInfo[meta][ch][scale]
for (let i = 0; i < dataList.length; i++) {
if (dataList[i].data.length === 0) {
_this.intoDataRec({
index: i,
metaID: meta,
channel: ch,
scale: scale,
data: await _this.getData(parseInt(scale), _this.sampleMethod, dataList[i].id, ch, metaInfo),
})
const data = await _this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: _this.sampleMethod,
id: dataList[i].id,
channel: ch,
meta: metaInfo,
})
_this.chartOverviewData.series[seriesIndex].data.push(...data)
for (let j = 0, len = data.length; j < len; j++) {
const previousTime = _this.chartDetailData.xAxis[seriesIndex].data[_this.chartDetailData.xAxis[seriesIndex].data.length - 1]
const dataTime = parseInt(data[j][0])
if (_this.currentScale === '1' && previousTime + idealTimeStep < dataTime) {
for (let lossTime = previousTime; lossTime < dataTime; lossTime += idealTimeStep) {
_this.chartDetailData.xAxis[seriesIndex].data.push(lossTime)
_this.chartDetailData.series[seriesIndex].data.push(null)
}
}
_this.chartDetailData.xAxis[seriesIndex].data.push(dataTime)
_this.chartDetailData.series[seriesIndex].data.push(data[j][1])
}
}
_this.updateChartSamplingChart({ chartID: _this.chartOverviewData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
_this.updateChartSamplingChart({ chartID: _this.chartDetailData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
seriesIndex++
}
}
await getAllData(this)
let seriesIndex = 0
for (const ch in this.dataInfo[meta]) {
const dataList = this.dataInfo[meta][ch][scale]
for (let i = 0; i < dataList.length; i++) {
this.chartOverviewData.series[seriesIndex].data.push(...dataList[i].data)
}
seriesIndex++
}
metaIndex++
}
this.hideChartLoading()
},
getDataListIndex (dataList, pos) {
let dataListIndex
// [REGION]
// because current chunk start time >= previous chunk end time
// so the if-condition is for = condition
for (let i = 0; i < dataList.length; i++) {
const chunkStartTime = parseInt(dataList[i].start_time_sec) * 1e6 + parseInt(dataList[i].start_time_micro)
if (this.startTime === chunkStartTime) {
dataListIndex = i
break
}
}
if (dataListIndex === undefined) {
for (let i = 0; i < dataList.length; i++) {
const chunkStartTime = parseInt(dataList[i].start_time_sec) * 1e6 + parseInt(dataList[i].start_time_micro)
const chunkEndTime = parseInt(dataList[i].end_time_sec) * 1e6 + parseInt(dataList[i].end_time_micro)
if (this.startTime >= chunkStartTime && this.startTime <= chunkEndTime) {
dataListIndex = i
break // only download 1 chunk data
}
}
}
if (dataListIndex === undefined) { // for the case start time not from 0s
dataListIndex = 0
}
// [ENDREGION]
if (pos === 'prev' && this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum) {
dataListIndex = Math.max(0, dataListIndex - 1)
} else if (pos === 'next' && this.currentChunkDataSplitIndex === 1) {
dataListIndex = Math.min(dataList.length - 1, dataListIndex + 1)
} else if (pos === 'up') {
dataListIndex = Math.max(0, dataListIndex - 1)
} else if (pos === 'down') {
dataListIndex = Math.min(dataList.length - 1, dataListIndex + 1)
}
return dataListIndex
},
async downloadSdDataChunk (scale, uuid, channel, startTime, endTime) {
// only download 1 chunk data for efficiency
return await this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: null,
id: null,
channel: channel,
meta: null,
uuid: uuid,
startTime: startTime,
endTime: endTime,
})
},
updateCurrentChunkDataSplitIndex (pos) {
this.currentChunkDataSplitIndex = parseInt(this.currentChunkDataSplitIndex)
if (pos === 'next') {
this.currentChunkDataSplitIndex++
if (this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum + 1) {
this.currentChunkDataSplitIndex = 1
}
} else if (pos === 'prev') {
this.currentChunkDataSplitIndex--
if (this.currentChunkDataSplitIndex < 1) {
this.currentChunkDataSplitIndex = this.maxChunkDataSplitNum
}
} else if (pos === 'up' || pos === 'down') {
this.currentChunkDataSplitIndex = 1
}
},
async drawSdChart (metaIndex, pos) {
this.showChartLoading('detail')
const scale = '1'
this.currentScale = scale
const metaID = this.metaList[metaIndex].id
const uuid = this.metaList[metaIndex].uuid
const channelList = Object.keys(this.dataInfo[metaID])
const ret = await download.rawSdDataAllTimeslotByUUID(uuid, channelList[0])
// channelList[0]: first channel
// cuz all channels data chunk will have same start time / end time
// so use first channel info is enough, and can avoid keyboard input move forward multiple time
const dataList = ret.data
const dataListIndex = this.getDataListIndex(dataList, pos)
let seriesIndex = 0
for (const channel of channelList) {
this.chartDetailData.xAxis[seriesIndex].data.length = 0
this.chartDetailData.series[seriesIndex].data.length = 0
if (pos === 'next' || pos === 'prev' || pos === 'up' || pos === 'down') {
this.startTimeCurrent = this.startTime = parseInt(dataList[dataListIndex].start_time_sec) * 1e6 + parseInt(dataList[dataListIndex].start_time_micro)
}
this.endTimeCurrent = this.endTime = parseInt(dataList[dataListIndex].end_time_sec) * 1e6 + parseInt(dataList[dataListIndex].end_time_micro)
this.windowLength = this.endTime - this.startTime
this.setOverviewDataZoomStartAndEndChart({
start: this.startTime * 100 / parseInt(this.metaList[metaIndex].time_duration),
end: this.endTime * 100 / parseInt(this.metaList[metaIndex].time_duration),
})
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + (this.endTime - this.startTime) * ((this.currentChunkDataSplitIndex - 1) / this.maxChunkDataSplitNum)
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + (this.endTime - this.startTime) * (this.currentChunkDataSplitIndex / this.maxChunkDataSplitNum)
if (this.currentChunkDataSplitIndex === 1 || this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum) {
this.isDownloading = true
if (this.sdDataChunkDict[metaID] === undefined) {
this.sdDataChunkDict[metaID] = {}
}
this.sdDataChunkDict[metaID][channel] = await this.downloadSdDataChunk(scale, uuid, channel, this.startTime, this.endTime)
}
const sdDataChunk = this.sdDataChunkDict[metaID][channel]
const sdDataChunkSplit = sdDataChunk.slice(sdDataChunk.length * ((this.currentChunkDataSplitIndex - 1) / this.maxChunkDataSplitNum), sdDataChunk.length * (this.currentChunkDataSplitIndex / this.maxChunkDataSplitNum))
const timeArr = sdDataChunkSplit.map(x => x[0])
const valArr = sdDataChunkSplit.map(x => x[1])
for (let i = 0, len = sdDataChunkSplit.length; i < len; i++) {
this.chartDetailData.xAxis[seriesIndex].data.push(timeArr[i])
let filterData = valArr[i]
if (this.useFilter) {
this.chartDetailData.series[seriesIndex].filter.forEach(filter => {
if (filter.filter != null && filterData) {
filterData = IIRFilter.filter(filter.filter, filterData)
}
})
}
this.chartDetailData.series[seriesIndex].data.push(filterData)
}
seriesIndex++
}
this.chartKey++
this.hideChartLoading()
this.isDownloading = false
},
async moveToNextDataSubRegion () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('next')
await this.drawSdChart(0, 'next')
}
},
async moveToPreviousDataSubRegion () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('prev')
await this.drawSdChart(0, 'prev')
}
},
async moveToNextDataChunk () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('down')
await this.drawSdChart(0, 'down')
} else {
if (this.endTime + this.windowLength > this.metaList[0].time_duration) {
this.endTime = this.metaList[0].time_duration
this.startTime = this.endTime - this.windowLength
} else {
this.startTime = this.endTime
this.endTime += this.windowLength
}
this.changeOverviewDataZoom()
}
},
async moveToPreviousDataChunk () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('up')
await this.drawSdChart(0, 'up')
} else {
if (this.startTime - this.windowLength < 0) {
this.startTime = 0
this.endTime = this.windowLength
} else {
this.endTime = this.startTime
this.startTime -= this.windowLength
}
this.changeOverviewDataZoom()
}
},
drawDetailData: async function () {
this.showChartLoading('detail')
const timeDur = this.endTimeCurrent - this.startTimeCurrent
let _metaIndex = 0
for (const meta in this.dataInfo) {
let metaInfo
this.metaList.forEach(_meta => {
if (parseInt(_meta.id) === parseInt(meta)) {
metaInfo = _meta
}
})
const sampleRate = (800000 / metaInfo.parameter_set.ADC_CLOCK)
let scale = (((timeDur / 100000) * sampleRate) / 4000) * 0.1
if (scale > 500) {
scale = '1000'
} else if (scale > 200) {
scale = '100'
} else if (scale > 30) {
scale = '10'
} else {
scale = '1'
}
const metaInfo = this.metaList[_metaIndex]
const allChannels = JSON.parse(metaInfo.channels)
allChannels.sort((a, b) => a > b ? 1 : -1)
const scale = this.computeResampleScale(metaInfo, timeDur)
this.currentScale = scale
let seriesIndex = 0
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
for (const ch in this.dataInfo[meta]) {
this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = this.dataInfo[meta][ch][scale]
let startIndex = 0
let endIndex = 0
// get the subfile index
for (let i = 0; i < dataList.length; i++) {
if (this.startTimeCurrent >= parseInt(dataList[i].start_time) && this.startTimeCurrent <= parseInt(dataList[i].end_time)) {
startIndex = i
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('curr')
await this.drawSdChart(_metaIndex, 'curr')
} else { // ble data
for (const ch of allChannels) {
let _channels
if (metaInfo.device.library_name === 'Neulive3.5ex') {
if (ch >= 0 && ch <= 31) {
_channels = allChannels.filter(c => c >= 0 && c <= 31)
} else if (ch >= 32 && ch <= 63) {
_channels = allChannels.filter(c => c >= 32 && c <= 63)
}
} else {
_channels = allChannels
}
if (this.endTimeCurrent >= parseInt(dataList[i].start_time) && this.endTimeCurrent <= parseInt(dataList[i].end_time)) {
endIndex = i
}
}
// download and push into chart
for (let i = startIndex; i <= endIndex; i++) {
const data = await this.getData(parseInt(scale), this.sampleMethod, dataList[i].id, ch, metaInfo)
for (let j = 0, len = data.length; j < len; j++) {
if (parseInt(data[j][0]) >= this.startTimeCurrent && parseInt(data[j][0]) <= this.endTimeCurrent) {
this.chartDetailData.series[seriesIndex].data.push(data[j])
const idealTimeStep = parseFloat((1e6 / metaInfo.parameter_set.SAMPLE_RATE) * _channels.length)
this.chartDetailData.xAxis[seriesIndex].data.length = 0
this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = this.dataInfo[meta][ch][scale]
let startIndex = 0
let endIndex = 0
// get the subfile index
for (let i = 0; i < dataList.length; i++) {
if (this.startTimeCurrent >= parseInt(dataList[i].start_time) && this.startTimeCurrent <= parseInt(dataList[i].end_time)) {
startIndex = i
}
if (this.endTimeCurrent >= parseInt(dataList[i].start_time) && this.endTimeCurrent <= parseInt(dataList[i].end_time)) {
endIndex = i
}
}
// download and push into chart
let endFlag = false // because data is sorted by time, so if current processed data piece exceeds endTimeCurrent, can early stop
this.isDownloading = true
for (let i = startIndex; i <= endIndex; i++) {
const data = await this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: this.sampleMethod,
id: dataList[i].id,
channel: ch,
meta: metaInfo,
})
for (let j = 0, len = data.length; j < len; j++) {
const previousTime = this.chartDetailData.xAxis[seriesIndex].data[this.chartDetailData.xAxis[seriesIndex].data.length - 1]
const dataTime = parseInt(data[j][0])
if (dataTime >= this.startTimeCurrent && dataTime <= this.endTimeCurrent) {
if (this.currentScale === '1' && previousTime + idealTimeStep < dataTime) {
for (let lossTime = previousTime; lossTime < dataTime; lossTime += idealTimeStep) {
this.chartDetailData.xAxis[seriesIndex].data.push(lossTime)
this.chartDetailData.series[seriesIndex].data.push(null)
}
}
this.chartDetailData.xAxis[seriesIndex].data.push(dataTime)
let filterData = data[j][1]
if (this.useFilter) {
this.chartDetailData.series[seriesIndex].filter.forEach(filter => {
if (filter.filter != null && filterData) {
filterData = IIRFilter.filter(filter.filter, filterData)
}
})
}
this.chartDetailData.series[seriesIndex].data.push(filterData)
}
// early stop
if (dataTime > this.endTimeCurrent) {
endFlag = true
break
}
}
// early stop
if (endFlag === true) {
break
}
}
seriesIndex++
}
for (let i = 0, len = this.chartDetailData.xAxis.length; i < len; i++) {
this.chartDetailData.xAxis[i].min = this.startTimeCurrent
this.chartDetailData.xAxis[i].max = this.endTimeCurrent
}
seriesIndex++
}
_metaIndex++
}
this.chartKey++
this.hideChartLoading()
this.isDownloading = false
},
checkDataZoom: function () {
checkDataZoom: async function () {
this.zoomTime++
if (this.zoomTime >= 2) {
if (this.startTimeCurrent !== this.startTime || this.endTimeCurrent !== this.endTime) {
this.startTimeCurrent = this.startTime
this.endTimeCurrent = this.endTime
if (this.useSdCardData) {
const metaID = this.metaList[0].id
const uuid = this.metaList[0].uuid
const channelList = Object.keys(this.dataInfo[metaID])
const ret = await download.rawSdDataAllTimeslotByUUID(uuid, channelList[0])
// channelList[0]: first channel
// cuz all channels data chunk will have same start time / end time
// so use first channel info is enough, and can avoid keyboard input move forward multiple time
const dataList = ret.data
const dataListIndex = this.getDataListIndex(dataList, 'curr')
this.startTimeCurrent = this.startTime = parseInt(dataList[dataListIndex].start_time_sec) * 1e6 + parseInt(dataList[dataListIndex].start_time_micro)
this.endTimeCurrent = this.endTime = parseInt(dataList[dataListIndex].end_time_sec) * 1e6 + parseInt(dataList[dataListIndex].end_time_micro)
} else {
this.startTimeCurrent = this.startTime
this.endTimeCurrent = this.endTime
}
this.windowLength = this.endTime - this.startTime
this.currentChunkDataSplitIndex = 1
this.drawDetailData()
this.$refs.top_control_panel.resetScale()
}
}
},
@@ -438,19 +706,39 @@ export default {
this.inDistributeMode = true
}
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
async handleKeyboardInputEvent (event) {
if (event.key === 'ArrowRight') {
this.userClickDirection = 'next'
await this.moveToNextDataSubRegion()
} else if (event.key === 'ArrowLeft') {
this.userClickDirection = 'prev'
await this.moveToPreviousDataSubRegion()
} else if (event.key === 'ArrowDown') {
this.userClickDirection = 'next'
await this.moveToNextDataChunk()
} else if (event.key === 'ArrowUp') {
this.userClickDirection = 'prev'
await this.moveToPreviousDataChunk()
}
},
async eventHandler (event) {
if (this.isInitDone && !this.isDownloading) {
await this.handleKeyboardInputEvent(event)
}
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom, types.chart.getData]),
...mapActions('replay', [types.meta.init]),
...mapActions('replay', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
...mapMutations('replay', [types.chart.updateAxisLabel, types.chart.updateChartSampling]),
...mapMutations('replay', [types.chart.updateAxisLabel, types.chart.updateChartSampling, types.chart.setOverviewDataZoomStartAndEnd]),
...mapMutations('replay', [types.meta.updateByIndex]),
...mapGetters('replay', [types.meta.getAll]),
...mapGetters('replay', [types.rec.getAll]),
},
// use watch because va-select with @change will have bug
watch: {
},
async mounted () {
this.showChartLoading()
const metaIDList = this.$route.params.metaList.split('-')
@@ -461,16 +749,19 @@ export default {
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.drawOverviewData()
this.drawDetailData()
this.dataZoomTimer = setInterval(this.checkDataZoom, 1000)
this.$refs.top_control_panel.init()
this.$refs.set_min_max.init()
this.dataZoomTimer = setInterval(this.checkDataZoom, 1000)
this.turnOnAxisEventHandler('click', 'yAxis.axisLabel', this.showMinMaxPanel, { axis: 'y' })
// [TODO] change the global event listener to chart-specific event listener
window.addEventListener('keyup', this.eventHandler)
await this.drawInitData()
this.isInitDone = true
console.log(this.chartOverviewData)
console.log(this.chartDetailData)
},
beforeDestroy () {
this.currentChunkDataSplitIndex = 1
this.$refs.chart_overview.clear()
this.$refs.chart_detail.clear()
for (const id in this.chartData) {
@@ -480,8 +771,13 @@ export default {
delete this.chartData[id]
}
this.clearChart({ chartAllData: this.chartData })
this.startTime = 0
this.endTime = 0
this.startTimeCurrent = 0
this.endTimeCurrent = 0
},
destroyed () {
window.removeEventListener('keyup', this.eventHandler)
clearInterval(this.dataZoomTimer)
},
}
@@ -1,7 +1,15 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<div>
<va-button small class="mb-3" @click="selectAllSeries">
SELECT ALL
</va-button>
<va-button small class="mb-3" @click="inverseSelectAllSeries">
INVERSE SELECT
</va-button>
</div>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
@@ -9,7 +17,7 @@
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/replay/types'
// import { chartData } from '../../../../data/task/ChartsManagement'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'neulive-replay-chart',
@@ -41,28 +49,34 @@ export default {
},
data () {
return {
chartID: 1,
}
},
computed: {
...mapFields('replay', [
'chartKey',
'chartData',
]),
},
watch: {
},
methods: {
resize: function () {
chartEvent: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
if (this.$refs.chart_ref.chart != null) {
this.$refs.chart_ref.chart.on('legendselectchanged', this.legendChange)
this.$refs.chart_ref.chart.on('legendselectall', this.legendChange)
this.$refs.chart_ref.chart.on('legendinverseselect', this.legendChange)
}
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
turnOnAxisEventHandler (eventName, query, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
chartID: this.chartID,
axis: attr.axis,
status: 'on',
})
@@ -70,9 +84,8 @@ export default {
this.$refs.chart_ref.chart._zr.on(eventName, query, handler)
},
turnOffAxisEventHandler (eventName, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
chartID: this.chartID,
axis: attr.axis,
status: 'off',
})
@@ -88,11 +101,36 @@ export default {
clear () {
this.$refs.chart_ref.clear()
},
legendChange (params) {
console.log('legendChange', params)
const activeLegendLen = Object.values(params.selected).filter(val => val === true).length
let index = 0
for (const [gridIndex, value] of Object.values(params.selected).entries()) { // [TODO] 技術債,目前real time chart一個grid只對應一條series
if (value === true) {
this.chartData[this.chartID].grid[gridIndex].height = (82 / activeLegendLen) - 2 + '%'
this.chartData[this.chartID].grid[gridIndex].top = index * (82 / activeLegendLen) + 7 + '%'
index++
} else {
this.chartData[this.chartID].grid[gridIndex].height = '0%'
this.chartData[this.chartID].grid[gridIndex].top = '0%'
}
}
},
selectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendAllSelect',
})
},
inverseSelectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendInverseSelect',
})
},
...mapMutations('replay', [types.chart.updateAxisEventHandler]),
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
mounted () {
this.resizeTimer = setInterval(this.chartEvent, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
@@ -1,13 +1,13 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: 8vh;'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
// import { chartData } from '../../../../data/task/ChartsManagement'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'neulive-replay-overview-chart',
@@ -42,6 +42,9 @@ export default {
}
},
computed: {
...mapFields('replay', [
'chartKey',
]),
},
watch: {
},
@@ -1,16 +1,33 @@
<template>
<div>
<va-card>
<edit-file-name style="display: inline;" :ref="'edit_file_name'"/>
<download-file style="display: inline;"/>
<delete-file style="display: inline;"/>
<show-resample-rate/>
<change-axis v-show="showXAxis" :ref="'change_axis_x'" deviceName="neulive" axis="x"/>
<change-axis v-show="showYAxis" :ref="'change_axis_y'" deviceName="neulive" axis="y"/>
<change-unit v-show="showXUnit" :ref="'change_unit_x'" deviceName="neulive" axis="x" @refreshChart="refreshChart"/>
<change-unit :ref="'change_unit_y'" deviceName="neulive" axis="y" @refreshChart="refreshChart"/>
<change-scale :ref="'change_scale_y'" deviceName="neulive" axis="y"/>
<show-parameter :ref="'show_param'" deviceName="neulive"/>
<edit-file-name :ref="'edit_file_name'"/>
<div class="row py-2">
<div class="col">
<download-file />
</div>
<div class="col">
<delete-file />
</div>
<div class="col">
<va-button flat small color="gray" icon="fa fa-filter" @click="openFilterPanel"/>
</div>
</div>
<div>
<show-parameter v-if="metaList[0] && metaList[0].device.library_name !== 'Neulive3.5-REC'" class="mb-2" :ref="'show_param'"/>
<show-resample-rate/>
<div class="flex-container">
<change-window-start-and-size class="flex-child" :ref="'change_window_start_and_size'" @changeOverviewDataZoom="changeOverviewDataZoom"/>
<lock-data-zoom class="flex-child2" :chartID="0"/>
</div>
<change-axis v-show="showXAxis" :ref="'change_axis_x'" axis="x"/>
<change-axis v-show="showYAxis" :ref="'change_axis_y'" axis="y"/>
<change-unit v-show="showXUnit" :ref="'change_unit_x'" axis="x"/>
<change-unit :ref="'change_unit_y'" class="box_inline-block" axis="y"/>
<sd-card-toggle :ref="'sd_card_toggle'" :metaIndex="0" @redrawAllChart="redrawAllChart" @refreshChart="refreshChart"/>
<change-current-chunk-index @refreshChart="refreshChart"/>
</div>
<filter-toggle :ref="'filter_toggle'" @refreshChart="refreshChart"/>
</va-card>
</div>
</template>
@@ -19,11 +36,16 @@
import ShowResampleRate from '@/components/analysis/replay/toolbox/ShowResampleRate.vue'
import ChangeAxis from '@/components/analysis/replay/toolbox/ChangeAxis.vue'
import ChangeUnit from '@/components/analysis/replay/toolbox/ChangeUnit.vue'
import ChangeScale from '@/components/analysis/replay/toolbox/ChangeScale.vue'
import ChangeCurrentChunkIndex from '@/components/analysis/replay/toolbox/ChangeCurrentChunkIndex.vue'
import DownloadFile from '@/components/analysis/replay/toolbox/DownloadFile.vue'
import DeleteFile from '@/components/analysis/replay/toolbox/DeleteFile.vue'
import EditFileName from '@/components/analysis/replay/toolbox/EditFileName.vue'
import ShowParameter from '@/components/analysis/replay/toolbox/ShowParameter.vue'
import SdCardToggle from '@/components/analysis/replay/toolbox/SdCardToggle.vue'
import LockDataZoom from '@/components/analysis/replay/toolbox/LockDataZoom.vue'
import ChangeWindowStartAndSize from '@/components/analysis/replay/toolbox/ChangeWindowStartAndSize.vue'
import FilterToggle from '@/components/analysis/replay/toolbox/FilterToggle.vue'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'top-control-panel',
@@ -31,20 +53,30 @@ export default {
ShowResampleRate,
ChangeAxis,
ChangeUnit,
ChangeScale,
ChangeCurrentChunkIndex,
DownloadFile,
DeleteFile,
EditFileName,
ShowParameter,
SdCardToggle,
LockDataZoom,
ChangeWindowStartAndSize,
FilterToggle,
},
computed: {
},
props: {
...mapFields('replay', [
'metaList',
'showCyclePanel',
'useSdCardData',
'startTime',
'endTime',
]),
},
async created () {
},
data () {
return {
deviceName: '',
showXAxis: false,
showYAxis: false,
showXUnit: false,
@@ -54,20 +86,34 @@ export default {
},
methods: {
init () {
this.$refs.change_axis_x.init()
this.$refs.change_axis_y.init()
this.$refs.change_unit_x.init()
this.$refs.change_unit_y.init()
this.$refs.change_scale_y.init()
this.deviceName = this.metaList[0].device.library_name
this.$refs.change_axis_x.init(this.deviceName)
this.$refs.change_axis_y.init(this.deviceName)
this.$refs.change_unit_x.init(this.deviceName)
this.$refs.change_unit_y.init(this.deviceName)
this.$refs.edit_file_name.init()
this.$refs.show_param.init()
if (this.deviceName !== 'Neulive3.5-REC') {
this.$refs.show_param.init(this.deviceName)
}
this.$refs.change_window_start_and_size.init()
this.$refs.sd_card_toggle.init()
this.$refs.filter_toggle.init()
},
refreshChart (options) {
this.$emit('refreshChart', options)
changeOverviewDataZoom () {
this.$emit('changeOverviewDataZoom')
},
resetScale () {
this.$refs.change_scale_y.reset()
redrawAllChart () {
this.$emit('redrawAllChart')
},
refreshChart () {
this.$emit('refreshChart')
},
openFilterPanel () {
const sd = this.useSdCardData ? 'sd' : 'ble'
const href = location.href.split('/')[2]
window.open(`http://${href}/#/admin/data-spike/${this.metaList[0].id}/${this.startTime}/${this.endTime}/${sd}`)
},
// startTime
},
// use watch because va-select with @change will have bug
watch: {
@@ -78,4 +124,29 @@ export default {
</script>
<style lang="scss">
.box_inline-block {
padding: 2px;
margin: 2px;
display: inline-block;
}
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child1 {
flex: 1;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
</style>
@@ -0,0 +1,117 @@
<template>
<div>
<va-button
:color="buttonColor"
:key="clickTime"
v-model="absToggleStatus[axis]"
@click="performABS()"
class="round-button px-1 py-1"
>
ABS
</va-button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { mapFields } from 'vuex-map-fields'
import types from '@/store/modules/analysis/replay/types'
export default {
name: 'AbsToggle',
components: {
},
computed: {
displayType () {
return {
x: this.axisXLabelScale.description === 'Log' ? 'log' : 'value',
y: this.axisYLabelScale.description === 'Log' ? 'log' : 'value',
}
},
...mapFields('replay', [
'chartData',
'metaList',
'absToggleStatus',
]),
},
props: {
chartID: {
type: Number, // detail data
default: 1,
},
axis: {
type: String, // x or y
},
metaIndex: {
type: Number, // which meta file
default: 0,
},
},
created () {
},
data () {
return {
metaID: 0,
metaInfo: null,
buttonColor: 'gray',
clickTime: 0,
}
},
mqtt: {
},
methods: {
init (deviceName) {
this.deviceName = deviceName
this.metaInfo = this.metaList[this.metaIndex]
this.metaID = this.metaInfo.id
this.absToggleStatus[this.axis] = false
},
performABS () {
this.absToggleStatus[this.axis] = !this.absToggleStatus[this.axis]
if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
this.calculateHelper(this.metaIndex) // [TODO] elite tmp use metaIndex for grid index
}
this.clickTime++
this.buttonColor = this.clickTime % 2 === 1 ? 'success' : 'gray'
},
async calculateHelper (gridIndex) {
let seriesData
if (this.absToggleStatus[this.axis]) {
seriesData = JSON.parse(JSON.stringify(this.chartData[this.chartID].series[gridIndex].data))
for (let i = 0; i < seriesData.length; i++) {
if (this.axis === 'x') {
seriesData[i][0] = Math.abs(seriesData[i][0])
} else if (this.axis === 'y') {
seriesData[i][1] = Math.abs(seriesData[i][1])
}
}
this.updateSeriesChart({
chartID: this.chartID,
seriesIndex: gridIndex,
data: seriesData,
})
} else {
if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
this.$emit('changeAxis', this.metaIndex) // [TODO] elite tmp use metaIndex for grid index
}
}
},
...mapMutations('replay', [types.chart.updateSeries]),
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.round-button {
border-radius: 12px;
}
</style>
@@ -1,19 +1,48 @@
<template>
<div>
<span> {{ axis }} axis: </span>
<div v-if="axis === 'x'" style="display: inline;">
<select v-model="channelFormat" @change="changeAxis()">
<option v-for="option in channelOptions" :key="option.id" :value="option">
{{ option.description }}
</option>
</select>
<div v-if="axis === 'x'">
<va-select
@input="changeAxis()"
:label="''"
v-if="deviceName!=='EliteEIS'"
v-model="channelFormat"
textBy="description"
class="va-select-without-margin"
:options="channelOptions"
:noClear='true'
/>
<va-select
@input="changeAxis()"
:label="''"
v-if="deviceName==='EliteEIS'"
v-model="channelFormat"
textBy="description"
class="va-select-without-margin"
:options="eisChannelOptions"
:noClear='true'
/>
</div>
<div v-if="axis === 'y'" style="display: inline;">
<select v-model="channelFormat" @change="changeAxis()">
<option v-for="option in channelYOptions" :key="option.id" :value="option">
{{ option.description }}
</option>
</select>
<div v-if="axis === 'y'">
<va-select
@input="changeAxis()"
:label="''"
v-if="deviceName!=='EliteEIS'"
v-model="channelFormat"
textBy="description"
class="va-select-without-margin"
:options="channelYOptions"
:noClear='true'
/>
<va-select
@input="changeAxis()"
:label="''"
v-if="deviceName==='EliteEIS'"
v-model="channelFormat"
textBy="description"
class="va-select-without-margin"
:options="eisChannelYOptions"
:noClear='true'
/>
</div>
</div>
</template>
@@ -26,9 +55,6 @@ export default {
components: {
},
props: {
deviceName: {
type: String, // elite or neulive
},
axis: {
type: String, // x or y
},
@@ -49,17 +75,18 @@ export default {
]),
},
methods: {
init () {
if (this.deviceName === 'neulive') {
init (deviceName) {
this.deviceName = deviceName
if (this.deviceName.startsWith('Neulive')) {
// now neulive only v-t
if (this.axis === 'x') {
this.axisXDataType = this.channelOptions[3]
} else if (this.axis === 'y') {
this.axisYDataType = this.channelOptions[1]
}
} else if (this.deviceName === 'elite') {
} else if (this.deviceName === 'EliteZM15') {
// use state meta to decide which default unit need to be used (like check I-V mode...)
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE)
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE, this.deviceName)
const xType = setting[0]
const xValue = setting[1]
const yType = setting[2]
@@ -70,6 +97,53 @@ export default {
settingType = xType
settingValue = xValue
if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.axisXDataType = this.channelOptions[0]
} else if (settingValue === 2) {
this.axisXDataType = this.channelOptions[1]
} else if (settingValue === 3) {
this.axisXDataType = this.channelOptions[2]
} else if (settingValue === 4) {
this.axisXDataType = this.channelOptions[3]
}
} else if (settingType === 'Time') {
this.axisXDataType = this.channelOptions[4]
}
this.channelFormat = this.axisXDataType
} else if (this.axis === 'y') {
settingType = yType
settingValue = yValue
if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.axisYDataType = this.channelOptions[0]
} else if (settingValue === 2) {
this.axisYDataType = this.channelOptions[1]
} else if (settingValue === 3) {
this.axisYDataType = this.channelOptions[2]
} else if (settingValue === 4) {
this.axisXDataType = this.channelOptions[3]
}
} else if (settingType === 'Time') {
this.axisYDataType = this.channelOptions[4]
}
this.channelFormat = this.axisYDataType
}
} else if (this.deviceName === 'EliteEIS') {
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE, this.deviceName)
const xType = setting[0]
const xValue = setting[1]
const yType = setting[2]
const yValue = setting[3]
let settingType
let settingValue
if (this.axis === 'x') {
settingType = xType
settingValue = xValue
if (settingType === 'EliteEIS') {
this.axisXDataType = this.eisChannelOptions.find(element => element.id === parseInt(settingValue) - 1)
} else if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.axisXDataType = this.channelOptions[0]
@@ -85,7 +159,9 @@ export default {
} else if (this.axis === 'y') {
settingType = yType
settingValue = yValue
if (settingType === 'Elite') {
if (settingType === 'EliteEIS') {
this.axisYDataType = this.eisChannelOptions.find(element => element.id === parseInt(settingValue) - 1)
} else if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.axisYDataType = this.channelOptions[0]
@@ -105,23 +181,22 @@ export default {
}
},
changeAxis () {
if (this.deviceName === 'neulive') {
if (this.deviceName.startsWith('Neulive')) {
// for elite now, cuz neulive now only have v-t graph, for future neulive replay need to adjust'
// console.log('(Last updated: 2021/06/19)')
} else if (this.deviceName === 'elite') {
} else if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
if (this.axis === 'x') {
this.axisXDataType = this.channelFormat
} else if (this.axis === 'y') {
this.axisYDataType = this.channelFormat
}
if (this.axisXDataType.description !== 'Time' && this.metaList[this.metaIndex].parameter_set.CYCLE_NUMBER > 0) {
if (this.metaList[this.metaIndex].parameter_set.CYCLE_NUMBER > 0) {
this.showCyclePanel = true
} else {
this.showCyclePanel = false
}
this.$emit('changeAxis', this.metaIndex) // temporarily use metaIndex for gridIndex
this.$emit('changeUnit', this.axis)
this.$emit('resetScale')
this.$emit('clearCycle')
}
},
@@ -130,6 +205,7 @@ export default {
},
data () {
return {
deviceName: '',
channelFormat: {},
channelOptions: [
{
@@ -144,6 +220,10 @@ export default {
id: 2,
description: 'Resistance',
},
{
id: 3,
description: 'Cycle',
},
{
id: 100, // no usage, so assign a large number to avoid conflicting with existing channel data
description: 'Time',
@@ -162,6 +242,74 @@ export default {
id: 2,
description: 'Resistance',
},
{
id: 3,
description: 'Cycle',
},
],
eisChannelOptions: [ // id 0 (Raw Imag), 1 (Raw Real), 3 (Cycle) 不用給user選
{
id: 2,
description: 'Frequency',
},
{
id: 4,
description: 'Z_imag',
},
{
id: 5,
description: 'Z_real',
},
{
id: 6,
description: 'Impedance',
},
{
id: 7,
description: 'Phase',
},
{
id: 8,
description: 'Current',
},
{
id: 9,
description: 'Gain Level',
},
{
id: 100, // no usage, so assign a large number to avoid conflicting with existing channel data
description: 'Time',
},
],
eisChannelYOptions: [ // id 0 (Raw Imag), 1 (Raw Real), 3 (Cycle) 不用給user選
{
id: 2,
description: 'Frequency',
},
{
id: 4,
description: 'Z_imag',
},
{
id: 5,
description: 'Z_real',
},
{
id: 6,
description: 'Impedance',
},
{
id: 7,
description: 'Phase',
},
{
id: 8,
description: 'Current',
},
{
id: 9,
description: 'Gain Level',
},
],
}
},
@@ -170,3 +318,11 @@ export default {
}
</script>
<style lang="scss">
.va-select-without-margin {
.va-select {
margin-bottom: 0;
}
}
</style>
@@ -52,6 +52,7 @@ export default {
computed: {
...mapFields('replay', [
'chartData',
'chartKey',
]),
},
watch: {
@@ -85,7 +86,7 @@ export default {
minVal: this.minMaxInfo[this.gridIndex].min,
maxVal: this.minMaxInfo[this.gridIndex].max,
})
this.$emit('refreshChart', this.chartData[this.chartID])
this.chartKey++
},
...mapMutations('replay', [types.chart.updateAxisValMinMax]),
},
@@ -0,0 +1,58 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0" v-if="useSdCardData">
<p class="display-6"> Chunk Index </p>
<va-input
@keyup.enter="refreshChart()"
v-model="currentChunkDataSplitIndex"
class="mb-0"
:readonly="!isInitDone || isDownloading"
>
<p slot="append" style="margin-right: 0;">
/{{ maxChunkDataSplitNum }}
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'ChangeWindowSize',
components: {
},
computed: {
...mapFields('replay', [
'useSdCardData',
'currentChunkDataSplitIndex',
'maxChunkDataSplitNum',
'isInitDone',
'isDownloading',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
refreshChart () {
this.$emit('refreshChart')
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -34,13 +34,28 @@ export default {
return {
showPanel: false,
metaID: 0,
showWhichCycles: '1',
cycleNum: 0,
cycleDataID: 3, // [TODO] manage by data type factory
}
},
computed: {
slices: function () {
...mapFields('replay', [
'chartData',
'recInfo',
'metaList',
'currentScale',
'sampleMethod',
'axisXDataType',
'axisYDataType',
'axisXLabelScale',
'axisYLabelScale',
'showWhichCycles',
]),
},
watch: {
},
methods: {
slices () {
const cycleList = this.showWhichCycles.split(',')
if (cycleList.length === 1 && cycleList[0] === '') {
return ''
@@ -58,28 +73,13 @@ export default {
}
const cycleSet = new Set(cycleList)
const sortedCycleList = [...cycleSet].sort() // ref: https://stackoverflow.com/questions/33089695/how-can-i-sort-an-es6-set
// console.log(sortedCycleList)
return sortedCycleList
},
...mapFields('replay', [
'chartData',
'recInfo',
'metaList',
'currentScale',
'sampleMethod',
'axisXDataType',
'axisYDataType',
'axisXLabelScale',
'axisYLabelScale',
]),
},
watch: {
},
methods: {
init () {
this.isThisModeHasCycle = this.GLOBAL.hasCycleMode(this.metaList[this.metaIndex].parameter_set.MODE)
this.cycleNum = this.metaList[this.metaIndex].parameter_set.CYCLE_NUMBER
this.metaID = this.metaList[this.metaIndex].id
const meta = this.metaList[this.metaIndex]
this.metaID = meta.id
this.isThisModeHasCycle = this.GLOBAL.hasCycleMode(this.metaList[this.metaIndex].parameter_set.MODE, meta.device.library_name)
if (this.isThisModeHasCycle === true) {
this.showPanel = true
} else {
@@ -103,13 +103,18 @@ export default {
}
}
const cycleDict = this.getCycleDictRec()(cycleDataList)
const dataXList = this.recInfo[this.metaID][this.axisXDataType.id][this.currentScale]
this.cycleNum = Object.keys(cycleDict).length
let dataXList
if (this.axisXDataType.description !== 'Time') {
dataXList = this.recInfo[this.metaID][this.axisXDataType.id][this.currentScale]
}
const dataYList = this.recInfo[this.metaID][this.axisYDataType.id][this.currentScale]
const filteredData = []
let counter = 0
for (let sliceIndex = 0; sliceIndex < this.cycleNum; sliceIndex++) {
// console.log(this.slices)
if (this.slices === '' || this.slices.includes((sliceIndex + 1).toString())) {
if (this.slices() === '' || this.slices().includes((sliceIndex + 1).toString())) {
// slice data
const value = cycleDict[sliceIndex + 1]
const startTime = value[0]
@@ -134,12 +139,12 @@ export default {
let x
let y
if (this.axisXLabelScale.description === 'Log') {
x = Math.log10(Math.abs(dataX[j][1]))
x = Math.abs(dataX[j][1])
} else if (this.axisXLabelScale.description === 'Linear') {
x = dataX[j][1]
}
if (this.axisYLabelScale.description === 'Log') {
y = Math.log10(Math.abs(dataY[j][1]))
y = Math.abs(dataY[j][1])
} else if (this.axisYLabelScale.description === 'Linear') {
y = dataY[j][1]
}
@@ -150,7 +155,7 @@ export default {
for (let j = 0, len = dataY.length; j < len; j++) {
if (parseInt(dataY[j][0]) >= startTime && parseInt(dataY[j][0]) <= endTime) {
if (this.axisYLabelScale.description === 'Log') {
dataY[j][1] = Math.log10(Math.abs(dataY[j][1]))
dataY[j][1] = Math.abs(dataY[j][1])
} else if (this.axisYLabelScale.description === 'Linear') {
// do nothing
}
@@ -165,7 +170,13 @@ export default {
seriesIndex: this.metaIndex,
data: filteredData,
})
// this.$emit('refreshChart', this.chartData[this.chartID])
if (this.axisXDataType.description === 'Time') {
if (counter === 0) {
this.chartData[this.chartID].xAxis[this.metaIndex].min = startTime
}
this.chartData[this.chartID].xAxis[this.metaIndex].max = endTime
counter++
}
}
}
}
@@ -1,15 +1,19 @@
<template>
<div>
<select v-model="scaleFormat" @change="changeScale()">
<option v-for="option in scaleOptions" :key="option.id" :value="option">
{{ option.description }}
</option>
</select>
<va-select
@input="changeScale()"
:label="''"
v-model="scaleFormat"
textBy="description"
class="va-select-without-margin"
:options="scaleOptions"
:noClear='true'
/>
</div>
</template>
<script>
import { mapMutations, mapGetters } from 'vuex'
import { mapActions, mapMutations, mapGetters } from 'vuex'
import { mapFields } from 'vuex-map-fields'
import types from '@/store/modules/analysis/replay/types'
import { download } from '@/data/download/Download'
@@ -19,9 +23,6 @@ export default {
components: {
},
props: {
deviceName: {
type: String, // elite or neulive
},
chartID: {
type: Number, // detail data
default: 1,
@@ -37,6 +38,13 @@ export default {
created () {
},
computed: {
displayType () {
return {
x: this.axisXLabelScale.description === 'Log' ? 'log' : 'value',
y: this.axisYLabelScale.description === 'Log' ? 'log' : 'value',
}
},
...mapFields('replay', [
'chartData',
'recInfo',
@@ -45,6 +53,7 @@ export default {
'axisYLabelScale',
'axisXDataType',
'axisYDataType',
'absToggleStatus',
'startTimeCurrent',
'endTimeCurrent',
'currentScale',
@@ -52,7 +61,8 @@ export default {
]),
},
methods: {
init () {
init (deviceName) {
this.deviceName = deviceName
this.metaInfo = this.metaList[this.metaIndex]
this.metaID = this.metaInfo.id
if (this.axis === 'x') {
@@ -69,7 +79,7 @@ export default {
} else if (this.axis === 'y') {
this.axisYLabelScale = this.scaleFormat
}
if (this.deviceName === 'neulive') {
if (this.deviceName.startsWith('Neulive')) {
const keys = Object.keys(this.metaInfo.mini_data)
for (let gridIndex = 0; gridIndex < keys.length; gridIndex++) {
const ch = keys[gridIndex]
@@ -77,7 +87,7 @@ export default {
this.calculateScaleHelper(gridIndex, ch)
}
}
} else if (this.deviceName === 'elite') {
} else if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
this.calculateScaleHelper(this.metaIndex, -1) // [TODO] elite tmp use metaIndex for grid index
}
},
@@ -87,14 +97,19 @@ export default {
seriesData = JSON.parse(JSON.stringify(this.chartData[this.chartID].series[gridIndex].data))
for (let i = 0; i < seriesData.length; i++) {
if (this.axis === 'x') {
seriesData[i][0] = Math.log10(Math.abs(seriesData[i][0]))
seriesData[i][0] = Math.abs(seriesData[i][0])
} else if (this.axis === 'y') {
seriesData[i][1] = Math.log10(Math.abs(seriesData[i][1]))
seriesData[i][1] = Math.abs(seriesData[i][1])
}
}
this.updateSeriesChart({
chartID: this.chartID,
seriesIndex: gridIndex,
data: seriesData,
})
} else if (this.scaleFormat.description === 'Linear') {
// console.log(this.recInfo)
if (this.deviceName === 'neulive') {
if (this.deviceName.startsWith('Neulive')) {
// [Region] get the subfile index
seriesData = []
const dataYList = this.recInfo[this.metaID][ch][this.currentScale]
@@ -114,94 +129,22 @@ export default {
for (let j = 0, len = data.length; j < len; j++) {
if (parseInt(data[j][0]) >= this.startTimeCurrent && parseInt(data[j][0]) <= this.endTimeCurrent) {
const processedData = data[j]
if (this.axisXLabelScale.description === 'Log') {
processedData[0] = Math.log10(Math.abs(processedData[0]))
if (this.displayType.x === 'log' || this.absToggleStatus.x === true) {
processedData[0] = Math.abs(processedData[0])
}
if (this.axisYLabelScale.description === 'Log') {
processedData[1] = Math.log10(Math.abs(processedData[1]))
}
seriesData.push(processedData)
}
}
}
} else if (this.deviceName === 'elite') {
seriesData = []
const cycleNum = this.metaInfo.parameter_set.CYCLE_NUMBER
let startTime
let endTime
if (cycleNum > 0 && this.axisXDataType.description !== 'Time') { // if有cycle的mode且目前x軸不為時間,抓出第一圈所在的時間段,初始化時先畫第一圈就好,加快初始時間
const cycleDataID = 3 // recorded cycle 所在的channel index
const dataCycleList = this.recInfo[this.metaID][cycleDataID][this.currentScale]
const cycleDataList = []
for (let i = 0; i < dataCycleList.length; i++) {
if (dataCycleList[i].data.length === 0) {
const data = await this.getData(parseInt(this.currentScale), this.sampleMethod, dataCycleList[i].id, cycleDataID, this.metaInfo)
cycleDataList.push({ data: data })
} else {
cycleDataList.push({ data: dataCycleList[i].data })
}
}
const cycleDict = this.getCycleDictRec()(cycleDataList)
startTime = cycleDict[1][0] // [1]: 第一圈
endTime = cycleDict[1][1] // [1]: 第一圈
this.$emit('clearCycle')
} else {
startTime = this.startTimeCurrent
endTime = this.endTimeCurrent
}
const dataYList = this.recInfo[this.metaID][this.axisYDataType.id][this.currentScale]
let startIndex = 0
let endIndex = 0
for (let i = 0; i < dataYList.length; i++) {
if (startTime >= parseInt(dataYList[i].start_time) && startTime <= parseInt(dataYList[i].end_time)) {
startIndex = i
}
if (endTime >= parseInt(dataYList[i].start_time) && endTime <= parseInt(dataYList[i].end_time)) {
endIndex = i
}
}
// [EndRegion]
for (let i = startIndex; i <= endIndex; i++) {
const dataY = await this.getData(parseInt(this.currentScale), this.sampleMethod, dataYList[i].id, this.axisYDataType.id, this.metaInfo)
let dataX
if (this.axisXDataType.description !== 'Time') {
const dataXList = this.recInfo[this.metaID][this.axisXDataType.id][this.currentScale]
dataX = await this.getData(parseInt(this.currentScale), this.sampleMethod, dataXList[i].id, this.axisXDataType.id, this.metaInfo)
}
for (let j = 0, len = dataY.length; j < len; j++) {
if (parseInt(dataY[j][0]) >= startTime && parseInt(dataY[j][0]) <= endTime) {
const processedData = dataY[j]
if (this.axisXDataType.description !== 'Time') {
processedData[0] = dataX[j][1]
}
if (this.axisXLabelScale.description === 'Log') {
processedData[0] = Math.log10(Math.abs(processedData[0]))
}
if (this.axisYLabelScale.description === 'Log') {
processedData[1] = Math.log10(Math.abs(processedData[1]))
if (this.displayType.y === 'log' || this.absToggleStatus.y === true) {
processedData[1] = Math.abs(processedData[1])
}
seriesData.push(processedData)
}
}
}
} else if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
this.$emit('changeAxis', this.metaIndex)
this.$emit('clearCycle')
}
}
this.updateSeriesChart({
chartID: this.chartID,
seriesIndex: gridIndex,
data: seriesData,
})
},
reset () {
if (this.axis === 'x') {
this.axisXLabelScale = this.scaleOptions[0] // 0 mean use default unit
this.scaleFormat = this.axisXLabelScale
} else if (this.axis === 'y') {
this.axisYLabelScale = this.scaleOptions[0] // 0 mean use default unit
this.scaleFormat = this.axisYLabelScale
}
this.setAxisDisplayChart({ type: this.displayType[this.axis], axis: this.axis, chartID: this.chartID, gridIndex: gridIndex })
},
getData: async function (scale, sampleMethod, id, channel, meta) {
if (scale === 1) {
@@ -214,6 +157,7 @@ export default {
}
}
},
...mapActions('replay', [types.chart.setAxisDisplay]),
...mapMutations('replay', [types.chart.updateSeries]),
...mapGetters('replay', [types.rec.getCycleDict]),
},
@@ -221,6 +165,7 @@ export default {
},
data () {
return {
deviceName: '',
metaID: 0,
metaInfo: null,
scaleFormat: { id: 0, description: 'Linear' },
@@ -241,3 +186,11 @@ export default {
}
</script>
<style lang="scss">
.va-select-without-margin {
.va-select {
margin-bottom: 0;
}
}
</style>
@@ -1,10 +1,30 @@
<template>
<div>
<select v-model="valueFormat" @change="changeUnit()">
<option v-for="option in valueFormatOptions[dataType]" :key="option.id" :value="option">
{{ option.unit }}
</option>
</select>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm12 xl12 md12 xs12 px-0 mx-0">
<p class="display-6"> UNIT </p>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName!=='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="valueFormatOptions[dataType]"
:noClear='true'
:disabled="!isInitDone || isDownloading"
/>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName==='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="eisValueFormatOptions[dataType]"
:noClear='true'
:disabled="!isInitDone || isDownloading"
/>
</div>
</div>
</template>
@@ -18,9 +38,6 @@ export default {
components: {
},
props: {
deviceName: {
type: String, // elite or neulive
},
axis: {
type: String, // x or y
},
@@ -34,25 +51,46 @@ export default {
computed: {
...mapFields('replay', [
'chartData',
'chartKey',
'metaList',
'valueFormatX',
'valueFormatY',
'axisXDataType',
'axisYDataType',
'isInitDone',
'isDownloading',
]),
},
methods: {
init () {
if (this.deviceName === 'neulive') {
initDefaultUnit (axis) {
if (this.deviceName.startsWith('Neulive')) {
if (axis === 'x') {
return this.valueFormatOptions[this.dataType].find(function (item, index, array) {
return item.unit === 'auto'
})
} else if (axis === 'y') {
return this.valueFormatOptions[this.dataType].find(function (item, index, array) {
return item.unit === 'uV'
})
}
} else if (this.deviceName === 'EliteZM15') {
return this.valueFormatOptions[this.dataType][0]
} else if (this.deviceName === 'EliteEIS') {
return this.eisValueFormatOptions[this.dataType][0]
}
},
init (deviceName) {
this.deviceName = deviceName
if (this.deviceName.startsWith('Neulive')) {
// now neulive only v-t
if (this.axis === 'x') {
this.dataType = 'time'
} else if (this.axis === 'y') {
this.dataType = 'voltage'
}
} else if (this.deviceName === 'elite') {
} else if (this.deviceName === 'EliteZM15') {
// use state meta to decide which default unit need to be used (like check I-V mode...)
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE)
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE, this.deviceName)
const xType = setting[0]
const xValue = setting[1]
const yType = setting[2]
@@ -68,6 +106,48 @@ export default {
}
if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.dataType = 'current'
} else if (settingValue === 2) {
this.dataType = 'voltage'
} else if (settingValue === 3) {
this.dataType = 'resistance'
} else if (settingValue === 4) {
this.dataType = 'cycle'
}
} else if (settingType === 'Time') {
this.dataType = 'time'
}
} else if (this.deviceName === 'EliteEIS') {
const setting = this.GLOBAL.getDefaultChartTypeByMode(this.metaList[this.metaIndex].parameter_set.MODE, this.deviceName)
const xType = setting[0]
const xValue = setting[1]
const yType = setting[2]
const yValue = setting[3]
let settingType
let settingValue
if (this.axis === 'x') {
settingType = xType
settingValue = xValue
} else if (this.axis === 'y') {
settingType = yType
settingValue = yValue
}
if (settingType === 'EliteEIS') {
if (settingValue === 3) {
this.dataType = 'frequency'
} else if (settingValue === 5 || settingValue === 6 || settingValue === 7) {
this.dataType = 'impedance'
} else if (settingValue === 8) {
this.dataType = 'phase'
} else if (settingValue === 9) {
this.dataType = 'current'
} else if (settingValue === 10) {
this.dataType = 'default'
}
} else if (settingType === 'Elite') {
// 1: I, 2: V, 3: R
if (settingValue === 1) {
this.dataType = 'current'
@@ -81,10 +161,10 @@ export default {
}
}
if (this.axis === 'x') {
this.valueFormatX = this.valueFormatOptions[this.dataType][0]
this.valueFormatX = this.initDefaultUnit(this.axis)
this.valueFormat = this.valueFormatX
} else if (this.axis === 'y') {
this.valueFormatY = this.valueFormatOptions[this.dataType][0] // 0 mean use default unit
this.valueFormatY = this.initDefaultUnit(this.axis)
this.valueFormat = this.valueFormatY
}
this.changeUnit()
@@ -96,7 +176,7 @@ export default {
this.valueFormatY = this.valueFormat
}
const chartID = 1 // detail data
if (this.deviceName === 'neulive') {
if (this.deviceName.startsWith('Neulive')) {
const keys = Object.keys(this.metaList[this.metaIndex].mini_data)
for (let axisIndex = 0; axisIndex < keys.length; axisIndex++) {
const key = keys[axisIndex]
@@ -107,7 +187,7 @@ export default {
channel: parseInt(key),
})
}
} else if (this.deviceName === 'elite') {
} else if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
this.updateAxisLabelChart({
chartID: chartID,
axis: this.axis,
@@ -115,7 +195,6 @@ export default {
channel: -1, // any num < 256, cuz channel only use for determining whether add unit text
})
}
this.$emit('refreshChart', this.chartData[chartID])
},
changeUnitByAxis () {
if (this.axis === 'x') {
@@ -127,6 +206,16 @@ export default {
this.dataType = 'resistance'
} else if (this.axisXDataType.description === 'Time') {
this.dataType = 'time'
} else if (this.axisXDataType.description === 'Cycle') {
this.dataType = 'cycle'
} else if (this.axisXDataType.description === 'Frequency') {
this.dataType = 'frequency'
} else if (this.axisXDataType.description === 'Z_imag' || this.axisXDataType.description === 'Z_real' || this.axisXDataType.description === 'Impedance') {
this.dataType = 'impedance'
} else if (this.axisXDataType.description === 'Phase') {
this.dataType = 'phase'
} else if (this.axisXDataType.description === 'Gain Level') {
this.dataType = 'default'
}
} else if (this.axis === 'y') {
if (this.axisYDataType.description === 'Current') {
@@ -135,9 +224,23 @@ export default {
this.dataType = 'voltage'
} else if (this.axisYDataType.description === 'Resistance') {
this.dataType = 'resistance'
} else if (this.axisYDataType.description === 'Cycle') {
this.dataType = 'cycle'
} else if (this.axisYDataType.description === 'Frequency') {
this.dataType = 'frequency'
} else if (this.axisYDataType.description === 'Z_imag' || this.axisYDataType.description === 'Z_real' || this.axisYDataType.description === 'Impedance') {
this.dataType = 'impedance'
} else if (this.axisYDataType.description === 'Phase') {
this.dataType = 'phase'
} else if (this.axisYDataType.description === 'Gain Level') {
this.dataType = 'default'
}
}
this.valueFormat = this.valueFormatOptions[this.dataType][0]
if (this.deviceName === 'EliteZM15') {
this.valueFormat = this.valueFormatOptions[this.dataType][0]
} else if (this.deviceName === 'EliteEIS') {
this.valueFormat = this.eisValueFormatOptions[this.dataType][0]
}
this.changeUnit()
},
// call vuex to mutate chart label formatter by unit and axis
@@ -147,6 +250,7 @@ export default {
},
data () {
return {
deviceName: '',
dataType: '',
valueFormat: {},
valueFormatOptions: { // below id: 0 mean default
@@ -206,6 +310,13 @@ export default {
unitScale: 1000000000,
},
],
cycle: [ // Time
{
id: 0,
unit: 'cycle',
unitScale: 1,
},
],
time: [ // Time
{
id: 0,
@@ -214,6 +325,85 @@ export default {
},
],
},
eisValueFormatOptions: { // below id: 0 mean default
default: [
{
id: 0,
unit: 'default',
unitScale: 1,
},
],
current: [
{
id: 0,
unit: 'nA',
unitScale: 1,
},
{
id: 1,
unit: 'uA',
unitScale: 1e3,
},
{
id: 2,
unit: 'mA',
unitScale: 1e6,
},
],
frequency: [ // Frequency
{
id: 0,
unit: 'mHz',
unitScale: 1,
},
{
id: 1,
unit: 'Hz',
unitScale: 1e3,
},
{
id: 2,
unit: 'kHz',
unitScale: 1e6,
},
],
impedance: [ // Impedance
{
id: 1,
unit: 'Ω',
unitScale: 1,
},
{
id: 0,
unit: 'mΩ',
unitScale: 1e-3,
},
{
id: 2,
unit: 'kΩ',
unitScale: 1e3,
},
{
id: 3,
unit: 'MΩ',
unitScale: 1e6,
},
],
time: [ // Time
{
id: 0,
unit: 'auto',
unitScale: 1,
},
],
phase: [ // Phase
{
id: 0,
unit: '°',
unitScale: 1,
},
],
},
}
},
destroyed () {
@@ -221,3 +411,11 @@ export default {
}
</script>
<style lang="scss">
.va-select-without-margin {
.va-select {
margin-bottom: 0;
}
}
</style>
@@ -0,0 +1,60 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm4 xl4 md4 xs4 px-0 mx-0">
<p class="display-6">Window size<p/>
</div>
<div class="flex sm8 xl8 md8 xs8 px-0 mx-0">
<va-input
@keyup.enter="itemChange(windowSize)"
@blur="itemChange(windowSize)"
v-model="windowSize"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'ChangeWindowSize',
components: {
},
computed: {
...mapFields('replay', [
'startTimeCurrent',
'endTimeCurrent',
'windowSize',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.windowSize = parseInt(val)
// this.endTimeCurrent = this.startTimeCurrent + parseInt(val)
// this.$emit('reDraw')
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,121 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> WINDOW START <p/>
<va-input
@keyup.enter="startItemChange(startTimeInput)"
v-model="startTimeInput"
:readonly="useSdCardData || !isInitDone || isDownloading"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> WINDOW SIZE <p/>
<va-input
@keyup.enter="lenItemChange(windowLengthInput)"
v-model="windowLengthInput"
:readonly="useSdCardData || !isInitDone || isDownloading"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'change-window-start-and-size',
components: {
},
props: {
},
computed: {
startTimeInput: {
get () {
return this.startTime / 1e3
},
set (val) {
this.startTime = val * 1e3
this.endTime = this.startTime + this.windowLength
},
},
windowLengthInput: {
get () {
return this.windowLength / 1e3
},
set (val) {
this.windowLength = val * 1e3
this.endTime = this.startTime + this.windowLength
},
},
...mapFields('replay', [
'startTime',
'endTime',
'windowLength',
'useSdCardData',
'isInitDone',
'isDownloading',
]),
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
init () {
this.windowLength = parseInt(this.endTime) - parseInt(this.startTime)
},
startItemChange (val) {
this.startTime = parseInt(val * 1e3) // us
this.windowLength = parseInt(this.windowLength)
this.endTime = this.startTime + this.windowLength
this.$emit('changeOverviewDataZoom')
},
lenItemChange (val) {
this.startTime = parseInt(this.startTime)
this.windowLength = parseInt(val * 1e3) // us
this.endTime = this.startTime + this.windowLength
this.$emit('changeOverviewDataZoom')
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
</style>
@@ -1,6 +1,6 @@
<template>
<div>
<va-button flat small class="resize-va-button" color="gray" icon="fa fa-trash" @click.stop="deleteFileConfirm()"/>
<va-button flat small color="gray" icon="fa fa-trash" @click.stop="deleteFileConfirm()"/>
<va-modal
v-model="showModal.deleteFile"
size="small"
@@ -90,15 +90,3 @@ export default {
}
</script>
<style lang="scss">
.resize-va-button {
.va-button {
margin: 0.375rem 0.5rem 0.375rem 0.1px;
}
.va-button--small {
padding: 0.1px;
}
}
</style>
@@ -1,12 +1,18 @@
<template>
<div>
<va-button flat small class="resize-va-button" color="gray" icon="fa fa-download" @click.stop="downloadCSV()"/>
<va-popover :message="`SD card data`" placement="top">
<div v-if="metaList[0]">
<va-button v-if="metaList[0].sd_data_uploaded" flat small color="primary" icon="fa fa-download" @click.stop="downloadSdCard()"/>
</div>
</va-popover>
<va-popover :message="`BLE data`" placement="top">
<va-button flat small color="success" icon="fa fa-download" @click.stop="downloadCsv()"/>
</va-popover>
</div>
</template>
<script>
// import { download } from '@/data/download/Download'
// import { exportData } from '@/data/export/export'
import api from '@/data/api/index'
import { mapFields } from 'vuex-map-fields'
export default {
@@ -29,13 +35,24 @@ export default {
watch: {
},
methods: {
downloadCSV: async function () {
for (let i = 0; i < this.metaList.length; i++) {
const meta = this.metaList[i]
window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/download/neulive/csv/' + meta.id + '/raw/true', 'raw')
if (JSON.parse(meta.channels).some(el => el >= 256)) {
window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/download/neulive/csv/' + meta.id + '/acc/true', 'acc')
}
async downloadCsv () {
const href = location.href.split('/')[2]
const meta = this.metaList[0]
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id)
}
},
async downloadSdCard () {
const href = location.href.split('/')[2]
const meta = this.metaList[0]
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id + '/sd')
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id + '/sd')
}
},
},
@@ -46,15 +63,3 @@ export default {
}
</script>
<style lang="scss">
.resize-va-button {
.va-button {
margin: 0.375rem 0.5rem 0.375rem 0.1px;
}
.va-button--small {
padding: 0.1px;
}
}
</style>
@@ -1,6 +1,7 @@
<template>
<div>
<va-button flat small class="resize-va-button" color="gray" icon="fa fa-file-text-o" @click.stop="editFileConfirm()"> {{ filename }} </va-button>
<span class="display-6"> {{ filename }} </span>
<va-button flat small color="gray" icon="fa fa-pencil" @click.stop="editFileConfirm()" />
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showModal.editFile"
@@ -91,6 +92,7 @@ export default {
editFile: async function () {
if (this.editInfo.success === true) {
await api.meta.update(this.editInfo.info.id, { name: this.editInfo.changedName })
await api.meta.update30(this.editInfo.info.uuid, { name: this.editInfo.changedName })
this.editInfo.name = this.editInfo.changedName
this.filename = this.editInfo.changedName
}
@@ -139,15 +141,3 @@ export default {
}
</script>
<style lang="scss">
.resize-va-button {
.va-button {
margin: 0.375rem 0.5rem 0.375rem 0.1px;
}
.va-button--small {
padding: 0.1px;
}
}
</style>
@@ -0,0 +1,135 @@
<template>
<div>
<span class="box_inline-block display-6"> RUN FILTER </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useFilter"
@input="refreshChart()"
:disable="!isInitDone || isDownloading"
/>
<div class="row mt-2" v-if="initDone">
<div v-for="(filter, filterIndex) in filterList" :key="filter.id" :index="filterIndex">
<global-filter :ref="'filter_ref'" :chartID="chartID" :filterIndex="filterIndex" />
</div>
</div>
<div style="text-align: center;">
<va-button color="success" small :disabled="useFilter" @click="openAllFilter()">
Activate All Filters
</va-button>
<va-button color="danger" small :disabled="useFilter" @click="closeAllFilter()">
Deactivate All Filters
</va-button>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import GlobalFilter from '@/components/analysis/replay/toolbox/GlobalFilter.vue'
export default {
name: 'FilterToggle',
components: {
GlobalFilter,
},
computed: {
...mapFields('replay', [
'chartData',
'useFilter',
'isInitDone',
'isDownloading',
]),
},
props: {
},
created () {
},
data () {
return {
initDone: false,
filterList: [],
chartID: 1,
maxFilterNum: 3,
}
},
mqtt: {
},
methods: {
init () {
this.useFilter = false
this.initDone = true
},
getFilterStatus () {
let isAllClose = true
let isReady = true
this.chartData[this.chartID].series.forEach(_series => {
if (_series.filter.some(_filter => _filter.filter !== null)) {
isAllClose = false
}
})
if (this.$refs.filter_ref.some(_ref => _ref.isLoading === true)) {
isReady = false
}
return { isAllClose: isAllClose, isReady: isReady }
},
refreshChart () {
const { isAllClose, isReady } = this.getFilterStatus()
if (!isAllClose && isReady) {
this.$emit('refreshChart')
} else {
this.useFilter = false
this.pageToast('Please open either filter or wait for filter loading done')
}
},
addFilter () {
let availableID
for (let i = 0; i < this.maxFilterNum; i++) {
if (this.filterList.some(block => block.id === i) === false) { // ref: https://www.tutorialrepublic.com/faq/how-to-check-if-an-array-includes-an-object-in-javascript.php
availableID = i
break
}
}
this.filterList.push({ id: availableID })
},
async openAllFilter () {
this.closeAllFilter()
for (let i = 0; i < this.maxFilterNum; i++) {
await this.$refs.filter_ref[i].updateFilterInfo(i)
}
},
closeAllFilter () {
this.chartData[this.chartID].series.forEach(_series => {
_series.filter.forEach(_filter => {
_filter.filter = null
})
})
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
},
mounted () {
for (let i = 0; i < this.maxFilterNum; i++) {
this.addFilter()
}
},
}
</script>
<style lang="scss">
.box_inline-block {
padding: 1px;
margin: 1px;
display: inline-block;
}
</style>
@@ -0,0 +1,249 @@
<template>
<div class="row pl-2 ma-0">
<va-checkbox
v-if="!isLoading"
:disabled="useFilter"
:key="filterKey"
:value="isActive"
@input="updateFilterInfo(filterIndex)"
/>
<fulfilling-bouncing-circle-spinner
v-if="isLoading"
:animation-duration="3000"
:color="'#6c7fee'"
:size="20"
class="mx-2 mt-3"
/>
<div class="filter-input my-0 pa-0 mx-1">
<va-select
small
noClear
:label="'FILTER ' + (filterIndex + 1)"
:disabled="useFilter"
v-model="selectFilter"
textBy="name"
:options="optionFilter"
@input="clearThenReload()"
/>
</div>
<va-input
:label="'order'"
:disabled="useFilter"
class="mx-1 order-input"
:class="inputDisabledStyle"
v-model.lazy="order"
@change="clearThenReload()"
:error="showError"
/>
<va-input
v-if="[1, 2, 3].indexOf(selectFilter.id) >= 0"
:label="'FREQ'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq2"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MIN'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq0"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MAX'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq1"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
</div>
</template>
<script>
import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'global-filter',
props: {
chartID: {
type: Number,
required: true,
},
filterIndex: {
type: Number,
required: true,
},
},
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('replay', [
'chartData',
'metaList',
'useFilter',
'currentScale',
]),
isActive () {
if (this.data && this.data.series.length > 0 && this.data.series.every(item => item != null)) {
return !!this.data.series.every(item => item.filter[this.filterIndex].filter != null)
}
return false
},
inputDisabledStyle () {
return this.useFilter ? 'va-select--disabled' : ''
},
},
data () {
return {
filterKey: 0,
data: null,
selectFilter: {
id: 1,
name: 'notch',
},
order: 20,
freq0: 60,
freq1: 60,
freq2: 60,
optionFilter: [
{
id: 1,
name: 'notch',
},
{
id: 2,
name: 'lowpass',
},
{
id: 3,
name: 'highpass',
},
{
id: 4,
name: 'bandstop',
},
{
id: 5,
name: 'bandpass',
},
],
showError: false,
isLoading: false,
}
},
methods: {
clearFilter () {
for (let i = 0; i < this.data.series.length; i++) {
if (this.data.series[i].filter[this.filterIndex].filter != null) {
this.data.series[i].filter[this.filterIndex].filter = null
}
}
},
clearThenReload () {
this.clearFilter()
this.updateFilterInfo(this.filterIndex)
},
async updateFilterInfo (index) {
if (this.isActive) {
this.clearFilter()
return
}
this.isLoading = true
let fs = 0
let sos = []
for (let i = 0; i < this.data.series.length; i++) {
const _fs = this.metaList[0].parameter_set.SAMPLE_RATE / parseInt(this.currentScale) // currentScale: for the case 10, 100, 1000:1
const _chNum = Object.keys(this.metaList[0].raw_data).length
if (fs !== _fs) {
fs = _fs
const ret = await api.analysis.IIRFilter({
type: this.selectFilter.name,
order: this.order,
fs: parseFloat(_fs / _chNum),
f0: parseFloat(this.freq0),
f1: parseFloat(this.freq1),
f2: parseFloat(this.freq2),
})
if (ret.status !== 200) {
this.showError = true
this.isLoading = false
return false
} else {
this.showError = false
}
const _sosRet = ret.data.split('/')
const _sos = []
_sosRet.forEach((v, vi) => {
const _tmp = v.split(',')
const _arr = []
_tmp.forEach(s => {
_arr.push(parseFloat(s))
})
_sos.push(_arr)
})
sos = _sos
}
this.data.series[i].filter[index].type = this.selectFilter.name
this.data.series[i].filter[index].order = this.order
// sampling rate changed by channel number
this.data.series[i].filter[index].fs = parseFloat(_fs / _chNum)
this.data.series[i].filter[index].f0 = parseFloat(this.freq0)
this.data.series[i].filter[index].f1 = parseFloat(this.freq1)
this.data.series[i].filter[index].f2 = parseFloat(this.freq2)
this.data.series[i].filter[index].sos = sos
this.data.series[i].filter[index].filter = IIRFilter.init(sos)
}
this.filterKey++
this.isLoading = false
},
},
mounted () {
this.data = this.chartData[this.chartID]
},
}
</script>
<style lang='scss'>
.filter-input {
max-width: 100px;
}
.freq-input {
max-width: 100px;
}
.order-input {
max-width: 50px;
}
.filter-apply-button {
max-width: 25px;
}
</style>
@@ -0,0 +1,60 @@
<template>
<div>
<va-button :icon="icon" @click="lockDataZoom"/>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/replay/types'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'lock-data-zoom',
components: {
},
props: {
chartID: {
type: Number, // detail data
default: 0,
},
},
created () {
},
data () {
return {
icon: 'fa fa-unlock',
}
},
computed: {
...mapFields('replay', [
'chartData',
'isLockDataZoom',
]),
},
watch: {
},
methods: {
lockDataZoom () {
this.isLockDataZoom = !this.isLockDataZoom
this.lockDataZoomChart({
chartAllData: this.chartData,
chartID: 0,
zoomLock: this.isLockDataZoom,
})
if (this.isLockDataZoom === true) {
this.icon = 'fa fa-lock'
} else if (this.isLockDataZoom === false) {
this.icon = 'fa fa-unlock'
}
},
...mapMutations('replay', [types.chart.lockDataZoom]),
},
mounted () {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,84 @@
<template>
<div v-if="showToggle">
<span class="box_inline-block display-6"> USE SD CARD DATA </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useSdCardData"
@input="refreshChart()"
:disable="!isInitDone || isDownloading"
/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'SdCardToggle',
components: {
},
computed: {
...mapFields('replay', [
'metaList',
'useSdCardData',
'currentChunkDataSplitIndex',
'startTime',
'endTime',
'startTimeCurrent',
'endTimeCurrent',
'isInitDone',
'isDownloading',
]),
},
props: {
metaIndex: {
type: Number,
default: 0,
},
},
created () {
},
data () {
return {
showToggle: false,
}
},
mqtt: {
},
methods: {
init () {
this.useSdCardData = false
if (this.metaList[this.metaIndex].sd_data_uploaded) {
this.showToggle = true
}
},
refreshChart () {
this.startTime = this.startTimeCurrent = 0
this.currentChunkDataSplitIndex = 1
if (!this.useSdCardData) {
this.endTime = this.endTimeCurrent = this.metaList[this.metaIndex].time_duration
this.$emit('redrawAllChart')
} else {
this.$emit('refreshChart')
}
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.box_inline-block {
padding: 1px;
margin: 1px;
display: inline-block;
}
</style>
@@ -1,22 +1,20 @@
<template>
<va-tree-root>
<va-tree-category label="Experiment Setting" :isOpen="false">
<pre><div style="text-transform: none;" class="title mb-3" :style="{color: this.$themes.primary}" v-html="content"></div></pre>
</va-tree-category>
</va-tree-root>
<div>
<span class="display-6"> EXPERIMENT SETTING </span>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="isOpen = !isOpen"/>
<pre><div v-if="isOpen" class="mb-3" :style="{color: this.$themes.dark}" v-html="content"></div></pre>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import paramTable from '@/data/param-table/index.js'
export default {
name: 'show-parameter',
components: {
},
props: {
deviceName: {
type: String, // elite or neulive
},
metaIndex: {
type: Number, // which meta file
default: 0,
@@ -26,241 +24,16 @@ export default {
},
data () {
return {
isOpen: false,
deviceName: '',
content: '',
// [region] for neulive
ampGainOptions: [
{
id: 0,
description: '25',
},
{
id: 1,
description: '100',
},
{
id: 2,
description: '400',
},
{
id: 3,
description: '800',
},
],
neuliveSampleRateOptions: [
{
id: 0,
value: 800,
description: '1k',
},
{
id: 1,
value: 400,
description: '2k',
},
{
id: 2,
value: 200,
description: '4k',
},
{
id: 3,
value: 160,
description: '5k',
},
{
id: 4,
value: 100,
description: '8k',
},
{
id: 5,
value: 80,
description: '10k',
},
],
stimulateElectrodeOptions: [
{
label: 'Monopolar',
value: 0,
},
{
label: 'Bipolar',
value: 1,
},
],
cycleNumberOptions: [
{
label: 'limite',
value: 0,
},
{
label: 'Unlimite',
value: 1,
},
],
patternModeOptions: [
{
label: 'Monophasic',
value: 0,
},
{
label: 'Biphasic',
value: 1,
},
],
unitOptions: [
{
label: 's',
value: 0,
},
{
label: 'ms',
value: 1,
},
{
label: 'us',
value: 2,
},
],
// [Endregion]
// [region] for elite
workingModeOptions: [
{
id: 0,
description: 'I-V Curve',
},
{
id: 1,
description: 'Cycle I-V',
},
{
id: 2,
description: 'Function Generator',
},
{
id: 3,
description: 'R-T Graph',
},
{
id: 4,
description: 'V-T Graph',
},
{
id: 5,
description: 'I-T Graph',
},
{
id: 6,
description: 'ADC_Test',
},
{
id: 7,
description: 'Constant Current',
},
{
id: 8,
description: 'Cyclic Voltammetry',
},
{
id: 9,
description: 'Cyclic Voltammetry-hi-cycles',
},
{
id: 10,
description: 'Linear Sweep Voltammetry',
},
{
id: 11,
description: 'Chronoamperometric Graph',
},
],
currentRangeOptions: [
{
value: 0,
label: '<40 μA',
},
{
value: 1,
label: '30-1350 μA',
},
{
value: 2,
label: '>1000 μA',
},
{
value: 3,
label: 'Auto',
},
],
stepTimeOptions: [
{
value: 0,
label: '0.5s',
},
{
value: 1,
label: '1.0s',
},
{
value: 2,
label: '2.0s',
},
],
resisterRangeOptions: [
{
value: 0,
label: '>50 kΩ',
},
{
value: 1,
label: '2-100 kΩ',
},
{
value: 2,
label: '<10 kΩ',
},
{
value: 3,
label: 'Auto',
},
],
eliteSampleRateOptions: [
{
value: 0,
label: '100',
},
{
value: 1,
label: '10',
},
{
value: 2,
label: '5',
},
{
value: 3,
label: '1',
},
{
value: 4,
label: '0.1',
},
{
value: 5,
label: '0.01',
},
],
chargeOptions: [
{
value: 0,
label: 'Charge',
},
{
value: 1,
label: 'Discharge',
},
],
// [Endregion]
params: {},
showDict: {},
chipChannelDict: {}, // for 3.5ex
channelList: [], // total selected channels
sampleRateList: [], // each chip per channel sample rate
preprocessKey: [],
ignoreKey: [],
}
},
computed: {
@@ -271,244 +44,105 @@ export default {
watch: {
},
methods: {
init () {
init (_deviceName) {
this.deviceName = _deviceName.replace(/[.]/g, '') // remove all '.'
const meta = this.metaList[this.metaIndex]
const params = meta.parameter_set
this.content += 'Duration: ' + parseInt(parseInt(meta.time_duration / 1000000) / 60) + ' m ' + parseInt((meta.time_duration % 60000000) / 1000000) + ' s<br/>'
this.content += '<br/>Parameter: <br/>'
this.content += '<br/> Recording: <br/>'
if (this.deviceName === 'neulive') {
this.params = meta.parameter_set
this.content += 'DURATION: ' + parseInt(parseInt(meta.time_duration / 1e6) / 60) + ' m ' + parseInt((meta.time_duration % 6e7) / 1e6) + ' s<br/>'
if (this.deviceName.startsWith('Neulive')) {
const recordingParam = paramTable[this.deviceName].showParameterRec
const stimulationParam = paramTable[this.deviceName].showParameterSti
// recording param
for (const [key, value] of Object.entries(params)) {
if (!(key.includes('sti') || key.includes('STI'))) {
let showKey = ''
let showValue = value
if (key === 'RECORDING_CH' || key === 'AXIS_CH' || key === 'AMP_GAIN' || key === 'ADC_CLOCK') {
if (key === 'RECORDING_CH') {
showKey = 'Channels'
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('')
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
showValue += `${i + 1} `
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'AXIS_CH') {
showKey = '3-AXIS'
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('').reverse()
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
if (i === 0) {
showValue += 'x '
} else if (i === 1) {
showValue += 'y '
} else if (i === 2) {
showValue += 'z '
} else if (i === 3) {
showValue += 'mag '
}
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'AMP_GAIN') {
showKey = 'Amp gain'
const targetID = (value + 1) % 4
for (let i = 0; i < this.ampGainOptions.length; i++) {
if (this.ampGainOptions[i].id === targetID) {
showValue = this.ampGainOptions[i].description
}
}
} else if (key === 'ADC_CLOCK') {
showKey = 'Sample Rate'
for (let i = 0; i < this.neuliveSampleRateOptions.length; i++) {
if (this.neuliveSampleRateOptions[i].value === value) {
showValue = this.neuliveSampleRateOptions[i].description
}
}
}
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
this.content += '<br/>RECORDING: <br/><br/>'
this.preprocessKey.push('SAMPLE_RATE')
const totalSampleRate = paramTable[this.deviceName].SAMPLE_RATE.rev_func(this.params.SAMPLE_RATE)
let chipAmount = 1
if (_deviceName === 'Neulive3.5ex') {
this.preprocessKey.push(...['REC_CH_DEV00', 'REC_CH_DEV01'])
chipAmount = 2
for (let i = 1, len = chipAmount; i <= len; i++) {
this.chipChannelDict['chip' + String(i)] = []
}
this.chipChannelDict.chip1 = paramTable[this.deviceName].REC_CH_DEV00.rev_func(this.params.REC_CH_DEV00)
this.chipChannelDict.chip2 = paramTable[this.deviceName].REC_CH_DEV01.rev_func(this.params.REC_CH_DEV01)
this.channelList.push(...this.chipChannelDict.chip1)
this.channelList.push(...this.chipChannelDict.chip2)
this.sampleRateList.push(parseInt(totalSampleRate / this.chipChannelDict.chip1.length))
this.sampleRateList.push(parseInt(totalSampleRate / this.chipChannelDict.chip2.length))
} else if (_deviceName === 'Neulive3.5') {
this.preprocessKey.push(...['RECORDING_CH_L', 'RECORDING_CH_H'])
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH_L.rev_func(this.params.RECORDING_CH_L))
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH_H.rev_func(this.params.RECORDING_CH_H))
this.sampleRateList.push(parseInt(totalSampleRate / this.channelList.length))
} else {
this.preprocessKey.push(...['RECORDING_CH'])
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH.rev_func(this.params.RECORDING_CH))
this.sampleRateList.push(parseInt(totalSampleRate / this.channelList.length))
}
const chipChannelDictKeys = Object.keys(this.chipChannelDict)
if (chipAmount === 1) {
this.content += ' CHANNELS: ' + this.channelList.toString() + '<br/>'
this.content += ' SAMPLING RATE PER CHANNEL: ' + (this.sampleRateList[0] / 1e3).toFixed(1) + 'k<br/>'
} else if (chipAmount > 1) { // e.g. 3.5ex
for (const [chipNumber, channels] of Object.entries(this.chipChannelDict)) {
this.content += ' CHANNELS (' + chipNumber + '): ' + channels.toString() + '<br/>'
}
for (let i = 0, len = chipAmount; i < len; i++) {
this.content += ' SAMPLING RATE PER CHANNEL (' + chipChannelDictKeys[i] + '): ' + (this.sampleRateList[i] / 1e3).toFixed(1) + 'k<br/>'
}
}
for (const key of recordingParam) {
if (!this.preprocessKey.includes(key)) {
this.fillShowDict(key)
}
}
this.dumpDict()
// stimulation param
this.content += '<br/> Stimulation: <br/>'
for (const [key, value] of Object.entries(params)) {
if (key.includes('sti') || key.includes('STI')) {
let showKey = ''
let showValue = value
if (key === 'STI_H_BRIDGE' || key === 'STI_CHANNEL' || key === 'STI_POLARITY' || key === 'CONTINUOUS_STI' ||
key === 'STI_AMPLITUDE' || key === 'STI_PATTERN' || key === 'STI_T1' || key === 'STI_T2' || key === 'STI_T3' || key === 'STI_T4') {
if (key === 'STI_H_BRIDGE') {
showKey = 'ELECTRODE'
if (value === true) {
showValue = this.stimulateElectrodeOptions[1].label
} else {
showValue = this.stimulateElectrodeOptions[0].label
}
} else if (key === 'STI_CHANNEL' || key === 'STI_POLARITY') {
if (key === 'STI_CHANNEL') {
showKey = 'Channels'
} else {
showKey = 'Polarity'
}
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('')
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
showValue += `${i + 1} `
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'CONTINUOUS_STI') {
showKey = 'CYCLE NUMBER'
if (value === true) {
showValue = this.cycleNumberOptions[1].label
} else {
showValue = this.cycleNumberOptions[0].label
}
} else if (key === 'STI_AMPLITUDE') {
showKey = 'AMPLITUDE (uA)'
showValue = value * 5
} else if (key === 'STI_PATTERN') {
showKey = 'PATTERN'
if (value === true) {
showValue = this.patternModeOptions[1].label
} else {
showValue = this.patternModeOptions[0].label
}
} else if (key === 'STI_T1') {
showKey = 'T1 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us, [0] is cuz now all values in array are same
} else if (key === 'STI_T2') {
showKey = 'T2 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
} else if (key === 'STI_T3') {
showKey = 'T3 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
} else if (key === 'STI_T4') {
showKey = 'T4 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
}
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
this.content += '<br/>STIMULATION: <br/><br/>'
if (+this.params.CONTINUOUS_STI === 1) { // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
this.ignoreKey.push(...['STI_CYCLE_LIST', 'STI_CYCLE_CH0', 'REPEAT_TIME_littleEndian', 'REPEAT_INTERVAL_littleEndian'])
}
for (const key of stimulationParam) {
if (!this.ignoreKey.includes(key)) {
this.fillShowDict(key)
}
}
if ('STI_T1' in params && 'STI_T2' in params && 'STI_T3' in params && 'STI_T4' in params) {
const period = (params.STI_T1[0] + params.STI_T2[0] + params.STI_T3[0] + params.STI_T4[0]) * 10 / 1000 // 10 is cuz 1 value = 10us
this.content += ' Frequency: ' + String(1000 / period) + ' Hz' + '<br/>' // 1000 is cuz 1s = 1000ms
this.content += ' Period: ' + String(period) + ' ms' + '<br/>'
}
} else if (this.deviceName === 'elite') {
for (const [key, value] of Object.entries(params)) {
let showKey = ''
let showValue = value
if (key === 'MODE') {
showKey = 'MODE'
for (let i = 0; i < this.workingModeOptions.length; i++) {
if (this.workingModeOptions[i].id === value) {
showValue = this.workingModeOptions[i].description
}
}
} else if (key === 'ADC_LEVEL') {
showKey = 'Current Range'
for (let i = 0; i < this.currentRangeOptions.length; i++) {
if (this.currentRangeOptions[i].value === value) {
showValue = this.currentRangeOptions[i].label
}
}
} else if (key === 'VOLT_ORIGIN') {
showKey = 'Volt start'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLT_FINAL') {
showKey = 'Volt stop'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLT_STEP') {
showKey = 'Volt step'
showValue = ((value * 2) + 2) / 10 + 'mV'
} else if (key === 'STEP_TIME') {
showKey = 'Step time'
for (let i = 0; i < this.stepTimeOptions.length; i++) {
if (this.stepTimeOptions[i].value === value) {
showValue = this.stepTimeOptions[i].label
}
}
} else if (key === 'CYCLE_NUMBER') {
showKey = 'Cycle number'
showValue = value
} else if (key === 'DAC_VOLT') {
showKey = 'Volt out'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'Resister_LEVEL') {
showKey = 'Resister Range'
for (let i = 0; i < this.resisterRangeOptions.length; i++) {
if (this.resisterRangeOptions[i].value === value) {
showValue = this.resisterRangeOptions[i].label
}
}
} else if (key === 'SAMPLE_RATE') {
showKey = 'Sample rate'
for (let i = 0; i < this.eliteSampleRateOptions.length; i++) {
if (this.eliteSampleRateOptions[i].value === value) {
showValue = this.eliteSampleRateOptions[i].label
}
}
} else if (key === 'Const_Current_value') {
showKey = 'Current'
showValue = value + 'μA'
} else if (key === 'VOLTSTOP_MAX') {
showKey = 'Volt max stop'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLTSTOP_MIN') {
showKey = 'Volt min stop'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'Charge') {
showKey = 'Charge'
for (let i = 0; i < this.chargeOptions.length; i++) {
if (this.chargeOptions[i].value === value) {
showValue = this.chargeOptions[i].label
}
}
} else if (key === 'VOLT_INITIAL') {
showKey = 'E-Initial'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLT_MAX') {
showKey = 'Volt max stop'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLT_MIN') {
showKey = 'Volt min stop'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'Scan_Rate') {
showKey = 'ScanRate'
showValue = (value / 100) + 0.01 + 'mV/s'
} else if (key === 'Data_Resolution_Scan') {
showKey = 'Resolution'
showValue = (value / 10) + 0.1 + 'sps'
} else if (key === 'VOLT_EFINAL') {
showKey = 'E-Final'
showValue = (value / 5000) - 5 + 'V'
} else if (key === 'VOLT_VSCAN') {
showKey = 'VScan'
showValue = (value / 5000) - 5 + 'V'
} else {
continue
}
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
this.dumpDict()
}
this.reset()
},
fillShowDict (key) {
const value = this.params[key]
let expVal
if (key === 'STI_CHANNEL') {
expVal = paramTable[this.deviceName][key].rev_func(value, this.params.STI_POLARITY)
} else if (key === 'STI_POLARITY') {
expVal = paramTable[this.deviceName][key].rev_func(value, this.params.STI_CHANNEL)
} else {
expVal = paramTable[this.deviceName][key].rev_func(value)
}
const name = paramTable[this.deviceName][key].showName
const defaultUnit = paramTable[this.deviceName][key].defaultUnit
const unitScale = defaultUnit !== undefined ? paramTable[this.deviceName][key].unit[defaultUnit] : 1
this.showDict[name] = defaultUnit !== undefined ? (expVal / unitScale).toString() + defaultUnit : expVal.toString()
},
dumpDict () {
for (const [showKey, showValue] of Object.entries(this.showDict)) {
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
this.showDict = {}
},
reset () {
this.params = {}
this.showDict = {}
this.chipChannelDict = {}
this.channelList.length = 0
this.sampleRateList.length = 0
this.preprocessKey.length = 0
this.ignoreKey.length = 0
},
},
}
@@ -1,6 +1,6 @@
<template>
<div>
<span> Resample: {{ currentScale }} to 1 </span>
<p class="display-6"> RESAMPLE: {{ currentScale }} to 1 <p/>
</div>
</template>
@@ -0,0 +1,96 @@
<template>
<div>
<va-card>
<p style="font-size: 125%; text-decoration: underline;"> Channel </p>
<channels/>
<sd-card-toggle ref="sd_card_toggle" :metaIndex="0"/>
<set-time-interval ref="set_time_interval" @setTimeInterval="setTimeInterval"/>
<p class="display-6"> High pass filter </p>
<cutoff-freq class="pl-3 ml-1"/>
<filter-order class="pl-3 ml-1"/>
<filter-button ref="filterButtonRef" @loadFilterData="loadFilterData"/>
<p class="display-6"> Threshold </p>
<threshold/>
<br/>
<p class="display-6"> Waveform </p>
<wave-length class="pl-3 ml-1" />
<prethreshold class="pl-3 ml-1" />
<refractory-period class="pl-3 ml-1" />
<detect-button ref="detectButtonRef" @loadThresholdData="loadThresholdData"/>
</va-card>
</div>
</template>
<script>
import Channels from '@/components/analysis/spike/item/Channels.vue'
import SdCardToggle from '@/components/analysis/spike/item/SdCardToggle.vue'
import SetTimeInterval from '@/components/analysis/spike/item/SetTimeInterval.vue'
import CutoffFreq from '@/components/analysis/spike/item/CutoffFreq.vue'
import FilterOrder from '@/components/analysis/spike/item/FilterOrder.vue'
import Prethreshold from '@/components/analysis/spike/item/Prethreshold.vue'
import RefractoryPeriod from '@/components/analysis/spike/item/RefractoryPeriod.vue'
import Threshold from '@/components/analysis/spike/item/Threshold.vue'
import WaveLength from '@/components/analysis/spike/item/WaveLength.vue'
import DetectButton from '@/components/analysis/spike/item/DetectButton.vue'
import FilterButton from '@/components/analysis/spike/item/FilterButton.vue'
export default {
name: 'control-panel',
components: {
Channels,
SdCardToggle,
SetTimeInterval,
CutoffFreq,
FilterOrder,
FilterButton,
Prethreshold,
RefractoryPeriod,
Threshold,
WaveLength,
DetectButton,
},
props: {
},
async created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
init ({ startTime, endTime }) {
this.$refs.sd_card_toggle.init()
this.$refs.set_time_interval.init({ startTime, endTime })
this.enableFilterButton()
},
setTimeInterval ({ startTime, endTime }) {
this.$emit('setTimeInterval', { startTime: startTime, endTime: endTime })
},
loadThresholdData () {
this.$emit('loadThresholdData')
},
loadFilterData () {
this.$emit('loadFilterData')
},
enableFilterButton () {
this.$refs.filterButtonRef.enableButton()
},
disableFilterButton () {
this.$refs.filterButtonRef.disableButton()
},
enableDetectButton () {
this.$refs.detectButtonRef.enableButton()
},
disableDetectButton () {
this.$refs.detectButtonRef.disableButton()
},
},
// use watch because va-select with @change will have bug
watch: {
},
mounted () {
},
}
</script>
+505
View File
@@ -0,0 +1,505 @@
<template>
<div>
<div class="row row-equal">
<div class="flex md12 xl3 xs12 sm12">
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-accordion>
<va-collapse class="mb-2" :isOpenDefault="true">
<span slot="header">Control Panel</span>
<div slot="body">
<div>
<control-panel
:ref="'control_panel'"
@loadFilterData="loadFilterData"
@loadThresholdData="loadThresholdData"
@setTimeInterval="setTimeInterval"
/>
</div>
</div>
</va-collapse>
</va-accordion>
</div>
</div>
</div>
<div :class="chartAreaClass">
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<spike-overview-chart :ref="'chart_overview'" :cardheight="'30vh'" :data="chartOverviewData"/>
<spike-chart :ref="'chart_detail'" :cardheight="'60vh'" :data="chartDetailData"/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
import SpikeOverviewChart from './SpikeOverviewChart.vue'
import SpikeChart from './SpikeChart.vue'
import ControlPanel from './ControlPanel.vue'
import api from '@/data/api/index'
import { taskInfo } from '@/data/task/TaskInfo'
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
import newSeries from '@/factories/chart/seriesFactory'
export default {
name: 'spike-data-replay',
components: {
SpikeOverviewChart,
SpikeChart,
ControlPanel,
},
computed: {
...mapFields('spike', [
'chartData',
'chartKey',
'chartOverviewData',
'chartDetailData',
'metaList',
'selectedChannel',
'cutoffFreq',
'filterOrder',
'selectedThreshold',
'upperThreshold',
'lowerThreshold',
'waveLength',
'preThreshold',
'refractoryPeriod',
'useSdCardData',
'showError',
]),
},
async created () {
this.chartOverviewData = await this.initChart({
chartAllData: this.chartData,
chartID: 0,
gridLength: 1,
type: 'overview',
device: 'neulive',
mode: 0,
})
this.chartDetailData = await this.initChart({
chartAllData: this.chartData,
chartID: 1,
gridLength: 1,
type: 'detail',
device: 'neulive',
mode: 0,
})
},
data () {
return {
dataInfo: {},
maxTime: 0,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl9', 'pa-0'],
randomColorArray: [],
controllerList: [],
startTime: 0,
endTime: -1,
}
},
mqtt: {
async '+/analysis/spike_detect/+/+/+' (data, topic) {
const topicSplitArray = topic.split('/')
if (this.metaList.some(e => e.id === parseInt(topicSplitArray[4]))) {
if (topicSplitArray[3] === 'data') {
const utf8decoder = new TextDecoder()
const dataStr = utf8decoder.decode(data)
const dataArray = JSON.parse(dataStr)
dataArray.forEach(d => this.chartOverviewData.series[0].data.push(d))
} else if (topicSplitArray[3] === 'time_mark') {
const utf8decoder = new TextDecoder()
const timeMarkStr = JSON.parse(utf8decoder.decode(data))
const timeMarkObj = timeMarkStr.split(' ')
this.drawTimeMark(timeMarkObj)
} else if (topicSplitArray[3] === 'detect_signal') {
const utf8decoder = new TextDecoder()
const detectSignalStr = utf8decoder.decode(data)
const detectSignalObj = Object.values(JSON.parse(detectSignalStr))
this.drawThresholdData(detectSignalObj)
}
}
},
},
methods: {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: taskInfo.getTaskInfo().controllerID,
mac: '',
status: 'loading',
isScan: true,
})
}
await update()
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
pageMqttSub: function (id) {
this.mqttSub(id + '/analysis/spike_detect/+/+/+')
},
pageMqttUnSub: function (id) {
this.mqttUnSub(id + '/analysis/spike_detect/+/+')
},
showChartLoading: function (chart) {
const loadStyle = {
text: '',
color: '#cccccc',
maskColor: 'rgba(255, 255, 255, 0.8)',
zlevel: 0,
}
switch (chart) {
case 'overview':
this.$refs.chart_overview.showLoading(loadStyle)
break
case 'detail':
this.$refs.chart_detail.showLoading(loadStyle)
break
default:
this.$refs.chart_overview.showLoading(loadStyle)
this.$refs.chart_detail.showLoading(loadStyle)
break
}
},
hideChartLoading: function (chart) {
switch (chart) {
case 'overview':
this.$refs.chart_overview.hideLoading()
break
case 'detail':
this.$refs.chart_detail.hideLoading()
break
default:
this.$refs.chart_overview.hideLoading()
this.$refs.chart_detail.hideLoading()
break
}
},
register: async function () {
const gridLength = 1
this.updateGridListChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridLength: gridLength,
})
const register = this.metaList.map(meta => {
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartOverviewData.chartID,
gridIndex: 0,
deviceNameX: 'Time',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: this.selectedChannel,
})
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridIndex: 0,
deviceNameX: 'Time',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: this.selectedChannel,
})
if (this.maxTime < parseInt(meta.time_duration)) {
this.maxTime = parseInt(meta.time_duration)
}
})
await Promise.all(register)
for (let i = 0; i < 2; i++) {
// ID 0 = overview, ID 1 = detail
this.updateAxisLabelChart({
chartID: i,
axis: 'x',
axisIndex: 0,
channel: this.selectedChannel,
})
this.updateAxisLabelChart({
chartID: i,
axis: 'y',
axisIndex: 0,
channel: this.selectedChannel,
})
this.updateDataZoomChart({
chartAllData: this.chartData,
chartID: i,
})
}
this.updateChartSamplingChart({ chartID: this.chartOverviewData.chartID, seriesIndex: 0, method: function (frame) { return frame[0] } })
this.chartOverviewData.xAxis[0].min = this.startTime
this.chartOverviewData.xAxis[0].max = this.endTime
},
getSuffix () {
return this.useSdCardData ? 'sd' : 'ble'
},
loadFilterData: async function () {
this.showChartLoading('overview')
this.$refs.chart_detail.clear()
this.$refs.control_panel.disableDetectButton()
for (const meta in this.dataInfo) {
this.resetOverviewChart()
const ret = await api.analysis.spikeDetect({
controller_id: this.controllerList[0].id,
meta_id: meta,
channel: this.selectedChannel,
filter_order: this.filterOrder,
cut_off_freq: this.cutoffFreq,
data_type: 'filter_data_' + this.getSuffix(),
start_time: this.startTime,
end_time: this.endTime,
})
if (ret.status === 200) {
this.showError.freq = false
this.$refs.control_panel.enableDetectButton()
console.log(this.chartOverviewData)
} else {
console.error(ret.data)
if (ret.status === 400) {
this.showError.freq = true
} else if (ret.status === 500) {
this.showError.freq = false
}
const msgList = ret.data.split('\n')
this.pageToast(msgList[msgList.length - 2])
}
this.hideChartLoading('overview')
}
},
loadThresholdData: async function () {
this.showChartLoading('detail')
this.chartOverviewData.series[0].markArea.data.length = 0
this.chartOverviewData.color = ['#000000'] // black
for (const meta in this.dataInfo) {
let thresholdValue
if (this.selectedThreshold === 'over') {
thresholdValue = this.upperThreshold
} else if (this.selectedThreshold === 'below') {
thresholdValue = this.lowerThreshold
}
this.resetDetailChart()
await api.analysis.spikeDetect({
controller_id: this.controllerList[0].id,
meta_id: meta,
channel: this.selectedChannel,
cut_off_freq: this.cutoffFreq,
filter_order: this.filterOrder,
threshold: [
`'${this.selectedThreshold}'`,
thresholdValue,
],
waveForm: [this.waveLength, this.preThreshold, this.refractoryPeriod],
data_type: 'threshold_data_' + this.getSuffix(),
start_time: this.startTime,
end_time: this.endTime,
})
}
this.chartKey++
this.hideChartLoading('detail')
},
drawThresholdData (detailSignalArray) {
if (detailSignalArray.length <= 8) {
this.chartDetailData.tooltip.show = true
} else {
this.chartDetailData.tooltip.show = false
}
this.chartDetailData.legend.data.length = 0
this.chartDetailData.legend.type = 'scroll'
this.chartDetailData.legend.top = '0.25%'
// this.chartDetailData.legendpageButtonPosition = 'start'
this.chartDetailData.animation = false
this.chartDetailData.color = []
for (let i = 0; i < detailSignalArray.length; i++) {
const detailSignal = detailSignalArray[i].split(' ')
const arrayLength = detailSignal.length
const step = parseInt(this.waveLength) / (arrayLength - 1)
const xAxisIndex = 0
const yAxisIndex = 0
const legendName = 'T_' + String(i + 1)
if (this.chartDetailData.series.length <= i) {
const _series = newSeries('line', legendName, xAxisIndex, yAxisIndex, [-1, -1], ['metaInfo.name', this.selectedChannel], null, null)
this.chartDetailData.series.push(_series)
}
for (let j = 0; j < arrayLength; j++) {
this.chartDetailData.series[i].data.push([j * step, detailSignal[j]])
}
// push legendName
this.chartDetailData.legend.data.push(legendName)
this.chartDetailData.series[i].name = legendName
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: i, method: function (frame) { return frame[0] } })
// set legend and series color
this.chartDetailData.color.push(this.randomColorArray[i])
}
this.chartDetailData.xAxis[0].min = 0
this.chartDetailData.xAxis[0].max = parseInt(this.waveLength)
this.chartDetailData.xAxis[0].axisLabel.show = true
},
drawTimeMark (timeMarkArray) {
// reset overview
this.chartOverviewData.series[0].markArea.data.length = 0
const timeMarkLength = timeMarkArray.length
for (let i = 0; i < timeMarkLength; i++) {
// generate color array for overview markArea and detail chart series
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16) // ref: https://css-tricks.com/snippets/javascript/random-hex-color/
this.randomColorArray.push(randomColor)
if (Number.isInteger(parseInt(timeMarkArray[i]))) { // cuz maybe is '' if no any match after filtering
const timeMark = parseInt(timeMarkArray[i])
let startTime = timeMark - parseInt(this.preThreshold)
let endTime = timeMark + parseInt(this.refractoryPeriod)
if (startTime < 0) {
startTime = 0
}
if (endTime > timeMark) {
endTime = timeMark
}
const overviewSignal = this.chartOverviewData.series[0].data
let startIndex = 0
let endIndex = 0
for (let i = 0; i < overviewSignal.length; i++) {
if (startTime <= overviewSignal[i][0]) {
startIndex = i
break
}
}
for (let i = 0; i < overviewSignal.length; i++) {
if (endTime <= overviewSignal[i][0]) {
endIndex = i
break
}
}
this.chartOverviewData.series[0].markArea.data.push([
{
itemStyle: {
color: randomColor,
borderColor: randomColor,
borderWidth: 1,
},
name: String(i + 1),
xAxis: overviewSignal[startIndex][0],
},
{
xAxis: overviewSignal[endIndex][0],
},
])
}
}
},
setTimeInterval ({ startTime, endTime }) {
this.startTime = startTime
this.endTime = endTime
this.chartOverviewData.xAxis[0].min = startTime
this.chartOverviewData.xAxis[0].max = endTime
this.resetOverviewChart()
this.resetDetailChart()
this.$refs.control_panel.disableDetectButton()
this.chartKey++
},
resetOverviewChart () {
this.chartOverviewData.series[0].data.length = 0
this.chartOverviewData.series[0].markArea.data.length = 0
this.chartOverviewData.color = ['#000000'] // black
},
resetDetailChart () {
for (let i = 0; i < this.chartDetailData.series.length; i++) {
this.chartDetailData.series[i].data.length = 0
}
this.chartDetailData.series.length = 0
this.chartDetailData.legend.data.length = 0
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 7000,
},
)
},
...mapActions('spike', [types.chart.init, types.chart.initSelectedChannel, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
...mapActions('spike', [types.meta.init]),
...mapActions('spike', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
...mapMutations('spike', [types.chart.updateAxisLabel, types.chart.updateChartSampling]),
...mapMutations('spike', [types.meta.updateByIndex]),
...mapGetters('spike', [types.meta.getAll]),
...mapGetters('spike', [types.rec.getAll]),
},
// use watch because va-select with @change will have bug
watch: {
},
async mounted () {
this.getControllerList()
this.pageMqttSub(this.controllerList[0].id)
const metaIDList = this.$route.params.metaList.split('-')
await this.initMeta({ metaIDList: metaIDList, api: api })
this.metaList = this.getAllMeta()
if (this.$route.params.startTime) {
this.startTime = parseInt(this.$route.params.startTime)
}
if (this.$route.params.endTime) {
this.endTime = parseInt(this.$route.params.endTime)
} else {
this.endTime = parseInt(this.metaList[0].time_duration)
}
if (this.$route.params.sd === undefined || this.$route.params.sd === 'sd') {
this.useSdCardData = true
} else if (this.$route.params.sd === 'ble') {
this.useSdCardData = false
}
this.initSelectedChannelChart({ metaIndex: 0 })
await this.initRec({ api: api })
this.dataInfo = this.getAllRec()
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.$refs.control_panel.init({
startTime: this.startTime,
endTime: this.endTime,
})
},
beforeDestroy () {
this.pageMqttUnSub(this.controllerList[0].id)
this.$refs.chart_overview.clear()
this.$refs.chart_detail.clear()
this.resetOverviewChart()
this.resetDetailChart()
for (const id in this.chartData) {
for (const _series of this.chartData[id].series) {
_series.length = 0
}
delete this.chartData[id]
}
this.clearChart({ chartAllData: this.chartData })
},
}
</script>
@@ -0,0 +1,110 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'spike-replay-chart',
components: {
},
props: {
data: {
type: Object,
default: function () {
return {}
},
},
cardclass: {
type: Array,
default: function () {
return ['xs12', 'md12', 'xl12', 'sm12']
},
},
cardheight: {
type: String,
default: '82vh',
},
cardwidth: {
type: String,
default: 'auto',
},
},
created () {
},
data () {
return {
}
},
computed: {
...mapFields('spike', [
'chartKey',
]),
},
watch: {
},
methods: {
resize: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
turnOnAxisEventHandler (eventName, query, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
axis: attr.axis,
status: 'on',
})
// doc: https://echarts.apache.org/zh/api.html#echartsInstance.on
this.$refs.chart_ref.chart._zr.on(eventName, query, handler)
},
turnOffAxisEventHandler (eventName, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
axis: attr.axis,
status: 'off',
})
// doc: https://echarts.apache.org/zh/api.html#echartsInstance.off
this.$refs.chart_ref.chart._zr.off(eventName, handler)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
hideLoading () {
this.$refs.chart_ref.hideLoading()
},
clear () {
this.$refs.chart_ref.clear()
},
...mapMutations('spike', [types.chart.updateAxisEventHandler]),
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
},
}
</script>
<style lang='scss'>
.va-card {
&__body {
padding-top: 0.25rem;
}
}
</style>
@@ -0,0 +1,87 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'spike-overview-chart',
components: {
},
props: {
data: {
type: Object,
default: function () {
return {}
},
},
cardclass: {
type: Array,
default: function () {
return ['xs12', 'md12', 'xl12', 'sm12']
},
},
cardheight: {
type: String,
default: '82vh',
},
cardwidth: {
type: String,
default: 'auto',
},
},
created () {
},
data () {
return {
}
},
computed: {
...mapFields('spike', [
'chartKey',
]),
},
watch: {
},
methods: {
resize: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
hideLoading () {
this.$refs.chart_ref.hideLoading()
},
clear () {
this.$refs.chart_ref.clear()
},
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
},
}
</script>
<style lang='scss'>
.va-card {
&__body {
padding-top: 0.25rem;
}
}
</style>
@@ -0,0 +1,43 @@
<template>
<div>
<va-button-toggle small flat v-model="selectedChannel" :options="selectedChannelOptions" @click.native="reloadData(selectedChannel)"/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Channels',
components: {
},
computed: {
...mapFields('spike', [
'selectedChannel',
'selectedChannelOptions',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
reloadData (val) {
this.selectedChannel = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,59 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Cut-off Freq<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(cutoffFreq)"
@keyup.enter="itemChange(cutoffFreq)"
@blur="itemChange(cutoffFreq)"
v-model="cutoffFreq"
class="mb-0"
:error="showError.freq"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'CutoffFreq',
components: {
},
computed: {
...mapFields('spike', [
'cutoffFreq',
'showError',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.cutoffFreq = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,71 @@
<template>
<div>
<va-button small @click="startDetect" :disabled="isDisable">
Start Detect
</va-button>
</div>
</template>
<script>
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
import { mapMutations } from 'vuex'
export default {
name: 'Channels',
components: {
},
computed: {
...mapFields('spike', [
'chartDetailData',
'selectedChannel',
'waveLength',
'refractoryPeriod',
]),
},
props: {
},
created () {
},
data () {
return {
isDisable: true,
}
},
mqtt: {
},
methods: {
startDetect () {
if (this.refractoryPeriod <= 0) {
this.pageToast('Invalid value')
} else {
this.$emit('loadThresholdData')
}
},
enableButton () {
this.isDisable = false
},
disableButton () {
this.isDisable = true
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
...mapMutations('spike', [types.chart.updateAxisLabel]),
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,56 @@
<template>
<div class="flex xl12 md12 sm12 xs12 mb-4">
<va-button small @click="startFilter" :disabled="isDisable">
Start Filter
</va-button>
</div>
</template>
<script>
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
import { mapMutations } from 'vuex'
export default {
name: 'FilterButton',
components: {
},
computed: {
...mapFields('spike', [
'chartDetailData',
'selectedChannel',
'waveLength',
]),
},
props: {
},
created () {
},
data () {
return {
isDisable: true,
}
},
mqtt: {
},
methods: {
startFilter () {
this.$emit('loadFilterData')
},
enableButton () {
this.isDisable = false
},
disableButton () {
this.isDisable = true
},
...mapMutations('spike', [types.chart.updateAxisLabel]),
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,54 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Filter order<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(filterOrder)"
@keyup.enter="itemChange(filterOrder)"
@blur="itemChange(filterOrder)"
v-model="filterOrder"
class="mb-0"
>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'FilterOrder',
components: {
},
computed: {
...mapFields('spike', [
'filterOrder',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.filterOrder = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,66 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Prethreshold<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(preThreshold)"
@keyup.enter="itemChange(preThreshold)"
@blur="itemChange(preThreshold)"
v-model="preThreshold"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Prethreshold',
components: {
},
computed: {
...mapFields('spike', [
'waveLength',
'preThreshold',
'refractoryPeriod',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
let inputVal
if (val > 0) {
inputVal = val
} else {
inputVal = 800
}
this.preThreshold = inputVal
this.refractoryPeriod = this.waveLength - inputVal
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,56 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0">
<p>Refractory period<p/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<va-input
readonly
v-model="refractoryPeriod"
class="mb-0"
:error="refractoryPeriod <= 0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'RefractoryPeriod',
components: {
},
computed: {
...mapFields('spike', [
'refractoryPeriod',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.refractoryPeriod = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,64 @@
<template>
<div v-if="showToggle">
<span class="box_inline-block display-6"> Use SD Card Data </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useSdCardData"
/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'SdCardToggle',
components: {
},
computed: {
...mapFields('spike', [
'metaList',
'useSdCardData',
]),
},
props: {
metaIndex: {
type: Number,
default: 0,
},
},
created () {
},
data () {
return {
showToggle: false,
}
},
mqtt: {
},
methods: {
init () {
if (this.metaList[this.metaIndex].sd_data_uploaded) {
this.showToggle = true
}
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.box_inline-block {
padding: 1px;
margin: 1px;
display: inline-block;
}
</style>
@@ -0,0 +1,107 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> START TIME <p/>
<va-input
@change="startTimeChange(startTimeInput)"
v-model.lazy="startTimeInput"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> END TIME <p/>
<va-input
@change="endTimeChange(endTimeInput)"
v-model.lazy="endTimeInput"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'set-start-end-time',
components: {
},
props: {
},
computed: {
...mapFields('spike', [
'metaList',
'useSdCardData',
]),
},
created () {
},
data () {
return {
startTimeInput: 'start',
endTimeInput: 'end',
startTime: 0,
endTime: -1,
}
},
mqtt: {
},
methods: {
init ({ startTime, endTime }) {
this.startTime = startTime
this.endTime = endTime
this.startTimeInput = String(startTime / 1e3)
this.endTimeInput = String(endTime / 1e3)
},
translator (val) {
if (val === 'start') {
return 0
} else if (val === 'end') {
return parseInt(this.metaList[0].time_duration)
}
return parseInt(val * 1e3) // us
},
startTimeChange (val) {
this.startTime = this.translator(val) // us
this.$emit('setTimeInterval', { startTime: this.startTime, endTime: this.endTime })
},
endTimeChange (val) {
this.endTime = this.translator(val) // us
this.$emit('setTimeInterval', { startTime: this.startTime, endTime: this.endTime })
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
</style>
@@ -0,0 +1,98 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<va-radio-button
v-for="(option, index) in options"
:key="index"
v-model="selectedThreshold"
:option="option"
:label="`${option}`"
@click.native.stop="validateThreshold()"
/>
<va-input
v-if="selectedThreshold === 'over'"
@change="validateThreshold()"
v-model.lazy="upperThreshold"
class="mb-0"
:error="showError"
:error-messages="errorMsg"
>
<p slot="append" style="margin-right: 0;" small>
µV
</p>
</va-input>
<va-input
v-if="selectedThreshold === 'below'"
@change="validateThreshold()"
v-model.lazy="lowerThreshold"
class="mb-0"
:error="showError"
:error-messages="errorMsg"
>
<p slot="append" style="margin-right: 0;" small>
µV
</p>
</va-input>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Threshold',
components: {
},
computed: {
...mapFields('spike', [
'selectedThreshold',
'upperThreshold',
'lowerThreshold',
]),
},
props: {
},
created () {
},
data () {
return {
showError: false,
errorMsg: [''],
options: [
// 'Auto',
'over',
'below',
],
}
},
mqtt: {
},
methods: {
validateThreshold () {
if (this.selectedThreshold === 'over') {
if (this.upperThreshold < 0) {
this.upperThreshold = 0
this.showError = true
this.errorMsg[0] = 'need >= 0'
return
}
} else if (this.selectedThreshold === 'below') {
if (this.lowerThreshold > 0) {
this.lowerThreshold = 0
this.showError = true
this.errorMsg[0] = 'need <= 0'
return
}
}
this.showError = false
this.errorMsg[0] = ''
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,66 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0">
<p>Waveform length<p/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(waveLength)"
@keyup.enter="itemChange(waveLength)"
@blur="itemChange(waveLength)"
v-model="waveLength"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'WaveLength',
components: {
},
computed: {
...mapFields('spike', [
'waveLength',
'preThreshold',
'refractoryPeriod',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
let inputVal
if (val > 0) {
inputVal = val
} else {
inputVal = 1600
}
this.waveLength = inputVal
this.refractoryPeriod = inputVal - this.preThreshold
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,37 @@
<template>
<div>
<span> Resample: {{ currentScale }} to 1 </span>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'show-resample-rate',
components: {
},
props: {
},
created () {
},
data () {
return {
}
},
computed: {
...mapFields('spike', [
'currentScale',
]),
},
watch: {
},
methods: {
},
mounted () {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,37 @@
<template>
<!-- <va-card
class="horizontal-bars"
> -->
<div class="row">
<div class="flex md12 xs12">
<va-progress-bar
:value="value"
>
{{ value }} %
</va-progress-bar>
</div>
</div>
<!-- </va-card> -->
</template>
<script>
export default {
name: 'progress-bar',
data () {
return {
}
},
props: {
value: {
type: Number,
required: true,
},
},
methods: {
},
mounted () {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,56 @@
<template>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showProgressModal"
size="small"
:title="title"
hide-default-actions
>
<progressBar
:value="value"
:ref="'progressBarRef'"
/>
</va-modal>
</template>
<script>
import progressBar from '@/components/common/progressBarByValueModal/progressBar.vue'
export default {
name: 'progress-bar-modal',
components: {
progressBar,
},
data () {
return {
showProgressModal: false,
value: 0,
title: '',
}
},
methods: {
show () {
this.showProgressModal = true
},
hide () {
this.showProgressModal = false
},
setTitle (title) {
this.title = title
},
setValue (value) {
this.value = value
},
addValue () {
this.value += 1
},
reset () {
this.value = 0
},
progressDone () {
this.$refs.progressBarRef.progressDone()
this.hide()
},
},
}
</script>
@@ -0,0 +1,84 @@
<template>
<!-- <va-card
class="horizontal-bars"
> -->
<div class="row">
<div class="flex md12 xs12 mb-2 mt-2">
<va-progress-bar
:value="progressValue"
>
{{ progressValue }} %
</va-progress-bar>
</div>
</div>
<!-- </va-card> -->
</template>
<script>
export default {
name: 'progress-bar',
data () {
return {
progressValue: 0,
timeInterval: null,
}
},
props: {
meta: { // control system 2.0 meta
type: Object,
required: true,
},
},
methods: {
async estimateTime () {
let sampleRate
if (this.meta.device.library_name !== 'Neulive3.5ex') {
sampleRate = this.meta.parameter_set.SAMPLE_RATE
} else {
const channelList = JSON.parse(this.meta.channels)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = this.meta.parameter_set.SAMPLE_RATE * 2
} else {
sampleRate = this.meta.parameter_set.SAMPLE_RATE
}
}
return parseFloat(this.meta.time_duration / 1e6) * parseFloat(sampleRate / 1e3) * 0.006 * 4
// 經驗公式
// time_duration/1e6: us -> s
// sampleRate/1e3: sample rate unit to (k)
// *4: control system 2.0 upload rate is about 250kb/s
},
animateValues (estimatedTime) { // estimatedTime unit: seconds
let currentTime = this.progressValue = 0
this.timeInterval = setInterval(() => {
if (estimatedTime < 1) {
this.progressValue = 99
clearInterval(this.timeInterval)
} else if (this.progressValue < 90) {
currentTime += (1 / estimatedTime)
this.progressValue = parseInt(currentTime * 100)
} else if (this.progressValue >= 90 && this.progressValue < 99) {
currentTime += (1 / (estimatedTime * 10)) // *10: buffer
this.progressValue = parseInt(currentTime * 100)
} else {
this.progressValue = 99
clearInterval(this.timeInterval)
}
}, 1000)
},
progressDone () {
this.progressValue = 100
},
},
async mounted () {
const estimatedTime = await this.estimateTime()
this.animateValues(estimatedTime)
},
destroyed () {
clearInterval(this.timeInterval)
},
}
</script>
@@ -0,0 +1,55 @@
<template>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showProgressModal"
size="small"
:title="title"
:noOutsideDismiss="true"
hide-default-actions
>
<progressBar
:meta="meta"
:ref="'progressBarRef'"
/>
<div class="flex-center sm12 xl12 md12 xs12">
<va-button @click="hide">Close</va-button>
</div>
</va-modal>
</template>
<script>
import progressBar from '@/components/common/progressBarModal/progressBar.vue'
export default {
name: 'progress-bar-modal',
components: {
progressBar,
},
props: {
title: {
type: String,
default: '',
},
meta: {
type: Object,
required: true,
},
},
data () {
return {
showProgressModal: false,
}
},
methods: {
show () {
this.showProgressModal = true
},
hide () {
this.showProgressModal = false
},
progressDone () {
this.$refs.progressBarRef.progressDone()
this.hide()
},
},
}
</script>
+88 -4
View File
@@ -38,7 +38,7 @@
<div class="row flex">
<div
class="flex sm12 xl6 md12 xs12 ma-0"
class="flex sm12 xl12 md12 xs12 ma-0"
v-for="device in controller.devices" :key="device.addr"
>
<div v-if="!controller.isScan || device.status == 'connected'">
@@ -94,6 +94,35 @@
</va-card>
</div>
</div>
<div
class="flex sm12 xl6 md12 xs12 ma-0"
v-for="trigger in triggerList" :key="trigger.mac"
>
<div v-if="!controller.isScan">
<va-card
style="background-color: #f8f8f8; box-shadow: 5px 5px 0 0 #f1f1f1;"
class="ma-0 pa-0"
>
<va-item :key="trigger.mac" class="pa-0 ma-0"
>
<!-- <va-item-section avatar class="ml-1">
<va-avatar style="border-radius: 0;">
<img :src="trigger.pic" :alt="trigger.name">
</va-avatar>
</va-item-section> -->
<va-item-section class="ml-3">
<va-item-label>
{{ trigger.name }}
<va-badge class="px-1 py-0 ml-2 mt-0" color="success">connected</va-badge>
</va-item-label>
<va-item-label caption>
{{ trigger.mac }}
</va-item-label>
</va-item-section>
</va-item>
</va-card>
</div>
</div>
</div>
</div>
@@ -138,14 +167,20 @@
<script>
import api from '@/data/api/index'
import { taskInfo } from '../../data/task/TaskInfo'
import { taskInfo } from '@/data/task/TaskInfo'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'devices-overview',
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
created () {
},
data () {
@@ -189,6 +224,7 @@ export default {
battery: device.battery,
library_name: device.library_name,
library_version: device.library_version,
record_in_sd: device.record_in_sd,
status: device.status,
pic: taskInfo.getDevicePic(device.device_name),
loading: false,
@@ -247,6 +283,44 @@ export default {
})
}
},
'+/data-server/trigger_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
}
let foundIndex
let mesJson
switch (action) {
case 'info_all': // control_ID/data-server/trigger_manager/info_all
this.triggerList = JSON.parse(mes).connected
break
case 'register': // control_ID/data-server/trigger_manager/register
mesJson = JSON.parse(mes)
foundIndex = this.triggerList.findIndex(o => o.mac === mesJson.mac)
if (foundIndex < 0) { // -1: not found
this.triggerList.push(mesJson)
} else {
this.triggerList[foundIndex] = mesJson
}
break
case 'remove': // control_ID/data-server/trigger_manager/remove/device_mac
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
this.triggerList.splice(foundIndex, 1)
break
case 'info': // control_ID/data-server/trigger_manager/info/device_mac
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
this.triggerList[foundIndex] = mesJson
break
default:
break
}
},
async '+/broadcast' (data, topic) {
const topicSplit = topic.split('/')
const mes = String.fromCharCode.apply(null, data).split(':')
@@ -287,6 +361,10 @@ export default {
header: 'device_update_calibration/0',
device: parseInt(mes[1]),
}))
this.mqttPub(topicSplit[0] + '_user', JSON.stringify({
header: 'get_sd_status/0',
device: parseInt(mes[1]),
}))
// this.mqttPub(topicSplit[0] + '_user', JSON.stringify({
// header: 'device_parent/0',
// device: parseInt(mes[1]),
@@ -341,14 +419,14 @@ export default {
default:
break
}
console.log(String.fromCharCode.apply(null, data), topic)
// console.log(String.fromCharCode.apply(null, data), topic)
},
},
methods: {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
id: taskInfo.getTaskInfo().controllerID,
mac: '',
status: 'loading',
isScan: true,
@@ -374,6 +452,7 @@ export default {
this.mqttSub(controller.id + '/hardware_device_connect/+')
this.mqttSub(controller.id + '/hardware_scan/+')
this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/data-server/trigger_manager/#')
})
},
pageMqttUnSub: function () {
@@ -384,6 +463,7 @@ export default {
this.mqttUnSub(controller.id + '/hardware_device_connect/+')
this.mqttUnSub(controller.id + '/hardware_scan/+')
this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/data-server/trigger_manager/#')
})
},
pageInitPub: function () {
@@ -398,6 +478,8 @@ export default {
this.mqttPub(controller.id + '_user', JSON.stringify({
header: 'hardware_info/0',
}))
this.mqttPub(controller.id + '/client/trigger_manager/scan', '{}')
this.mqttPub(controller.id + '/client/trigger_manager/info_all', '{}')
this.createInitialCollection('admin')
})
},
@@ -419,6 +501,7 @@ export default {
this.mqttPub(controller.id + '_user', JSON.stringify({
header: 'hardware_scan/0',
}))
this.mqttPub(controller.id + '/client/trigger_manager/scan', '{}')
}
}
})
@@ -508,6 +591,7 @@ export default {
watch: {
},
async mounted () {
await taskInfo.updateTaskInfo()
await this.getControllerList()
this.pageMqttSub()
+243 -539
View File
@@ -1,143 +1,27 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12 pa-0 ma-0"
v-model="showDownload"
size="small"
:hideDefaultActions="true"
:mobileFullscreen="false"
>
<template slot="header">
<div class="row mb-2">
<va-icon name="fa fa-download mr-3 " color="primary"/>
<h5 class="mt-0 mb-0">{{'Download'}}</h5>
</div>
</template>
<div class="downloadOptionsList">
<template v-for="(downloadOptions, i) in downloadOptionsList">
<va-accordion :key="downloadOptions.filename">
<va-collapse>
<va-item :key="downloadOptions.filename" slot="header" clickable>
<va-item-section avatar>
<va-item-label>
{{ downloadOptions.filename }}
</va-item-label>
</va-item-section>
<va-item-section>
<!-- <va-item-label>
{{ downloadInfo.minTime }}
</va-item-label>
<va-item-label caption>
{{ downloadInfo.maxTime }}
</va-item-label> -->
</va-item-section>
<va-item-section v-if="downloadOptions.download.status == 'idle'" side>
<va-icon name="fa fa-play" color="gray" @click.native.stop="confirmDownload(downloadOptions.metaPath)"/>
</va-item-section>
<va-item-section v-else side class="md4 offset--md8">
<div class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="20"
>
</fulfilling-bouncing-circle-spinner>
<span class="ml-3">{{loaded}}</span>
</div>
</div>
</va-item-section>
</va-item>
<div slot="body">
<div v-if="downloadOptions.download.status == 'downloading'" class="row flex sm12 xl12 md12 xs12">
<div class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
</div>
<div v-else>
<div class="mb-5">
<va-item class="flex sm12 xl12 md12 xs12 mr-1 pa-0">
<va-icon
class="flex sm1 xl1 md1 xs1 start "
name="glyphicon glyphicon-filter"
size="1.0rem"
:fixedWidth = "Boolean('true')"
/>
<div class="flex sm11 xl11 md11 xs11 row">
<div v-for="(item,index) in downloadOptions.channel" :key="item.channel" class="filter-button">
<va-button
class="mx-1 px-1"
v-if="item.checked"
@click="channelUnchecked(index,downloadOptions.metaPath)"
small
>
{{item.alias}}
</va-button>
<va-button
class="mx-1 px-1"
v-else
@click="channelChecked(index,downloadOptions.metaPath)"
small outline
>
{{item.alias}}
</va-button>
</div>
</div>
</va-item>
</div>
<div class="mb-5">
<va-item class="flex sm12 xl12 md12 xs12 pa-0">
<va-icon
class="flex sm1 xl1 md1 xs1 mr-4"
:fixedWidth="true"
name="va-icon glyphicon glyphicon-time"
size="1.0rem"
/>
<div class="flex">
<va-slider
class="flex sm11 xl11 md11 xs11 filter-button"
value-visible
range
v-model="downloadOptions.timeRange"
@click.native="timeRangeChange(downloadOptions.timeRange,downloadOptions.metaPath)"
@mouseup.native="timeRangeChange(downloadOptions.timeRange,downloadOptions.metaPath)"
@mouseleave.native="timeRangeChange(downloadOptions.timeRange,downloadOptions.metaPath)"
@touchend.native="timeRangeChange(downloadOptions.timeRange,downloadOptions.metaPath)"
:min="downloadOptions.minTime"
:max="downloadOptions.maxTime"
/>
<span> {{ transTime(downloadOptions.timeRange) }} </span>
</div>
</va-item>
</div>
</div>
</div>
</va-collapse>
</va-accordion>
<va-list-separator v-if="i < downloadOptionsList.length - 1" :key="'separator' + downloadOptions.filename" />
</template>
<div class="row flex sm12 xl12 md12 xs12" style="height: 100vh;">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</va-modal>
</div>
</div>
</template>
<script>
import { file } from '../../data/file/File'
import { download } from '../../data/download/Download'
import { exportData } from '../../data/export/export'
import api from '@/data/api/index'
import types from '@/store/modules/download/types'
import { mapActions, mapMutations } from 'vuex'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import newDownload from '@/factories/file/downloadFactory'
import newChannel from '@/factories/file/channelFactory'
import { taskInfo } from '@/data/task/TaskInfo'
export default {
name: 'Download',
@@ -146,391 +30,21 @@ export default {
},
data: () => {
return {
showDownload: false,
controllerList: [],
downloadList: [],
downloadListRecord: [],
downloadOptionsList: [],
// downloadInfo: {
// metaPath: '',
// previousPath: '',
// filenameWithType: '',
// filename: '',
// channel: [],
// timeRange: [0, 0],
// minTime: 0,
// maxTime: 0,
// status: 'idle',
// device: '',
// },
currentlyDownloadInfo: {
metaPath: '',
previousPath: '',
filenameWithType: '',
filename: '',
channel: [],
timeRange: [0, 0],
minTime: 0,
maxTime: 0,
download: {
status: 'idle',
rate: 0,
},
device: '',
},
estimatedTime: 0,
fileSegmentIndex: 0,
fileSegmentMax: 0,
totoalFileSize: 0,
loadingPercent: 0,
loadTime: 0,
interval: null,
data: '',
}
},
computed: {
// transTime (min,max) {
// return this.getTimeInFormat(min) + ' - ' + this.getTimeInFormat(max)
// },
loaded () {
return this.loadingPercent + '%'
},
...mapFields('download', [
'metaList',
'downloadInfo',
'metaInfo',
]),
},
// mqtt: {
// async '+/file_segment/+' (data, topic) {
// console.log('file_segment')
// console.log(String.fromCharCode.apply(null, data))
// // console.log(navigator.connection.downlink)
// // console.log(this.fileNum)
// // console.log(this.downloadOptionsList.length)
// // console.log(JSON.parse(String.fromCharCode.apply(null, data)).data.file_size)
// if (this.downloadOptionsList.length > 0) {
// await download.updateFileSegmentInfo(String.fromCharCode.apply(null, data))
// this.fileSegmentMax = download.getFileSegmentMaxIndex()
// this.checkFileSegmentGetOrNot()
// }
// },
// async '+/file_segment_get/+' (data, topic) {
// console.log('file_segment_get')
// console.log(JSON.parse(new TextDecoder('utf-8').decode(data)))
// if (this.downloadOptionsList.length > 0) {
// const segmentData = atob(JSON.parse(new TextDecoder('utf-8').decode(data)).data.segment)
// this.data += segmentData
// this.fileSegmentIndex += 1
// this.checkFileSegmentGetOrNot()
// }
// },
// },
methods: {
transTime (timeRange) {
return this.getTimeInFormat(timeRange[0]) + ' - ' + this.getTimeInFormat(timeRange[1])
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
showDownloadModal: function () {
this.showDownload = true
},
channelChecked: function (channelIndex, path) {
download.getDownloadOptions(path).channel[channelIndex].checked = true
this.refresh()
},
channelUnchecked: function (channelIndex, path) {
download.getDownloadOptions(path).channel[channelIndex].checked = false
this.refresh()
},
getTimeInFormat: function (time) {
if (time < 60) {
const second = time
return `${second}s`
} else if (time < 3600) {
const second = time % 60
const minute = parseInt(time / 60)
return `${minute}m${second}s`
} else {
const hour = parseInt(time / 3600)
const hourRemain = time % 3600
const minute = parseInt(hourRemain / 60)
const second = hourRemain % 60
return `${hour}h${minute}m${second}s`
}
},
getCurrentFullPathName: function () {
return this.currentlyDownloadInfo.previousPath + '/' + this.currentlyDownloadInfo.filenameWithType
},
downloading: async function (info) {
this.$emit('download')
},
addDownloadTask: async function (info) {
this.downloading()
/*
** download all
*/
if (parseInt(info.time_duration) < 18e8) {
const rawData = await download.getAllRawDatas(info)
exportData.exportCSV(info, rawData)
} else {
for (const channelID in info.raw_data) {
const rawDataIDList = info.raw_data[channelID]
while (rawDataIDList.length > 1000) {
const _list = rawDataIDList.splice(0, 1000)
const rawData = await download.getIndexRawDatas(info, channelID, _list)
exportData.exportCSV(info, rawData)
}
const rawData = await download.getIndexRawDatas(info, channelID, rawDataIDList)
exportData.exportCSV(info, rawData, channelID)
rawData[channelID].length = 0
// const rawData = await download.getAllRawDatas(info)
// console.timeEnd('download')
}
}
// console.time('download')
// const rawData = await download.getAllRawDatas(info)
// console.timeEnd('download')
// exportData.exportCSV(info, rawData)
/*
** download split
*/
this.downloading()
},
_addDownloadTask: function (downloadPath) {
if (Array.isArray(downloadPath)) {
downloadPath.forEach(path => {
this.downloadList.push(path.path)
this.updateDownloadInfo(path.path)
})
} else {
this.downloadList.push(downloadPath)
this.updateDownloadInfo(downloadPath)
}
this.confirmDownload()
},
refresh: function () {
this.downloadOptionsList = download.getDownloadOptionsList()
console.log(this.downloadOptionsList)
},
confirmDownload: function (path) {
console.log('confirmDownload')
console.log(download.getDownloadOptionsList())
console.log(download.isFileDownloading())
console.log(download.downloadTaskRemain())
if (!download.isFileDownloading() && download.downloadTaskRemain()) {
const downloadOptionsIndex = download.getDownloadOptionsList().findIndex(downloadInfo => downloadInfo.metaPath === path)
// this.downloadBlockList[downloadInfoIndex].status = 'downloading'
this.currentlyDownloadInfo = download.getDownloadOptionsList()[downloadOptionsIndex]
this.currentlyDownloadInfo.download.status = 'downloading'
console.log(this.currentlyDownloadInfo)
console.log(download.getDownloadOptionsList())
download.updateDownloadSubFileList(
this.currentlyDownloadInfo.metaPath,
this.currentlyDownloadInfo.channel,
this.currentlyDownloadInfo.timeRange,
)
this.calculateFileSize(this.currentlyDownloadInfo.metaPath)
console.log('getactualsize')
this.subfileListRemain()
}
// if (this.downloadList.length > 0 && this.currentlyDownloadInfo.status === 'idle') {
// this.showDownload()
// this.updateDownloadSubFileList(this.downloadList[0])
// } else {
// /* TODO If file downloading then waiting */
// console.log('no download task or file downloading')
// }
},
_confirmDownload: function () {
console.log('confirmDownload')
console.log(download.getDownloadOptionsList())
// if (this.downloadList.length > 0 && this.currentlyDownloadInfo.status === 'idle') {
// this.showDownload()
// this.updateDownloadSubFileList(this.downloadList[0])
// } else {
// /* TODO If file downloading then waiting */
// console.log('no download task or file downloading')
// }
},
downloadAllSubFileList: async function (path) {
this.currentlyDownloadInfo = this.downloadInfo
download.downloadAllSubFileList(path)
this.subfileListRemain()
},
// updateDownloadInfo: function (path) {
// console.log(path)
// const metaInfo = file.getMetaInfo(path)
// const downloadInfoIndex = this.downloadBlockList.findIndex(downloadInfo => downloadInfo.metaPath === path)
// if (downloadInfoIndex === -1) {
// this.downloadInfo.library = metaInfo.configuration._LIBRARY_
// this.downloadInfo.channel = metaInfo.channel.map(ch => {
// let channel = ''
// if (this.downloadInfo.library.includes('Elite')) {
// channel = (ch === 0) ? 'current' : (ch === 1) ? 'voltage' : (ch === 2) ? 'resistance' : ''
// } else {
// channel = 'channel' + ch
// }
// return {
// alias: channel,
// channel: ch,
// checked: true,
// }
// })
// this.downloadInfo.metaPath = path
// this.downloadInfo.previousPath = path.split('/').slice(0, -1).join('/')
// this.downloadInfo.filename = path.split(/[\/.]+/).slice(0, -1).pop()
// this.downloadInfo.minTime = parseInt([...new Set(metaInfo['start-time'])][0] / 1000)
// this.downloadInfo.maxTime = parseInt(metaInfo.time_duration / 1000)
// this.downloadInfo.timeRange = [this.downloadInfo.minTime, this.downloadInfo.maxTime]
// this.downloadInfo.status = 'idle'
// this.downloadBlockList.push(JSON.parse(JSON.stringify(this.downloadInfo)))
// } else {
// for (var item in this.downloadInfo) {
// this.downloadInfo[item] = this.downloadBlockList[downloadInfoIndex][item]
// }
// }
// },
updatecurrentlyDownloadInfo: function () {
const downloadInfoIndex = this.downloadBlockList.findIndex(downloadInfo => downloadInfo.metaPath === this.currentlyDownloadInfo.metaPath)
if (downloadInfoIndex !== -1) {
for (var item in this.downloadBlockList[downloadInfoIndex]) {
this.downloadBlockList[downloadInfoIndex][item] = this.currentlyDownloadInfo[item]
}
}
},
updateMetaDownloadInfo: function () {
download.updateDownloadSubFileList(
this.currentlyDownloadInfo.metaPath,
this.currentlyDownloadInfo.channel,
this.currentlyDownloadInfo.timeRange,
)
},
updateDownloadSubFileList: function (path) {
console.log('updateDownloadSubFileList')
const downloadOptionsIndex = this.downloadBlockList.findIndex(downloadInfo => downloadInfo.metaPath === path)
this.downloadBlockList[downloadOptionsIndex].status = 'downloading'
this.currentlyDownloadInfo = this.downloadBlockList[downloadOptionsIndex]
this.updatecurrentlyDownloadInfo()
download.updateDownloadSubFileList(
this.currentlyDownloadInfo.metaPath,
this.currentlyDownloadInfo.channel,
this.currentlyDownloadInfo.timeRange,
)
this.subfileListRemain()
},
_updateDownloadSubFileList: function (path) {
console.log(path)
console.log(this.downloadInfo)
const downloadOptionsIndex = this.downloadBlockList.findIndex(downloadInfo => downloadInfo.metaPath === path)
this.downloadBlockList[downloadOptionsIndex].status = 'downloading'
this.currentlyDownloadInfo = this.downloadBlockList[downloadOptionsIndex]
// this.updatecurrentlyDownloadInfo()
download.updateDownloadSubFileList(
this.currentlyDownloadInfo.metaPath,
this.currentlyDownloadInfo.channel,
this.currentlyDownloadInfo.timeRange,
)
this.subfileListRemain()
},
subfileListRemain: function () {
console.log('subfileListRemain')
if (download.checkDownloadSubFileList(this.currentlyDownloadInfo.metaPath)) {
this.currentlyDownloadInfo.filenameWithType = download.getFirstSubfile(this.currentlyDownloadInfo.metaPath)
} else {
this.loadingPercent = 100
console.log(this.currentlyDownloadInfo.filenameWithType + ' download complete')
console.log(performance.now())
this.exportCSV()
this.test(this.downloadDone)
// this.currentlyDownloadInfo.status = this.currentlyDownloadInfo.status = 'idle'
// this.updatecurrentlyDownloadInfo()
// this.loadingPercent = 0
}
},
checkFileSegmentGetOrNot: function () {
console.log('checkFileSegmentGetOrNot')
if (this.fileSegmentIndex >= this.fileSegmentMax) {
const subfile = file.shiftDownloadSubFileList(this.currentlyDownloadInfo.metaPath)
file.pushDownloadData(this.currentlyDownloadInfo.metaPath, subfile, this.data)
this.data = ''
this.fileSegmentIndex = 0
this.subfileListRemain()
} else {
}
},
exportCSV: function () {
exportData.exportCSV(this.currentlyDownloadInfo)
},
exportTXT: function () {
},
timeRangeChange: function (timeRange, path) {
download.getDownloadOptions(path).timeRange = timeRange
this.refresh()
},
calculateFileSize: function () {
this.totoalFileSize = file.getFileSize(this.currentlyDownloadInfo.metaPath)
// const allSubfile = download.getAllSubfile(this.currentlyDownloadInfo.metaPath)
// this.fileNum = allSubfile.length
const downlinkSpeed = this.getNetSpeed('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAABTVBMVEX////qQzU0qFNChfT7vAU/g/RZlPU5gPT7uQCxyPqXufn/vQDpNCLqQTMwp1D7uADpOCfqPS4fo0bpMiD8wADpLxwopUv0paD1q6b86ObpOiv4/Pn+9/b4xcH2ubX1r6rsXVIre/NwvoOf0qvO6NQzqUm/4cf5zMn739zrSz398O/sVEjvdWz2t7PvenP4ycb7wir96Lf+8tb8zFH93pj+9eH//PD92Yvw9v7+7cP80Xb95q6pxPnq9u1PsmigvvlunvaJyZiGrvjD1vvyl5HtamHoJgzwhH3zmpTtXlLvenHuYSzygCL2mxjsUzDwcSf0jx34phH8z2DxfVHX5P38x0KHvnDOtiGjsjJ0rkLhuRW4tCnS4PyMsDtZq0it2bhmmvbO5OE1noBft3U1pWA/jNc8lLk5nJNBieSSy58+j8t5wIo6mKY2o25FrmBHQXgmAAAIJUlEQVR4nO2b2V/bRhCAhbADxJLQYQvwgcE2PsAhEHK0SUxigoGkTdMj6ZXDTdw2JbT+/x9r+QDZllYra8da85vvKU/yfszszO5IEQQEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRDWpLfXSplbqysWq9n1Unk7HfaSmJFey6ycNipGwpDUAZKRSMxt7B6vl7fDXl5QyqvVHdVQk5o8N4osa0nJqDSOSzNrmc7sVlRVk8fl7J4dTbmRXQt7sf5JZ6onkkPknC3VxE52tiJZPtUMjcru0lI1dkthL5uaTMNI0kVvOJLGRnYmCmx2R/IXPpukVFnh3nG9IvkPn81R1bJhKxC5vWME8evHMRO2hitr1URQv65jospp81jRkgz8LJLaStgyDpSfqywC2ENWN7gL48rJpAXUGe3kVthKQ2w3DKZ+c9ZuPOWocZQqrHagHY4yNRuoBbqjaZyc444D90A35AQPmzFdlYD8uorht410Q4UTnJON0A3TOxA15pLE6nUXvOYRlI3wI9gAFZRCFxReQBaZufAjKBwDtgkeioyQZX4UtcFDIyz5PqrJsqYlk2oymdQ8hqhc7MHtih9Ba+4rVTaqp6fHZ2fHp7sNaxDuPk3lQVCo+iijmmQ0zjJr9ncx6e3Syq6WcBk6crAHhVXqTSirJ7sZ53l2unQsGQ6R5GAPCuUT2vCpz28RL7K3q+pIIDlo9B026EYWmlT1vuGVT1X707jYg8IKVauXjQbdDbZctd0wuYjgGtU7peTcOvUTM/KgcPFQZDqnNYo6Khu7fh6ZftENo8yHYCnhLajN+R3NZzsFh489KAg73jk6yZisXNE4EVz3boXG7iSjzrUKH4KC93EtcTzZkzmZAD9a/obsJ0tnYa8xEKm9SPxboqHEwZkrCPuLkUj8JSFRpdOwlxiQg0iH+PJ3boJqNewVBuTOYqSrGH/lLKjtcFIuJuZhz7Dj+L1jldHKYa8wIKnIJfEfHDJVoj+Kcsr+sk1xvG0kZ30T9uvMleNI25DV2fo2zYH7y5FhxZdDhgbfH/vQsL8YGVG0tw15J+z1BefHUcNIPPLqsvsbt8NeX2BSo35DbUPbCHt9wbkzFsJe2+iHkN/P0ai562gYiS9abUPeCHt5DDhwFOy3DZWT62sQ7u+5GVq3DY2bz3sm555zkvYUl38Ke3kM2F92N4wsPqJ8ytKNwLyGMnxAiGFk+T6t4WY0IJtLUIYPSYZ7tE9ZWpgPyMJNKMM3pCR9MD3D6FMgwftxUpLuT89wfh7KkMk2ZGG4CWR4j1RK49SPYWH4FYzh1yTDN1M1BGoXjwhZuvhwqobvYAxdzt09Q+pSysJw4VkIhnevvSHtmY2NIdChhnRom7Lh42tv+AQNJ4OjfRiG4XRraRiG0+2HQJWGnzMNVLcgnksPpmoI1PH5uVtAGXJ0PwQ6efNzx4e6PXEzpwG7AXMzawObYrCalwaftUWhDIkz72XqmffmAg0kwxtQhqT3FrH4z5RPeXeThiOC4cIRlCHh3VPsF0Wpsfyt14QgQh3aBPf3h7HYr6Jo5ln+FGm3QjV8wfXsHYv8Joqicsjyp46i7oZgzcLtPX7sd0W00IsMf4qwDcGG+oLLtxixt2IPpcXul95tuvuBvZixGP+eJhZ5Lw5gGMQj0jYEuv92GfsmKrb34VJQVNrMfmiBtA2Bzt1dRr9r6zQJ0YbeZPQ7pEoanYcrNMJIv+g2iSF0Rj2RVGZAt+Hwwa3XJIZQckx+hXh0Bd2GQ9V00CSGg8gkT28QduH8AtiXGD0ub1Cxtw6CIpOz2xPi7QPs2N2n3/RjsfcOft16mgr6E69JhRTwQ4wBB70M/eAs2DmeBu77xBwFT9JeSxxpEiNbsR7sB0jN3oKNBom98SYxohjokrFEOK/Ng1fSLo/GmwRDxWceEQS8V1whElI0aKI+I1aZTrsHu97bKepehqKem6yiegnCnkmvOPQMYqeiTtIXH296CEb/YO3iTME7iJ1M9n+V8qqinRDCzS+GyZkUivqFv0z9OO8pCDdGHKXmnaZWpio+wpi60D/d8FIEHEGNkqfIUyuMrQLd81J50RS3/vyLrDitXdiFotjQO9bybd16nmL+TVQEe+XkuCiqIHYdD5vk/Vi4UMzB32vrc9S9mi48nY5bH7o87TnquaKLZKpwruj2dNj67x/XMEancZyx0aKopzbJ1nmxVrN5pmq14nnL1EefoihfXBShPk9wpdam24qDlZu6Lh7mLur183r9Itc6FHXddLxDb/3rmKnA4xknaPr+WIDMHopC+PNsfXLqjNPOUYumf0U6ttrjbWNqp5kh6lCKytZo24B7ZUiG5vQ2GVuf5+2bcXrHtRFSfgqqT0V72wAecxMVD8EUTeXL5TwDfvpEUvTVM3wxaBvRKR64HRXBothpG91MhfvPeJSKcHuxd9sIW7CjmINqGt22Eb6gANgXLcWPYdt1aepA9UZRKK/Q4BREkM1otpl+hRSIWgsgUyedugKRZ52pSrD3HwDU2HZGU+RlC9rIk259/lDMOlcZOqCWY5Sq+iGHAexROGTgaJqsvssBoSgGdDTNcy4T1EYzQBwVU6nz0wPdKbbGpoR0fno7Pwt+FoW64jeQiuk+OeaSVDHnMhF10Zud8F3RkRSt2aiHXMdObDVnT69PoZlrm7qzpzUg1s12Ll+YpeR0IFUr5i9abd2iP/M2u/9ut+r5Ym3G7a5IpWqFYrOZt2g2i4VCLXVt3BAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAE4Yf/AZmODCZml4YgAAAAAElFTkSuQmCC', 3)
// this.totoalFileSize = (allSubfile.length) * 1000000
console.log(downlinkSpeed)
console.log(this.totoalFileSize)
this.estimatedTime = this.totoalFileSize / (downlinkSpeed * 1024)
if (this.totoalFileSize >= 1 * 1024 * 1024) {
console.log('size big')
this.estimatedTime = this.estimatedTime * 10
}
console.log(this.estimatedTime)
this.loadTime = parseInt((this.estimatedTime / 1000) % 60) * 100
console.log(this.loadTime)
this.doProgress()
// allSubfile.forEach(subfile => {
// console.log(subfile)
// this.currentlyDownloadInfo.filenameWithType = subfile
// })
},
doProgress () {
const step = this.estimatedTime / 100 * 1000
this.interval = setInterval(() => {
if (this.loadingPercent >= 99) {
console.log('complete')
clearInterval(this.interval)
this.estimatedTime = 0
} else {
this.loadingPercent++
}
}, step)
},
getNetSpeed (url, times) {
const connection = window.navigator.connection
if (connection && connection.downlink) {
return connection.downlink * 1024 / 8
}
},
test: async function (fn) {
return await setTimeout(async function () {
await fn()
}, 1000)
},
downloadDone: function () {
download.getDownloadOptionsList().shift()
this.loadingPercent = 0
this.refresh()
this.confirmDownload()
},
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
id: taskInfo.getTaskInfo().controllerID,
mac: '',
status: 'loading',
isScan: true,
@@ -539,43 +53,233 @@ export default {
}
await update()
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
// this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/file_meta_info/+')
this.mqttSub(controller.id + '/file_segment/+')
this.mqttSub(controller.id + '/file_segment_get/+')
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
// this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/file_meta_info/+')
this.mqttUnSub(controller.id + '/file_segment/+')
this.mqttUnSub(controller.id + '/file_segment_get/+')
})
chunkArray (rawDataIDArray, chunkSize) {
const arrayLength = rawDataIDArray.length
const results = []
for (let index = 0; index < arrayLength; index += chunkSize) {
results.push(rawDataIDArray.slice(index, index + chunkSize))
}
return results
},
...mapActions('download',
[
types.raw.rawDataByID,
types.raw.rawSdDataByID,
types.export.setHeader,
types.export.setTitle,
types.export.setData,
types.export.getChipIndex,
types.download.closeWriter,
types.download.setStream,
types.download.setSampleRate,
types.download.setIDList,
types.download.clearStream,
],
),
...mapMutations('download',
[
types.download.clearIDList,
],
),
},
watch: {
},
// watch: {
// loadingPercent(val) {
// if (val >= 100) {
// console.log('complete');
// clearInterval(this.interval)
// }
// }
// },
async mounted () {
await this.getControllerList()
this.pageMqttSub()
// TODO authorization
const format = this.$route.params.format
this.metaList = this.$route.params.metaList.split('-')
const sd = this.$route.params.sd
while (this.metaList.length !== 0) {
let meta
const metaID = this.metaList.pop()
const ret = await api.meta.getByID(metaID)
if (ret.status === 200) {
meta = ret.data[0]
}
if (meta !== undefined) {
this.metaInfo = meta
const channelArray = JSON.parse(this.metaInfo.channels)
const channelList = []
// split to different subfile by channel
if (meta.device.library_name.indexOf('Neulive') >= 0) {
if (meta.device.library_name === 'Neulive3.5ex') {
if (channelArray.some(el => el >= 256)) { // 3-axis
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
}
if (channelArray.some(el => (el >= 32 && el <= 63))) { // chip 2
channelList.push(channelArray.filter(el => (el >= 32 && el <= 63)))
}
if (channelArray.some(el => (el >= 0 && el <= 31))) { // chip 1
channelList.push(channelArray.filter(el => (el >= 0 && el <= 31)))
}
} else {
if (channelArray.some(el => el >= 256)) {
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
channelList.push(channelArray.filter(el => el < 256))
} else {
channelList.push(channelArray)
}
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
channelList.push(channelArray)
}
while (channelList.length > 0) {
const channels = channelList.pop()
let maxSplitFiles = 1
let fileIndex = 0
// find max raw data need sub file
for (const rawDataIndex of channels) {
const dataLength = meta.raw_data[rawDataIndex].length
maxSplitFiles = Math.max(maxSplitFiles, Math.ceil(meta.raw_data[rawDataIndex].length / dataLength))
meta.raw_data[rawDataIndex] = this.chunkArray(meta.raw_data[rawDataIndex], dataLength)
}
while (fileIndex < maxSplitFiles) {
// create new download info & add channel
if (this.downloadInfo === null) {
this.downloadInfo = newDownload()
}
this.downloadInfo.channel = channels
// download format
this.downloadInfo.format = format
// download device and filename
if (meta.device.library_name.indexOf('Neulive') >= 0) {
this.downloadInfo.device = [meta.device.library_name.slice(0, 7), meta.device.library_name.slice(7)]
if (this.downloadInfo.channel.some(el => el >= 256 && el <= 259)) {
this.downloadInfo.filename = meta.name + '-accerlate'
this.downloadInfo.filename += (sd !== undefined) ? '-sd' : ''
this.downloadInfo.size = parseInt(parseInt(meta.size) / (76 * (maxSplitFiles)))
} else {
this.downloadInfo.filename = meta.name
this.downloadInfo.filename += (sd !== undefined) ? '-sd' : ''
this.downloadInfo.size = parseInt(meta.size / (maxSplitFiles))
if (this.downloadInfo.device[1] === '3.5ex') {
const chipIndex = await this.getChipIndexExport({ downloadInfo: this.downloadInfo })
if (chipIndex === 1) {
this.downloadInfo.filename += '_ch01-32'
} else if (chipIndex === 2) {
this.downloadInfo.filename += '_ch33-64'
}
}
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
this.downloadInfo.device = [meta.device.library_name.slice(0, 5), meta.device.library_name.slice(5)]
this.downloadInfo.filename = meta.name
this.downloadInfo.size = parseInt(meta.size / (maxSplitFiles))
}
// create new channel info & get sample rate
for (const channel of this.downloadInfo.channel) {
if (this.downloadInfo.channelInfo[channel] === undefined) {
this.downloadInfo.channelInfo[channel] = newChannel()
this.setSampleRateDownload({ channel: channel })
}
}
// set download ID list
this.clearIDListDownload()
for (const channel of this.downloadInfo.channel) {
this.setIDListDownload({ channel: channel, fileIndex: fileIndex })
}
// if create writer
this.clearStreamDownload()
this.setStreamDownload()
// set Header
if (this.downloadInfo.format !== 'csv-edf') {
await this.setHeaderExport()
}
await this.setTitleExport()
// start download data
if (sd === 'sd') {
const dataList = await api.raw.getSdDataAllTimeslotByUUID(this.metaInfo.uuid, this.downloadInfo.channel[0])
const timeslots = dataList.data
for (let idx = 0; idx < timeslots.length; idx++) { // 哪一個row的data (index, start from 0)
let dataString = ''
let maxLength = -1
// download all channel data
for (const channel of this.downloadInfo.channel) {
await this.rawSdDataByIDRaw({
uuid: this.metaInfo.uuid,
channel: channel,
idx: idx,
api: api,
})
if (this.downloadInfo.channelInfo[channel].downloadDataBuffer.length > maxLength) {
maxLength = this.downloadInfo.channelInfo[channel].downloadDataBuffer.length
}
}
// write data stream
const rowData = []
for (let i = 0; i < maxLength; i++) {
for (const channel of this.downloadInfo.channel) {
const dataBuffer = this.downloadInfo.channelInfo[channel].downloadDataBuffer
if (dataBuffer[i] === undefined) {
rowData.push(['', ''])
} else {
rowData.push(dataBuffer[i])
}
}
dataString = dataString + rowData.join(',') + '\r\n'
rowData.length = 0
}
const encode = TextEncoder.prototype.encode.bind(new TextEncoder())
this.downloadInfo.stream.writer.write(encode(dataString))
// reset buffer
for (const channel of this.downloadInfo.channel) {
this.downloadInfo.channelInfo[channel].downloadDataBuffer.length = 0
}
}
} else {
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
if (rawID !== undefined) {
await this.rawDataByIDRaw({
channel: channel,
rawID: rawID,
idx: _idx,
api: api,
})
}
}
}
await this.setDataExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
rowIndex += 1
}
}
this.closeWriterDownload()
fileIndex += 1
}
}
}
}
await new Promise(resolve => setTimeout(resolve, 2000)) // sleep 2s prevent window close
window.close()
},
destroyed () {
this.pageMqttUnSub()
+283
View File
@@ -0,0 +1,283 @@
<template>
<div>
<div class="row flex sm12 xl12 md12 xs12" style="height: 100vh;">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
<progress-modal
:ref="'progressModalRef'"
/>
</div>
</div>
</div>
</template>
<script>
import api from '@/data/api/index'
import types from '@/store/modules/download/types'
import { mapActions, mapMutations } from 'vuex'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import newDownload from '@/factories/file/downloadFactory'
import newChannel from '@/factories/file/channelFactory'
import progressModal from '@/components/common/progressBarByValueModal/progressModal.vue'
import { taskInfo } from '@/data/task/TaskInfo'
export default {
name: 'DownloadOffline',
components: {
FulfillingBouncingCircleSpinner,
progressModal,
},
data: () => {
return {
controllerList: [],
}
},
computed: {
...mapFields('download', [
'metaList',
'downloadInfo',
'metaInfo',
]),
},
methods: {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: taskInfo.getTaskInfo().controllerID,
mac: '',
status: 'loading',
isScan: true,
files: [],
})
}
await update()
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
chunkArray (rawDataIDArray, chunkSize) {
const arrayLength = rawDataIDArray.length
const results = []
for (let index = 0; index < arrayLength; index += chunkSize) {
results.push(rawDataIDArray.slice(index, index + chunkSize))
}
return results
},
...mapActions('download',
[
types.raw.rawDataByID,
types.raw.rawSdDataByID,
types.export.setHeader,
types.export.setTitle,
types.export.setData,
types.export.setAll,
types.export.getHeader,
types.export.getTitle,
types.download.closeWriter,
types.download.setStream,
types.download.setSampleRate,
types.download.setIDList,
types.download.saveFile,
],
),
...mapMutations('download',
[
types.download.clearIDList,
],
),
},
watch: {
},
async mounted () {
await this.getControllerList()
// TODO authorization
const format = this.$route.params.format
this.metaList = this.$route.params.metaList.split('-')
const sd = this.$route.params.sd
while (this.metaList.length !== 0) {
let meta
const metaID = this.metaList.pop()
const ret = await api.meta.getByID(metaID)
if (ret.status === 200) {
meta = ret.data[0]
}
if (meta !== undefined) {
this.metaInfo = meta
const channelArray = JSON.parse(this.metaInfo.channels)
const channelList = []
if (meta.device.library_name.indexOf('Neulive') >= 0) {
if (channelArray.some(el => el >= 256)) {
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
channelList.push(channelArray.filter(el => el < 256))
} else {
channelList.push(channelArray)
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
channelList.push(channelArray)
}
this.$refs.progressModalRef.show()
while (channelList.length > 0) {
const channels = channelList.pop()
let maxSplitFiles = 1
let fileIndex = 0
// find max raw data need sub file
for (const rawDataIndex of Object.keys(meta.raw_data)) {
let dataLength = 0
if (rawDataIndex >= 256) {
dataLength = meta.raw_data[rawDataIndex].length
} else {
dataLength = 160
}
maxSplitFiles = Math.max(maxSplitFiles, Math.ceil(meta.raw_data[rawDataIndex].length / dataLength))
meta.raw_data[rawDataIndex] = this.chunkArray(meta.raw_data[rawDataIndex], dataLength)
}
while (fileIndex < maxSplitFiles) {
if (this.downloadInfo === null) {
this.downloadInfo = newDownload()
}
this.downloadInfo.channel = channels
// download format
this.downloadInfo.format = format
// download device and filename
if (meta.device.library_name.indexOf('Neulive') >= 0) {
this.downloadInfo.device = [meta.device.library_name.slice(0, 7), meta.device.library_name.slice(7)]
if (this.downloadInfo.channel.some(el => el >= 256 && el <= 259)) {
this.downloadInfo.filename = meta.name + '-accerlate-' + format
this.downloadInfo.filename += (sd !== undefined) ? '-sd' : ''
this.downloadInfo.size = parseInt(parseInt(meta.size) / 76)
} else {
this.downloadInfo.filename = meta.name + '-' + format
this.downloadInfo.filename += (sd !== undefined) ? '-sd' : ''
this.downloadInfo.size = parseInt(meta.size)
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
this.downloadInfo.device = [meta.device.library_name.slice(0, 5), meta.device.library_name.slice(5)]
this.downloadInfo.filename = meta.name + '-' + (fileIndex + 1).toString()
this.downloadInfo.size = parseInt(meta.size)
}
this.$refs.progressModalRef.setTitle(this.downloadInfo.filename)
// get sampleRate && downloadList
for (const channel of this.downloadInfo.channel) {
if (this.downloadInfo.channelInfo[channel] === undefined) {
this.downloadInfo.channelInfo[channel] = newChannel()
this.setSampleRateDownload({ channel: channel })
}
}
this.clearIDListDownload()
for (const channel of this.downloadInfo.channel) {
this.setIDListDownload({ channel: channel, fileIndex: fileIndex })
}
// start download data
fileIndex += 1
if (sd === 'sd') {
let dataString = ''
const headerString = await this.getHeaderExport()
const { titleString, channelString } = await this.getTitleExport()
const stringArray = [] // use for generate Blob
stringArray.push(headerString)
stringArray.push(channelString)
stringArray.push(titleString)
const dataList = await api.raw.getSdDataAllTimeslotByUUID(this.metaInfo.uuid, this.downloadInfo.channel[0])
const timeslots = dataList.data
for (let idx = 0; idx < timeslots.length; idx++) { // 哪一個row的data (index, start from 0)
let maxLength = -1
// download all channel data
for (const channel of this.downloadInfo.channel) {
await this.rawSdDataByIDRaw({
uuid: this.metaInfo.uuid,
channel: channel,
idx: idx,
api: api,
})
if (this.downloadInfo.channelInfo[channel].downloadDataBuffer.length > maxLength) {
maxLength = this.downloadInfo.channelInfo[channel].downloadDataBuffer.length
}
}
// write data
const rowData = []
for (let i = 0; i < maxLength; i++) {
for (const channel of this.downloadInfo.channel) {
const dataBuffer = this.downloadInfo.channelInfo[channel].downloadDataBuffer
if (dataBuffer[i] === undefined) {
rowData.push(['', ''])
} else {
rowData.push(dataBuffer[i])
}
}
dataString = dataString + rowData.join(',') + '\r\n'
rowData.length = 0
}
// reset buffer
for (const channel of this.downloadInfo.channel) {
this.downloadInfo.channelInfo[channel].downloadDataBuffer.length = 0
}
this.$refs.progressModalRef.setValue(parseInt((100 / timeslots.length) * (idx + 1)))
}
stringArray.push(dataString)
const exportString = new Blob(stringArray)
this.saveFileDownload({ data: exportString })
} else {
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
this.$refs.progressModalRef.setValue(parseInt((rowIndex / this.downloadInfo.maxRows) * 100))
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
// console.log(rowIndex, _idx, rawID)
if (rawID !== undefined) {
await this.rawDataByIDRaw({
channel: channel,
rawID: rawID,
idx: _idx,
api: api,
})
}
}
}
rowIndex += 1
}
await this.setAllExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
this.$refs.progressModalRef.setValue(100)
}
}
}
}
}
await new Promise(resolve => setTimeout(resolve, 2000)) // sleep
window.close()
},
destroyed () {
},
}
</script>
<style>
.filter-button {
text-align: center;
}
</style>
@@ -1,159 +0,0 @@
<template>
<div>
</div>
</template>
<script>
import api from '@/data/api/index'
import types from '@/store/modules/file/download/elite/csv/types'
import { mapActions, mapGetters, mapMutations } from 'vuex'
// import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'Download',
components: {
// FulfillingBouncingCircleSpinner,
},
data: () => {
return {
controllerList: [],
spliceDataNumsLimit: 20,
}
},
computed: {
getByIndex (index) {
return this.getByIndexDownload(index)
},
getDownloading () {
return this.getDownloadingDownload()
},
getRemainIDLength (index) {
return this.getRemainIDLengthDownload(index)
},
},
methods: {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
mac: '',
status: 'loading',
isScan: true,
files: [],
})
}
await update()
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
// this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/file_meta_info/+')
this.mqttSub(controller.id + '/file_segment/+')
this.mqttSub(controller.id + '/file_segment_get/+')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
// this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/file_meta_info/+')
this.mqttUnSub(controller.id + '/file_segment/+')
this.mqttUnSub(controller.id + '/file_segment_get/+')
})
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
...mapActions('eliteCsv',
[
types.download.init,
types.download.rawDataByIDs,
types.export.createHeadersString,
types.export.createTitlesString,
types.export.createDataString,
types.export.closeFileStream,
],
),
...mapMutations('eliteCsv',
[
types.download.setDownloading,
types.download.removeDownloadBuffer,
],
),
...mapGetters('eliteCsv',
[
types.download.getByIndex,
types.download.getDownloading,
types.download.getAll,
types.download.getRemainIDLength,
],
),
},
watch: {
},
async mounted () {
await this.getControllerList()
// TODO authorization
this.pageMqttSub()
const metaIDList = this.$route.params.metaList.split('-')
const recoverData = this.$route.params.recover
await this.initDownload({ metaIDList: metaIDList, api: api })
// let first file download in download List
this.setDownloadingDownload()
const downloadingFile = this.getDownloading
downloadingFile.recoverData = recoverData
let spliceDataNums = 0
// console.log('downloadingFile', downloadingFile)
// console.log(this.getRemainIDLength(0))
// create headers and title
this.createHeadersStringExport()
this.createTitlesStringExport()
// loop until each channel's rawID List empty
while (this.getRemainIDLength(0)) {
// forEach channel in raw_data
for (const channel in downloadingFile.raw_data) {
// get spliceDataNumsLimit numbers of ID to download once time
if (downloadingFile.raw_data[channel].length >= this.spliceDataNumsLimit) {
spliceDataNums = this.spliceDataNumsLimit
} else {
spliceDataNums = downloadingFile.raw_data[channel].length
}
const rawID = downloadingFile.raw_data[channel].splice(0, spliceDataNums)
// console.log('rawID', channel, rawID)
// download data and save into dataBuffer by rawDataIDList
await this.rawDataByIDsDownload({
channel: channel,
rawDataIDList: rawID,
api: api,
})
}
// export data
this.createDataStringExport()
// this.removeDownloadBufferDownload()
}
this.closeFileStreamExport()
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
<style>
.filter-button {
text-align: center;
}
</style>
@@ -1,216 +0,0 @@
<template>
<div>
<div class="row flex sm12 xl12 md12 xs12" style="height: 100vh;">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
</div>
</template>
<script>
import api from '@/data/api/index'
import types from '@/store/modules/file/download/elite/excel/types'
import { mapActions, mapGetters, mapMutations } from 'vuex'
// import { file } from '../../data/file/File'
// import { download } from '../../data/download/Download'
// import { exportData } from '../../data/export/export'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'Download',
components: {
FulfillingBouncingCircleSpinner,
},
data: () => {
return {
controllerList: [],
}
},
computed: {
getByIndex (index) {
return this.getByIndexDownload(index)
},
getDownloading () {
return this.getDownloadingDownload()
},
getRemainIDLength (index) {
return this.getRemainIDLengthDownload(index)
},
// transTime (min,max) {
// return this.getTimeInFormat(min) + ' - ' + this.getTimeInFormat(max)
// },
loaded () {
return this.loadingPercent + '%'
},
},
methods: {
transTime (timeRange) {
return this.getTimeInFormat(timeRange[0]) + ' - ' + this.getTimeInFormat(timeRange[1])
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
mac: '',
status: 'loading',
isScan: true,
files: [],
})
}
await update()
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
// this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/file_meta_info/+')
this.mqttSub(controller.id + '/file_segment/+')
this.mqttSub(controller.id + '/file_segment_get/+')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
// this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/file_meta_info/+')
this.mqttUnSub(controller.id + '/file_segment/+')
this.mqttUnSub(controller.id + '/file_segment_get/+')
})
},
...mapActions('eliteExcel',
[
types.download.init,
types.download.rawDataByIDs,
types.export.createHeadersString,
types.export.createTitlesString,
types.export.createDataString,
types.export.closeFileStream,
],
),
...mapMutations('eliteExcel',
[
types.download.setDownloading,
types.download.removeDownloadBuffer,
],
),
...mapGetters('eliteExcel',
[
types.download.getByIndex,
types.download.getDownloading,
types.download.getAll,
types.download.getRemainIDLength,
],
),
},
openPortal () {
this.windowRef = window.open('', '', 'width=600,height=400,left=200,top=200')
this.windowRef.document.body.appendChild(this.$el)
// copyStyles(window.document, this.windowRef.document)
this.windowRef.addEventListener('beforeunload', this.closePortal)
},
closePortal () {
if (this.windowRef) {
this.windowRef.close()
this.windowRef = null
this.$emit('close')
}
},
// watch: {
// loadingPercent(val) {
// if (val >= 100) {
// console.log('complete');
// clearInterval(this.interval)
// }
// }
// },
async mounted () {
await this.getControllerList()
// TODO authorization
this.pageMqttSub()
const metaIDList = this.$route.params.metaList.split('-')
await this.initDownload({ metaIDList: metaIDList, api: api })
this.setDownloadingDownload()
const downloadingFile = this.getDownloading
// create headers and title
this.createHeadersStringExport()
this.createTitlesStringExport()
// let spliceDataNums = 0
let rowIndex = 0
// let columnIndex = 0
// // while (this.getRemainIDLength(0)) {
while (rowIndex < downloadingFile.needRows) {
for (const channel in downloadingFile.downloadIDList) {
const rawID = downloadingFile.downloadIDList[channel][rowIndex]
// console.log('rawID', channel, rawID)
if (rawID !== undefined) {
await this.rawDataByIDsDownload({
channel: channel,
rawDataIDList: rawID,
api: api,
})
}
}
rowIndex += 1
this.createDataStringExport({ lastPartData: (rowIndex === downloadingFile.needRows) })
// this.removeDownloadBufferDownload()
// console.log('end', downloadingFile)
}
this.closeFileStreamExport()
await new Promise(resolve => setTimeout(resolve, 2000)) // sleep
window.close()
// }
// if (downloadingFile.raw_data[channel].length >= 10) {
// spliceDataNums = 10
// } else {
// spliceDataNums = downloadingFile.raw_data[channel].length
// }
// const rawID = downloadingFile.raw_data[channel].splice(0, spliceDataNums)
// console.log('rawID', channel, rawID)
// await this.rawDataByIDsDownload({
// channel: channel,
// rawDataIDList: rawID,
// api: api,
// })
// }
// // console.log('end', downloadingFile)
// // export data
// this.createDataStringExport({})
// this.removeDownloadBufferDownload()
// }
// const rawData = await download.getAllRawDatas(meta.data[0])
// exportData.exportCSV(meta.data[0], rawData)
// await this.initMeta({ metaIDList: metaIDList, api: api })
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
<style>
.filter-button {
text-align: center;
}
</style>
@@ -1,178 +0,0 @@
<template>
<div>
<div class="row flex sm12 xl12 md12 xs12" style="height: 100vh;">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
</div>
</template>
<script>
import api from '@/data/api/index'
import types from '@/store/modules/file/download/neulive/csv/types'
import { mapActions, mapGetters, mapMutations } from 'vuex'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'Download',
components: {
FulfillingBouncingCircleSpinner,
},
data: () => {
return {
controllerList: [],
spliceDataNumsLimit: 1,
}
},
computed: {
getByIndex (index) {
return this.getByIndexDownload(index)
},
getDownloading () {
return this.getDownloadingDownload()
},
getRemainIDLength (index) {
return this.getRemainIDLengthDownload(index)
},
},
methods: {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
mac: '',
status: 'loading',
isScan: true,
files: [],
})
}
await update()
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
// this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/file_meta_info/+')
this.mqttSub(controller.id + '/file_segment/+')
this.mqttSub(controller.id + '/file_segment_get/+')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
// this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/file_meta_info/+')
this.mqttUnSub(controller.id + '/file_segment/+')
this.mqttUnSub(controller.id + '/file_segment_get/+')
})
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
...mapActions('neuliveCsv',
[
types.download.init,
types.download.rawDataByIDs,
types.export.createHeadersString,
types.export.createTitlesString,
types.export.createDataString,
types.export.closeFileStream,
],
),
...mapMutations('neuliveCsv',
[
types.download.setDownloading,
types.download.removeDownloadBuffer,
],
),
...mapGetters('neuliveCsv',
[
types.download.getByIndex,
types.download.getDownloading,
types.download.getAll,
types.download.getRemainIDLength,
],
),
},
watch: {
},
async mounted () {
await this.getControllerList()
// TODO authorization
this.pageMqttSub()
const metaIDList = this.$route.params.metaList.split('-')
const recoverData = this.$route.params.recover
const rawData = this.$route.params.rawData
await this.initDownload({ metaIDList: metaIDList, api: api })
// let first file download in download List
this.setDownloadingDownload()
const downloadingFile = this.getDownloading
downloadingFile.recoverData = recoverData
let spliceDataNums = 0
// console.log(this.getRemainIDLength(0))
const channelArray = Object.keys(downloadingFile.raw_data)
const filterChannelArray = (rawData === 'acc') ? channelArray.filter(ch => parseInt(ch) >= 256) : channelArray.filter(ch => parseInt(ch) < 256)
// create headers and title
this.createHeadersStringExport()
this.createTitlesStringExport({ rawData: rawData })
// loop until each channel's rawID List empty
while (this.getRemainIDLength(0, filterChannelArray)) {
const lastPartData = ((this.getRemainIDLength(0, filterChannelArray) - this.spliceDataNumsLimit) <= 0)
// forEach channel in raw_data
for (const channel of filterChannelArray) {
// get spliceDataNumsLimit numbers of ID to download once time
if (downloadingFile.raw_data[channel].length >= this.spliceDataNumsLimit) {
spliceDataNums = this.spliceDataNumsLimit
} else {
spliceDataNums = downloadingFile.raw_data[channel].length
}
const rawID = downloadingFile.raw_data[channel].splice(0, spliceDataNums)
// download data and save into dataBuffer by rawDataIDList
await this.rawDataByIDsDownload({
channel: channel,
rawDataIDList: rawID,
api: api,
})
}
// export data
this.createDataStringExport({ lastPartData: lastPartData, rawData: rawData })
// if (this.spliceDataNumsLimit <= 10) {
// this.spliceDataNumsLimit += 1
// }
// console.log('removeNeuliveCSV')
// this.removeDownloadBufferDownload()
}
this.closeFileStreamExport()
await new Promise(resolve => setTimeout(resolve, 2000)) // sleep
window.close()
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
<style>
.filter-button {
text-align: center;
}
</style>
@@ -1,207 +0,0 @@
<template>
<div>
</div>
</template>
<script>
import api from '@/data/api/index'
import types from '@/store/modules/file/download/neulive/excel/types'
import { mapActions, mapGetters, mapMutations } from 'vuex'
// import { file } from '../../data/file/File'
// import { download } from '../../data/download/Download'
// import { exportData } from '../../data/export/export'
// import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'Download',
components: {
// FulfillingBouncingCircleSpinner,
},
data: () => {
return {
controllerList: [],
}
},
computed: {
getByIndex (index) {
return this.getByIndexDownload(index)
},
getDownloading () {
return this.getDownloadingDownload()
},
getRemainIDLength (index) {
return this.getRemainIDLengthDownload(index)
},
// transTime (min,max) {
// return this.getTimeInFormat(min) + ' - ' + this.getTimeInFormat(max)
// },
loaded () {
return this.loadingPercent + '%'
},
},
methods: {
transTime (timeRange) {
return this.getTimeInFormat(timeRange[0]) + ' - ' + this.getTimeInFormat(timeRange[1])
},
notify (name) {
this.showToast(`done ${name}`, {
position: 'bottom-right',
})
},
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
mac: '',
status: 'loading',
isScan: true,
files: [],
})
}
await update()
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
// this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/file_meta_info/+')
this.mqttSub(controller.id + '/file_segment/+')
this.mqttSub(controller.id + '/file_segment_get/+')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
// this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/file_meta_info/+')
this.mqttUnSub(controller.id + '/file_segment/+')
this.mqttUnSub(controller.id + '/file_segment_get/+')
})
},
...mapActions('neuliveExcel',
[
types.download.init,
types.download.rawDataByIDs,
types.export.createHeadersString,
types.export.createTitlesString,
types.export.createDataString,
types.export.closeFileStream,
],
),
...mapMutations('neuliveExcel',
[
types.download.setDownloading,
types.download.removeDownloadBuffer,
],
),
...mapGetters('neuliveExcel',
[
types.download.getByIndex,
types.download.getDownloading,
types.download.getAll,
types.download.getRemainIDLength,
],
),
},
openPortal () {
this.windowRef = window.open('', '', 'width=600,height=400,left=200,top=200')
this.windowRef.document.body.appendChild(this.$el)
// copyStyles(window.document, this.windowRef.document)
this.windowRef.addEventListener('beforeunload', this.closePortal)
},
closePortal () {
if (this.windowRef) {
this.windowRef.close()
this.windowRef = null
this.$emit('close')
}
},
// watch: {
// loadingPercent(val) {
// if (val >= 100) {
// console.log('complete');
// clearInterval(this.interval)
// }
// }
// },
async mounted () {
await this.getControllerList()
// TODO authorization
this.pageMqttSub()
const metaIDList = this.$route.params.metaList.split('-')
await this.initDownload({ metaIDList: metaIDList, api: api })
this.setDownloadingDownload()
const downloadingFile = this.getDownloading
console.log('downloadingFile', downloadingFile)
console.log(this.getRemainIDLength(0))
// create headers and title
this.createHeadersStringExport()
this.createTitlesStringExport()
// let spliceDataNums = 0
let rowIndex = 0
// let columnIndex = 0
// // while (this.getRemainIDLength(0)) {
while (rowIndex < downloadingFile.needRows) {
for (const channel in downloadingFile.downloadIDList) {
const rawID = downloadingFile.downloadIDList[channel][rowIndex]
// console.log('rawID', channel, rawID)
if (rawID !== undefined) {
await this.rawDataByIDsDownload({
channel: channel,
rawDataIDList: rawID,
api: api,
})
}
}
this.createDataStringExport()
this.removeDownloadBufferDownload()
// console.log('end', downloadingFile)
rowIndex += 1
}
this.closeFileStreamExport()
// }
// if (downloadingFile.raw_data[channel].length >= 10) {
// spliceDataNums = 10
// } else {
// spliceDataNums = downloadingFile.raw_data[channel].length
// }
// const rawID = downloadingFile.raw_data[channel].splice(0, spliceDataNums)
// console.log('rawID', channel, rawID)
// await this.rawDataByIDsDownload({
// channel: channel,
// rawDataIDList: rawID,
// api: api,
// })
// }
// // console.log('end', downloadingFile)
// // export data
// this.createDataStringExport({})
// this.removeDownloadBufferDownload()
// }
// const rawData = await download.getAllRawDatas(meta.data[0])
// exportData.exportCSV(meta.data[0], rawData)
// await this.initMeta({ metaIDList: metaIDList, api: api })
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
<style>
.filter-button {
text-align: center;
}
</style>
+4 -1
View File
@@ -9,6 +9,7 @@ import FileTable from './file-table/FileTable.vue'
// import Download from '../download/Download.vue'
import { file } from '../../data/file/File'
import api from '../../data/api/index'
import { taskInfo } from '@/data/task/TaskInfo'
export default {
components: {
@@ -23,10 +24,11 @@ export default {
getControllerList: async function () {
const update = async () => {
this.controllerList.push({
id: 'tommy_test',
id: taskInfo.getTaskInfo().controllerID,
mac: '',
status: 'loading',
isScan: true,
devices: [],
})
}
await update()
@@ -78,6 +80,7 @@ export default {
},
},
async mounted () {
await taskInfo.updateTaskInfo()
await this.getControllerList()
await api.auth.getJwtData()
console.log(await api.meta.getByParentWithLimit('folder', 2, 5, 0, 'DESC'))
+70
View File
@@ -0,0 +1,70 @@
<template>
<va-card>
<sdcard-file-option />
<sdcard-file-table />
<sdcard-upload-modal />
<sdcard-delete-modal />
</va-card>
</template>
<script>
import SdcardFileOption from './option/SdcardFileOption.vue'
import SdcardFileTable from './sdcard/SdcardFileTable.vue'
import SdcardUploadModal from './sdcard/SdcardUploadModal.vue'
import SdcardDeleteModal from './sdcard/SdcardDeleteModal.vue'
import api from '@/data/api/index'
import { mapFields } from 'vuex-map-fields'
export default {
components: {
SdcardFileOption,
SdcardFileTable,
SdcardUploadModal,
SdcardDeleteModal,
},
data () {
return {
}
},
computed: {
...mapFields('sdcard', [
'controllerList',
]),
},
methods: {
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
this.mqttSub(controller.mac_address + '/data-server/s_d_card_manager/#')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
this.mqttUnSub(controller.mac_address + '/data-server/s_d_card_manager/#')
})
},
pageInitPub: function () {
this.controllerList.forEach(controller => {
this.mqttPub(controller.mac_address + '/client/s_d_card_manager/info_all', '{}')
})
},
},
async mounted () {
const ret = await api.controller.getAll()
this.controllerList = ret.data
this.pageMqttSub()
this.pageInitPub()
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
+28
View File
@@ -0,0 +1,28 @@
<template>
<va-card>
<file-option />
<trigger-file-table />
</va-card>
</template>
<script>
import FileOption from './option/FileOption.vue'
import TriggerFileTable from './trigger/TriggerFileTable.vue'
export default {
components: {
FileOption,
TriggerFileTable,
},
data () {
return {
}
},
methods: {
},
async mounted () {
},
destroyed () {
},
}
</script>
+83 -32
View File
@@ -64,9 +64,6 @@
<va-card class="flex sm12 xl12 md12 xs12">
<template slot="header">
<div class="flex sm12 xl12 md12 xs12" style="text-align: end;">
<!-- <va-button class="" color="success" v-if="selected.length" @click="downloadSelected" target="_blank" small>
DOWNLOAD
</va-button> -->
<va-button class="" color="danger" v-if="selected.length" @click="deleteMultiFilesConfirm" target="_blank" small>
DELETE
</va-button>
@@ -166,6 +163,9 @@
<template slot="size" slot-scope="props">
{{transFileSize(props.rowData.size)}}
</template>
<template slot="sd_size" slot-scope="props">
{{estimateSDFileSize(props.rowData)}}
</template>
<template slot="time_duration" slot-scope="props">
{{transTime(props.rowData.time_duration)}}
</template>
@@ -173,15 +173,27 @@
{{transDeviceInfo(props.rowData.configuration)}}
</template>
<template slot="actions" slot-scope="props" >
<va-popover :message="`download ${props.rowData.name} in csv format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadCsv(props.rowData)"/>
<va-popover :message="`sdcard ${props.rowData.name}`" placement="top">
<sd-card-button
v-if="(props.rowData.type !== 'folder') && (props.rowData.device.library_name.indexOf('Neulive') >= 0) && (props.rowData.sd_data_record === true)"
:meta="props.rowData"
:controller="controllerList[0]"
@refreshPage="refreshPage"
@downloadSdCard="downloadSdCard"
/>
</va-popover>
<va-popover :message="`download ${props.rowData.name} in excel format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder'" @click.stop="downloadExcel(props.rowData)"/>
<va-popover :message="`download ${props.rowData.name} in csv format`" placement="top">
<va-button flat small color="success" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadCsv(props.rowData)"/>
</va-popover>
<va-popover :message="`download ${props.rowData.name} in csv-edf format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadExcel(props.rowData)"/>
</va-popover>
<va-popover :message="`replay ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-eye" v-if="props.rowData.type !== 'folder'" @click.stop="replay(props.rowData)"/>
</va-popover>
<va-popover :message="`spike ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-filter" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="spike(props.rowData)"/>
</va-popover>
<va-popover :message="`edit ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-pencil" @click.stop="editFileConfirm(props.rowData)"/>
</va-popover>
@@ -199,16 +211,18 @@ import { debounce } from 'lodash'
import { file } from '../../../data/file/File'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import api from '../../../data/api/index'
import SdCardButton from '../sdcard/SdCardButton.vue'
export default {
name: 'FileTable',
components: {
FulfillingBouncingCircleSpinner,
SdCardButton,
},
data () {
return {
term: null,
perPage: '160',
perPage: '80',
perPageOptions: ['20', '40', '80', '160'],
fileLists: [],
@@ -255,6 +269,7 @@ export default {
height: '2rem',
},
},
controllerList: [],
}
},
computed: {
@@ -288,7 +303,14 @@ export default {
dataClass: 'text-center',
}, {
name: '__slot:size',
title: 'size',
title: 'size (BLE)',
sortField: 'size',
width: '10%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:sd_size',
title: 'size (SD)',
sortField: 'size',
width: '10%',
titleClass: 'text-center',
@@ -310,7 +332,7 @@ export default {
{
name: '__slot:actions',
dataClass: 'text-right',
width: '20%',
width: '25%',
},
]
},
@@ -351,6 +373,28 @@ export default {
return this.GLOBAL.transFileSize(fileSize)
}
},
estimateSDFileSize: function (_meta) {
if (_meta.size === undefined || !_meta.sd_data_record) {
return '--'
} else {
if (_meta.parameter_set && _meta.parameter_set.SAMPLE_RATE > 1e4) {
let scale = _meta.parameter_set.SAMPLE_RATE / 1e4 // 1e4: 以10k為基準
const channels = JSON.parse(_meta.channels)
if (_meta.device.library_name === 'Neulive3.5ex') {
const chip1ChannelLen = channels.filter(c => c >= 0 && c <= 31).length
const chip2ChannelLen = channels.filter(c => c >= 32 && c <= 63).length
if (chip1ChannelLen > 0 && chip2ChannelLen > 0) { // select two chip
scale = _meta.parameter_set.SAMPLE_RATE * 2 * 2 / 1e4 // 多一個*2是擴充板downsample那邊的issue
} else { // only select one chip
// do nothing
}
}
return this.GLOBAL.transFileSize(_meta.size * scale)
} else {
return this.GLOBAL.transFileSize(_meta.size)
}
}
},
transDate: function (dateTime) {
return this.GLOBAL.dateToStr(dateTime)
},
@@ -481,6 +525,7 @@ export default {
// TODO editFile
if (this.editInfo.type !== 'folder') {
await api.meta.update(this.editInfo.info.id, { name: this.editInfo.changedName })
await api.meta.update30(this.editInfo.info.uuid, { name: this.editInfo.changedName })
} else {
}
await this.editFileDone()
@@ -641,41 +686,47 @@ export default {
},
downloadCsv: async function (meta) {
const href = location.href.split('/')[2]
if (meta.device.library_name.indexOf('Elite') >= 0) {
window.open('http://' + href + '/#/download/elite/csv/' + meta.id)
} else if (meta.device.library_name.indexOf('Neulive') >= 0) {
window.open('http://' + href + '/#/download/neulive/csv/' + meta.id + '/raw', 'raw')
if (JSON.parse(meta.channels).some(el => el >= 256)) {
window.open('http://' + href + '/#/download/neulive/csv/' + meta.id + '/acc', 'acc')
}
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id)
}
},
downloadExcel: async function (meta) {
const href = location.href.split('/')[2]
if (meta.device.library_name.indexOf('Elite') >= 0) {
window.open('http://' + href + '/#/download/elite/excel/' + meta.id)
} else if (meta.device.library_name.indexOf('Neulive') >= 0) {
window.open('http://' + href + '/#/download/neulive/csv/' + meta.id + '/raw/true', 'raw')
if (JSON.parse(meta.channels).some(el => el >= 256)) {
window.open('http://' + href + '/#/download/neulive/csv/' + meta.id + '/acc/true', 'acc')
}
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv-edf/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv-edf/' + meta.id)
}
},
downloadSdCard: async function (meta) {
const href = location.href.split('/')[2]
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id + '/sd')
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id + '/sd')
}
},
replay: function (meta) {
this.$router.push({
path: '/admin/data-replay/' + meta.id,
})
},
spike: function (meta) {
if (meta.device.library_name.includes('Neulive') === true) {
// window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/admin/data-replay/' + meta.id)
this.$router.push({
path: '/admin/data-replay/' + meta.id,
})
} else if (meta.device.library_name.includes('Elite') === true) {
// window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/admin/data-replay/elite/' + meta.id)
this.$router.push({
path: '/admin/data-replay/elite/' + meta.id,
path: '/admin/data-spike/' + meta.id,
})
}
},
},
mounted () {
async mounted () {
const controllerList = await api.controller.getAll()
this.controllerList = controllerList.data
},
destroyed () {
},
+109
View File
@@ -0,0 +1,109 @@
<template>
<div class="row">
<div class="row flex xs12 md6 xl6 sm6 select-box px-0">
<va-select
:ref="'selection'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'select'"
v-model="selectionModel"
textBy="description"
:options="selectionOptions"
:input="selectionEvent()"
/>
<va-select
:ref="'action'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'action'"
v-model="actionModel"
textBy="description"
:options="actionOptions"
:input="actionEvent()"
/>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/file/types'
export default {
components: {
},
computed: {
...mapFields('file', [
'dataList',
]),
},
data () {
return {
selectionModel: {},
selectionOptions: [
{
id: 0,
description: 'Select All File',
},
{
id: 1,
description: 'Clear',
},
],
actionModel: {},
actionOptions: [
{
id: 0,
description: 'Export (.csv)',
},
],
}
},
methods: {
selectionEvent () {
if (this.selectionModel == null) {
return null
}
switch (this.selectionModel.description) {
case 'Select All File':
this.fileTableSelectAll()
break
case 'Clear':
this.fileTableSelectClear()
break
default:
break
}
if (this.$refs.selection !== undefined && this.$refs.selection !== null) {
this.$refs.selection.clear()
}
},
actionEvent () {
if (this.actionModel == null) {
return null
}
switch (this.actionModel.description) {
case 'Export (.csv)':
this.fileDownloadCsv()
break
default:
break
}
if (this.$refs.action !== undefined && this.$refs.action !== null) {
this.$refs.action.clear()
}
},
...mapActions('file', [fileTypes.file.downloadCsv]),
...mapActions('file', [fileTypes.table.selectAll, fileTypes.table.selectClear]),
},
}
</script>
<style lang="scss">
.file-option-select-box {
text-align: right;
font-weight: normal;
letter-spacing: 0;
text-transform: none;
}
</style>
@@ -0,0 +1,194 @@
<template>
<div class="row">
<div class="row flex xs12 md6 xl6 sm6 select-box px-0">
<va-select
:ref="'selection'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'select'"
v-model="selectionModel"
textBy="description"
:options="selectionOptions"
:input="selectionEvent()"
/>
<va-select
:ref="'action'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'action'"
v-model="actionModel"
textBy="description"
:options="actionOptions"
:input="actionEvent()"
/>
</div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="deleteConfirmModal"
:title="'Those files will be deleted:'"
:okText=" $t('modal.confirm') "
@ok="deleteFile()"
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/sdcard/types'
import api from '@/data/api/index'
export default {
components: {
},
computed: {
...mapFields('sdcard', [
'sdInfoList',
'dataList',
'controllerList',
'sdcardUploadModal',
'sdcardDeleteModal',
]),
},
data () {
return {
selectionModel: {},
selectionOptions: [
{
id: 0,
description: 'Select All File',
},
{
id: 1,
description: 'Clear',
},
],
actionModel: {},
actionOptions: [
{
id: 0,
description: 'Upload to hub',
},
{
id: 1,
description: 'Delete',
},
],
deleteConfirmModal: false,
}
},
mqtt: {
async '+/data-server/s_d_card_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('s_d_card_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
let ret
let dataObj
switch (action) {
case 'upload_raw':
ret = JSON.parse(mes)
dataObj = this.dataList.find(d => (d.uuid === ret.meta_uuid && d.sdName === ret.sd_name))
dataObj.uploading = false
if (ret.status === 'success') {
dataObj.uploaded = true
const ret = await api.meta.getByUUID(dataObj.uuid) // control system 2.0 meta
const metaID = ret.data[0].id
await api.meta.update(metaID, { sd_data_uploaded: true })
}
break
case 'delete_meta':
ret = JSON.parse(mes)
dataObj = this.dataList.find(d => (d.uuid === ret.meta_uuid && d.sdName === ret.sd_name))
dataObj.deleting = false
if (ret.status === 'success') {
dataObj.deleted = true
}
break
default:
break
}
},
},
methods: {
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
selectionEvent () {
if (this.selectionModel == null) {
return null
}
switch (this.selectionModel.description) {
case 'Select All File':
this.sdcardTableSelectAll()
break
case 'Clear':
this.sdcardTableSelectClear()
break
default:
break
}
if (this.$refs.selection !== undefined && this.$refs.selection !== null) {
this.$refs.selection.clear()
}
},
actionEvent () {
if (this.actionModel == null) {
return null
}
switch (this.actionModel.description) {
case 'Upload to hub':
this.uploadFile()
this.sdcardUploadModal = true
break
case 'Delete':
this.deleteConfirmModal = true
break
default:
break
}
if (this.$refs.action !== undefined && this.$refs.action !== null) {
this.$refs.action.clear()
}
},
uploadFile () {
for (let i = 0; i < this.dataList.length; i++) {
const file = this.dataList[i]
if (file.selected) {
file.uploading = true
const sdName = this.sdInfoList.find(info => info.meta_list.includes(file.uuid)).name
this.mqttPub(this.controllerList[0].mac_address + '/client/s_d_card_manager/upload_raw', JSON.stringify({ sd_name: sdName, meta_uuid: file.uuid }))
}
}
},
async deleteFile () {
for (let i = 0; i < this.dataList.length; i++) {
const file = this.dataList[i]
if (file.selected) {
file.deleting = true
const sdName = this.sdInfoList.find(info => info.meta_list.includes(file.uuid)).name
this.mqttPub(this.controllerList[0].mac_address + '/client/s_d_card_manager/delete_meta', JSON.stringify({ sd_name: sdName, meta_uuid: file.uuid }))
}
}
this.sdcardDeleteModal = true
},
...mapActions('sdcard', [fileTypes.file.uploadToHub]),
...mapActions('sdcard', [fileTypes.table.selectAll, fileTypes.table.selectClear]),
},
}
</script>
<style lang="scss">
.file-option-select-box {
text-align: right;
font-weight: normal;
letter-spacing: 0;
text-transform: none;
}
</style>
@@ -0,0 +1,88 @@
<template>
<div/>
</template>
<script>
import { apiManager } from './apiManager'
export default {
name: 'SdCardApiAction',
components: {
},
props: {
},
data () {
return {
metaID: '',
mac_address: '',
uuid: '',
}
},
mqtt: {
},
methods: {
initMetaInfo (meta) {
this.metaID = meta.id
// bytes array to hex string formamt
this.mac_address = meta.device.device_address.map(data =>
data.toString(16)
.padStart(2, '0')
.toUpperCase()).join(':')
this.uuid = meta.uuid
},
// async checkInSD (meta) {
// this.initMetaInfo(meta)
// this.pageToast('Start checking in SD card...')
// apiManager.startProgressBar()
// const result = await apiManager.call('Check_in_SD', this.metaID, this.mac_address, this.uuid)
// this.checkSDresult = result.data
// this.pageToast('In SD card: ' + this.checkSDresult)
// apiManager.endProgressBar()
// return result.data
// },
async moveToDB (meta, controller) {
this.initMetaInfo(meta)
this.pageToast('Start Moving to database...')
const result = await apiManager.call('Move_to_DB', controller.id, this.metaID, this.mac_address, this.uuid)
if (result !== undefined) {
return result
} else {
throw new Error('error')
}
},
async deleteSDCard (meta, controller) {
this.initMetaInfo(meta)
this.pageToast('Start deleting in SD card...')
await apiManager.call('Delete_SD', controller.id, this.metaID, this.mac_address, this.uuid)
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
#nprogress .bar {
background: red;
}
#nprogress .spinner-icon {
border-top-color: red;
border-left-color: red;
}
</style>
+133
View File
@@ -0,0 +1,133 @@
<template>
<div>
<!-- recorded = false, no icon; recorded = true, gray icon; uploaded = true, blue icon; in uuid list: gray icon + red exclam -->
<va-button
class="va-button--with-left-icon va-button--without-title"
v-show="meta.sd_data_record === true"
:icon="meta.sd_data_uploaded === true ? 'fa fa-download' : ''"
:color="this.$themes.primary"
@click.native.stop="performButtonAction()"
flat
small
>
<i
v-if="meta.sd_data_uploaded === false"
class="i-sd-card"
style="width: 1rem; height: 1rem;"
@click="performButtonAction()"
/>
</va-button>
<sd-card-api-action :ref="'sd_card_api'"/>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="moveToDBModal"
size="small"
:title="'UPLOAD SD CARD DATA'"
:okText=" $t('modal.confirm') "
@ok="moveToDB()"
/>
<progress-modal
title="SD card data uploading"
:meta="meta"
:ref="'progressModalRef'"
/>
</div>
</template>
<script>
import SdCardApiAction from './SdCardApiAction.vue'
import progressModal from '@/components/common/progressBarModal/progressModal.vue'
export default {
name: 'SdCardButton',
components: {
SdCardApiAction,
progressModal,
},
props: {
controller: {
type: Object,
required: true,
},
meta: {
type: Object,
required: true,
},
},
data () {
return {
isSdCardDataLoaded: false,
moveToDBModal: false,
checkSDresult: '',
metaID: '',
mac_address: '',
uuid: '',
icon: 'fa fa-usb',
}
},
mqtt: {
},
methods: {
async init () {
this.metaID = this.meta.id
this.mac_address = this.meta.device.device_address.map(data =>
data.toString(16)
.padStart(2, '0')
.toUpperCase()).join(':')
this.uuid = this.meta.uuid
},
async performButtonAction () {
const recorded = this.meta.sd_data_record // true or false
const uploaded = this.meta.sd_data_uploaded // true or false
if (recorded === true) {
if (uploaded !== true) {
this.moveToDBModal = true
} else {
this.executeDownload()
}
}
},
async moveToDB () {
await this.executeUpload()
this.$emit('refreshPage')
},
async executeUpload () {
this.$refs.progressModalRef.show()
const result = await this.$refs.sd_card_api.moveToDB(this.meta, this.controller).catch(_ => {
this.$refs.progressModalRef.hide()
this.pageToast('Upload SD card data error, please connect Neulive to Hub by usb or data corrupted or data deleted.')
})
if (result.data === true) {
this.pageToast('Upload SD card data done.')
} else {
this.pageToast('Upload SD card data error, please connect Neulive to Hub by usb or data corrupted or data deleted.')
}
this.$refs.progressModalRef.progressDone()
// remove delete sd card first, still have problem.
// await this.$refs.sd_card_api.deleteSDCard(this.meta, this.controller)
},
executeDownload () {
this.$emit('downloadSdCard', this.meta)
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 7000,
},
)
},
},
async mounted () {
await this.init()
},
watch: { // cuz data table will not call mounted
meta: async function (val) {
await this.init()
},
},
}
</script>
@@ -0,0 +1,54 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="sdcardDeleteModal"
:cancelText="''"
:okText=" $t('modal.close') "
@cancel="removeDeletedFiles()"
@ok="removeDeletedFiles()"
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
<vm-progress v-if="data.deleting" :percentage="50" :text-inside="true" :stroke-width="18" :striped="true"></vm-progress>
<vm-progress v-if="!data.deleting && !data.deleted" :percentage="0" status="exception"></vm-progress>
<vm-progress v-if="!data.deleting && data.deleted" :percentage="100" status="success"></vm-progress>
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
components: {
},
props: {
},
created () {
},
data () {
return {}
},
computed: {
...mapFields('sdcard', [
'dataList',
'sdcardDeleteModal',
]),
},
watch: {
},
methods: {
removeDeletedFiles () {
this.dataList.forEach(item => {
item.deleting = false
})
this.dataList = this.dataList.filter(d => d.deleted !== true)
},
},
}
</script>
@@ -0,0 +1,156 @@
<template>
<div style="height: 94vh; overflow: auto;">
<va-card class="flex sm12 xl12 md12 xs12">
<!-- FileListTable -->
<div v-if="tableLoading" class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
<va-data-table
:fields="fields"
:data="dataList"
:per-page="parseInt(perPage)"
hoverable
clickable
v-else
:ref="'datatable_ref'"
>
<template slot="select" slot-scope="props">
<va-checkbox v-model="props.rowData.selected"/>
</template>
<template slot="name" slot-scope="props">
<va-icon
:name="'fa fa-file'"
size="0.9rem"
class="mr-1"
style="cursor: pointer;"
:color="'dark'"
/>
{{ props.rowData.name }}
</template>
<template slot="create_date" slot-scope="props">
{{ props.rowData.create_date }}
</template>
<template slot="sd_name" slot-scope="props">
{{ props.rowData.sdName }}
</template>
<template slot="device_info" slot-scope="props">
{{ props.rowData.device.library_name + ', ' + props.rowData.device.device_version }}
</template>
</va-data-table>
</va-card>
</div>
</template>
<script>
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/sdcard/types'
export default {
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('sdcard', [
'tableLoading',
'tableKey',
'sdInfoList',
'dataList',
]),
},
data () {
return {
perPage: 80,
fields: [
{
name: '__slot:select',
width: '5%',
},
{
name: '__slot:name',
title: 'name',
sortField: 'name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:create_date',
title: 'create date',
sortField: 'create_date',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:sd_name',
title: 'sd_name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:device_info',
title: 'device info',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
],
}
},
mqtt: {
async '+/data-server/s_d_card_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('s_d_card_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
let foundIndex
let mesJson
switch (action) {
case 'info_all':
this.sdInfoList = JSON.parse(mes)
await this.sdcardUpdateDataList()
break
case 'register':
mesJson = JSON.parse(mes)
foundIndex = this.sdInfoList.findIndex(o => o.name === mesJson.name)
if (foundIndex < 0) { // -1: not found
this.sdInfoList.push(mesJson)
} else {
this.sdInfoList[foundIndex] = mesJson
}
await this.sdcardUpdateDataList()
break
case 'remove':
foundIndex = this.sdInfoList.findIndex(o => o.name === mes)
this.sdInfoList.splice(foundIndex, 1)
await this.sdcardUpdateDataList()
break
// case 'info':
// foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
// this.triggerList[foundIndex] = mesJson
// break
default:
break
}
},
},
methods: {
...mapActions('sdcard', [fileTypes.file.updateList]),
},
async mounted () {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,80 @@
<template>
<div>
<vm-progress v-if="meta.uploading" :percentage="progressValue" :text-inside="true" :stroke-width="18" :striped="true"></vm-progress>
<vm-progress v-if="!meta.uploading && !meta.uploaded" :percentage="0" status="exception"></vm-progress>
<vm-progress v-if="!meta.uploading && meta.uploaded" :percentage="100" status="success"></vm-progress>
</div>
</template>
<script>
import api from '@/data/api/index'
export default {
name: 'sdcard-reader-progress-bar',
data () {
return {
progressValue: 0,
timeInterval: null,
}
},
props: {
meta: { // control system 3.0 meta
type: Object,
required: true,
},
},
methods: {
async estimateTime () {
const oldMetaList = await api.meta.getByUUID(this.meta.uuid) // control system 2.0 meta table
const oldMeta = oldMetaList.data[0]
const timeDuration = oldMeta.time_duration
let sampleRate
if (oldMeta.device.library_name !== 'Neulive3.5ex') {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE
} else {
const channelList = JSON.parse(oldMeta.channels)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE * 2
} else {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE
}
}
return parseFloat(timeDuration / 1e6) * parseFloat(sampleRate / 1e3) * 0.006 * 1
// 經驗公式
// time_duration/1e6: us -> s
// sampleRate/1e3: sample rate unit to (k)
// *3.5: control system 2.0 upload rate is about 250~350kb/s
},
animateValues (estimatedTime) { // estimatedTime unit: seconds
let currentTime = this.progressValue = 0
this.timeInterval = setInterval(() => {
if (estimatedTime < 1) {
this.progressValue = 99
clearInterval(this.timeInterval)
} else if (this.progressValue < 90) {
currentTime += (1 / estimatedTime)
this.progressValue = parseInt(currentTime * 100)
} else if (this.progressValue >= 90 && this.progressValue < 99) {
currentTime += (1 / (estimatedTime * 10)) // *10: buffer
this.progressValue = parseInt(currentTime * 100)
} else {
this.progressValue = 99
clearInterval(this.timeInterval)
}
}, 1000)
},
progressDone () {
this.progressValue = 100
},
},
async mounted () {
const estimatedTime = await this.estimateTime()
this.animateValues(estimatedTime)
},
destroyed () {
clearInterval(this.timeInterval)
},
}
</script>
@@ -0,0 +1,44 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="sdcardUploadModal"
:cancelText="''"
:okText=" $t('modal.close') "
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
<progress-bar :meta="data" />
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import ProgressBar from '@/components/file/sdcard/SdcardReaderProgressBar.vue'
export default {
components: {
ProgressBar,
},
props: {
},
created () {
},
data () {
return {}
},
computed: {
...mapFields('sdcard', [
'dataList',
'sdcardUploadModal',
]),
},
watch: {
},
}
</script>
+35
View File
@@ -0,0 +1,35 @@
import api from '@/data/api/index'
import nProgress from 'nprogress'
export const apiManager = {
call: async (instruction, controllerID, metaID, macAddress, uuid) => {
if (instruction === 'Check_in_SD') {
return await api.sdcard.exist({
controllerID: controllerID,
metaID: metaID,
mac: macAddress,
uuid: uuid,
})
} else if (instruction === 'Delete_SD') {
return await api.sdcard.deleted({
controllerID: controllerID,
metaID: metaID,
mac: macAddress,
uuid: uuid,
})
} else if (instruction === 'Move_to_DB') {
return await api.sdcard.upload({
controllerID: controllerID,
metaID: metaID,
mac: macAddress,
uuid: uuid,
})
}
},
startProgressBar: () => {
nProgress.start()
},
endProgressBar: () => {
nProgress.done()
},
}
@@ -0,0 +1,126 @@
<template>
<div style="height: 94vh; overflow: auto;">
<va-card class="flex sm12 xl12 md12 xs12">
<!-- FileListTable -->
<div v-if="tableLoading" class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
<va-data-table
:fields="fields"
:data="dataList"
:per-page="parseInt(perPage)"
hoverable
clickable
v-else
:ref="'datatable_ref'"
>
<template slot="select" slot-scope="props">
<va-checkbox v-model="props.rowData.selected"/>
</template>
<template slot="name" slot-scope="props">
<va-icon
:name="'fa fa-file'"
size="0.9rem"
class="mr-1"
style="cursor: pointer;"
:color="'dark'"
/>
{{ props.rowData.name }}
</template>
<template slot="create_date" slot-scope="props">
{{ props.rowData.create_date }}
</template>
<template slot="time_duration" slot-scope="props">
{{ transTime(props.rowData.time_duration) }}
</template>
<template slot="device_info" slot-scope="props">
{{ props.rowData.device.library }}
</template>
</va-data-table>
</va-card>
</div>
</template>
<script>
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/file/types'
export default {
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('file', [
'tableLoading',
'tableKey',
'dataList',
]),
},
data () {
return {
perPage: 80,
fields: [
{
name: '__slot:select',
width: '5%',
},
{
name: '__slot:name',
title: 'name',
sortField: 'name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:create_date',
title: 'create date',
sortField: 'create_date',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:time_duration',
title: 'duration',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:device_info',
title: 'device info',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
],
}
},
methods: {
transTime: function (time) {
if (time === undefined) {
return '--'
} else {
return this.GLOBAL.microsecondsFormat(time * 1e3) // ms -> us
}
},
...mapActions('file', [fileTypes.file.updateList]),
},
async mounted () {
await this.fileUpdateDataList()
},
destroyed () {
},
}
</script>
@@ -11,16 +11,16 @@
<!-- {{ controller }} -->
<va-item :key="controller.device_mac" class="pa-0 ma-0"
>
<va-item-section avatar class="ml-2 mr-2">
<va-item-section avatar class="ml-4 mr-4">
<img
:src="getImageUrl()"
:alt="'BPS-CONTROLLER'"
width="300px"
height="300px"
width="200px"
height="200px"
contain
/>
</va-item-section>
<va-item-section class="ml-4" >
<va-item-section class="ml-5 mr-4" >
<va-item-label style="font-size: 2rem;">
{{ controller.wifi_name }}
</va-item-label>
@@ -46,8 +46,8 @@
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
import api from '@/data/api/index'
import types from '@/store/modules/about/types'
import eliteImgUrl from '@/assets/img/elite_img.png'
import types from '@/store/modules/setting/types'
import controllerImgUrl from '@/assets/img/controller.png'
export default {
name: 'controller-info',
@@ -90,11 +90,11 @@ export default {
},
methods: {
getImageUrl () {
return eliteImgUrl
return controllerImgUrl
},
...mapActions('about', [types.controller.init]),
...mapMutations('about', [types.controller.set, types.controller.updateByIndex]),
...mapGetters('about', [types.controller.getAll]),
...mapActions('setting', [types.controller.init]),
...mapMutations('setting', [types.controller.set, types.controller.updateByIndex]),
...mapGetters('setting', [types.controller.getAll]),
},
async mounted () {
this.initController({ api: api })
@@ -106,8 +106,8 @@ export default {
</script>
<style lang="scss">
.area-style {
height: 100%;
.area-style-setting-controller {
height: auto;
overflow-y: hidden;
overflow-x: hidden;
@@ -1,10 +1,11 @@
<template>
<div class="device-info">
<div class="row flex">
<draggable class="flex d-flex sm12 xl12 md12 xs12" v-model="devices" @start="drag=true" @end="drag=false" >
<div class="flex-wrap-setting sm12 xl12 md12 xs12">
<!-- <draggable class="flex d-flex sm12 xl12 md12 xs12" v-model="devicesList" @start="drag=true" @end="drag=false" > -->
<div
class="flex d-flex sm12 xl3 md4 xs12 ma-0 area-style-info "
v-for="(device, index) in devices" :key="device.id"
class="flex d-flex sm12 xl3 md6 xs12 ma-0"
v-for="(device, index) in devicesList" :key="device.id"
>
<va-card
:noPadding="true"
@@ -21,7 +22,7 @@
class="mr-1"
color="#babfc2"
style="cursor: pointer;"
@click.native.stop="removeCheck(index, device)"
@click.native.stop="remove(index, device)"
/>
</div>
</div>
@@ -30,7 +31,7 @@
<p> {{ device.name }} </p>
</div>
<div class="center-medium">
<p> {{ macArrayToString(device.mac_address) }} </p>
<p> {{ device.mac_address }} </p>
</div>
<div class="mt-4 mb-4" style="text-align: center;">
<img
@@ -48,26 +49,27 @@
<div class="flex sm12 xl12 md12 xs12" style="text-align: center;">
<va-button v-if="device.auto_connect" color="success" @click="autoConnect(index, device)">Auto Connect</va-button>
<va-button v-if="!device.auto_connect" color="#babfc2" @click="autoConnect(index, device)" outline>Auto Connect</va-button>
<va-button >User Guide</va-button>
<va-button @click="openGuide(device.library)">User Guide</va-button>
</div>
</va-card>
</div>
</draggable>
</div>
<!-- </draggable> -->
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
import { mapFields } from 'vuex-map-fields'
import api from '@/data/api/index'
import types from '@/store/modules/about/types'
import types from '@/store/modules/setting/types'
import neuliveImgUrl from '@/assets/img/neulive_img.png'
import eliteImgUrl from '@/assets/img/elite_img.png'
import draggable from 'vuedraggable'
import eliteImgUrl from '@/assets/img/elite_img_v2.png'
// import draggable from 'vuedraggable'
export default {
name: 'device-info',
components: {
draggable,
},
created () {
},
@@ -76,6 +78,9 @@ export default {
}
},
computed: {
...mapFields('setting', [
'devicesList',
]),
devices: {
get () {
return this.getAllDevice()
@@ -94,7 +99,7 @@ export default {
timeStampToLocaleTime: function (time) {
return this.GLOBAL.dateToStr(time)
},
getImageUrl (libraryName) {
getImageUrl: function (libraryName) {
if (libraryName !== null) {
if (libraryName.indexOf('Neulive') >= 0) {
return neuliveImgUrl
@@ -104,40 +109,41 @@ export default {
}
return null
},
autoConnect (index, device) {
autoConnect: async function (index, device) {
device.auto_connect = !device.auto_connect
this.updateDatabaseByIDDevice({
id: device.id,
request: { auto_connect: device.auto_connect },
api: api,
})
this.updateByIndexDevice({ index: index, item: { auto_connect: device.auto_connect } })
await api.device.update(device.id, { auto_connect: device.auto_connect })
},
removeCheck (index, device) {
/**
* TODO remove modal
*/
this.removeDevice({ id: device.id, index: index, device, deviceList: this.devices, api: api })
remove (index, device) {
console.log('removeDevcie', index, device)
this.removeDevice({ id: device.id, index: index, api: api })
},
...mapActions('about', [types.device.init, types.device.remove, types.device.reorder, types.device.updateDatabaseByID]),
...mapMutations('about', [types.device.set, types.device.updateByIndex]),
...mapGetters('about', [types.device.getAll]),
openGuide: async function (library) {
if (library.indexOf('3.1') !== -1) {
window.open('http://' + location.href.split('/')[2] + '/NeuLive31_quickstart_patents.pdf')
}
},
...mapActions('setting', [types.device.init, types.device.remove, types.device.reorder]),
...mapMutations('setting', [types.device.set, types.device.updateByIndex]),
...mapGetters('setting', [types.device.getAll]),
},
async mounted () {
this.initDevice({ api: api })
const devices = await api.device.getAll()
if (devices.status === 200) {
this.devicesList = devices.data
}
},
destroyed () {
},
}
</script>
<style lang="scss">
.area-style-info {
.area-style-setting-device {
height: auto;
overflow-y: hidden;
overflow-x: hidden;
@include media-breakpoint-only(xl) {
height: 48vh;
height: auto;
overflow-y: scroll;
overflow-x: hidden;
}
@@ -163,4 +169,10 @@ export default {
text-align: center;
font-size: 28px;
}
.flex-wrap-setting {
display: flex;
flex-direction: horizontal;
flex-wrap: wrap;
}
</style>
@@ -1,7 +1,15 @@
<template>
<div class="about">
<controller-info/>
<device-info/>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<controller-info/>
</div>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<device-info/>
</div>
</div>
</div>
</template>
<script>
+271 -153
View File
@@ -38,12 +38,13 @@
<div class="flex sm12 xl12 md12 xs12">
<div class="flex sm12 xl12 md12 xs12" style="text-align: center;">
<va-card
class="ma-0 pa-0"
stripe="primary"
>
<template slot="header">
<!-- <template slot="header">
<va-icon name="fa fa-gears mr-3" color="primary"/>
<h5 class="mt-0 mb-0">CONTROL</h5>
</template>
</template> -->
<va-button class="no-dark-style-va-button" v-if="!showDeviceSetting" color="primary" target="_blank" @click="showDeviceSettingClick()" small>
SHOW DEVICES
</va-button>
@@ -53,18 +54,24 @@
<va-button class="" color="success" target="_blank" @click="startAllClick()" small>
START ALL
</va-button>
<va-button class="" color="primary" target="_blank" @click="recordAllClick()" small>
RECORD ALL
</va-button>
<va-button class="" color="warning" target="_blank" @click="stimulateAllClick()" small>
STIMULATE ALL
</va-button>
<va-button class="no-dark-style-va-button" color="danger" target="_blank" @click="stopAllClick()" small>
STOP ALL
</va-button>
<va-button class="no-dark-style-va-button" color="primary" target="_blank" @click.stop="addChart()" small>
<va-button class="no-dark-style-va-button" color="primary" target="_blank" @click.stop="addChart(false)" small>
ADD CHART
</va-button>
</va-card>
</div>
<div :key="chartKey">
<div :key="taskContentChartPanelKey">
<div
v-for="chart in chartDataAll"
v-for="chart in chartData"
:key="chart.chartID"
>
<canvas-chart
@@ -77,6 +84,15 @@
@remove="removeChartCheck(chart.chartID)"
/>
</div>
<div
v-for="trigger in triggerList"
:key="trigger.id"
>
<trigger-chart
v-show="trigger.display === true"
:mac="trigger.mac"
/>
</div>
</div>
<div style="height: 150px;">
@@ -95,11 +111,15 @@
<script>
import { dataStreamBuffer } from '../../../data/task/DataStreamBuffer'
import { chartData } from '../../../data/task/ChartsManagement'
import { taskInfo } from '../../../data/task/TaskInfo'
import CanvasChart from './chart/CanvasChart'
import TaskDevices from './controller/TaskDevices'
// import api from '../../../data/api'
import TriggerChart from './chart/TriggerChart'
// import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import deviceTypes from '@/store/modules/device/types'
import taskTypes from '@/store/modules/task/content/types'
// import TaskFormula from './formula/TaskFormula'
// TODO change chart data tag format
@@ -109,11 +129,19 @@ export default {
components: {
CanvasChart,
TaskDevices,
// TaskFormula,
TriggerChart,
},
computed: {
...mapFields('taskContent', [
'deviceList',
'chartData',
'taskContentChartPanelKey',
'taskContentChartNumber',
]),
...mapFields('device', [
'triggerList',
]),
},
// async created () {
// await taskInfo.updateTaskInfo(this.$route.params.taskID)
// },
data () {
return {
// modal
@@ -121,29 +149,23 @@ export default {
showRemoveChartModal: false,
// refresh timer
dataRefreshTimer: null,
// charts data
chartDataAll: chartData.getAllChartsData(),
// chart refresh key
chartKey: 0,
chartNumber: taskInfo.getTaskInfo().chartList.length,
selectChartID: null,
showDeviceSetting: true,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl8', 'pa-0'],
getParmDone: false,
deviceList: {},
}
},
mqtt: {
async '+/get_device_info_all/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data))
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
this.refreshDevicesWindow()
},
async '+/get_device_info/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data))
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
this.refreshDevicesWindow()
this.getParmDone = true
this.checkStiStatus()
},
async '+/device_parameter/+' (data, topic) {
await taskInfo.deviceParameter(String.fromCharCode.apply(null, data))
@@ -154,69 +176,123 @@ export default {
this.refreshDevicesWindow()
},
async '+/hardware_device/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data))
// console.log(this.deviceList)
// for (const d1 in taskInfo.getTaskInfo().deviceList) {
// if (taskInfo.getTaskInfo().deviceList[d1].info.device_address.length > 0) {
// const addr1 = taskInfo.getTaskInfo().deviceList[d1].info.device_address.join()
// let parameter, content
// for (const d2 in this.deviceList) {
// const addr2 = this.deviceList[d2].info.device_address.join()
// if (addr1 === addr2) {
// console.log(this.deviceList[d2].parameterSet)
// for (const p in this.deviceList[d2].parameterSet) {
// if (p !== 'STIMULATING') {
// if (parameter == null) {
// parameter = p
// } else {
// parameter = parameter + '|' + p
// }
// let c
// if (Array.isArray(this.deviceList[d2].parameterSet[p])) {
// c = '0:' + this.deviceList[d2].parameterSet[p].length + '=' + this.deviceList[d2].parameterSet[p]
// } else {
// c = this.deviceList[d2].parameterSet[p]
// }
// if (content == null) {
// content = c
// } else {
// content = content + '|' + c
// }
// }
// }
// console.log('****', parameter, content)
// // this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
// // header: 'device_parameter/0',
// // device: parseInt(d1),
// // parameter: parameter,
// // content: content,
// // }))
// }
// }
// }
// }
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
if (this.$refs.devices_ref != null) {
await this.$refs.devices_ref.loadingDone()
await this.taskContentChartInitCustomizedFromCache()
}
},
// listen datastream [ topic: controllerID/data_server/device_data_stream/deviceID/channel ]
async '+/data_server/device_data_stream/+/+' (data, topic) {
const topicSplit = topic.split('/')
if (this.getParmDone & !taskInfo.getDeviceInfoByRawID(parseInt(topicSplit[3])).info.isSync) {
if (chartData.defaultChartGenerator(taskInfo.getDeviceIDByRaw(parseInt(topicSplit[3])), null, 'device')) {
taskInfo.deviceStart(parseInt(topicSplit[3]))
const deviceID = parseInt(topicSplit[3])
const channel = parseInt(topicSplit[4])
if (this.getParmDone & !taskInfo.getDeviceInfoByRawID(deviceID).info.isSync) {
const result = await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: taskInfo.getDeviceIDByRaw(deviceID),
_chartID: null,
_type: 'device',
})
if (result) {
taskInfo.deviceStart(deviceID, this.deviceList)
}
}
dataStreamBuffer.into(String.fromCharCode.apply(null, data), parseInt(topicSplit[3]), (parseInt(topicSplit[4]) + 1))
dataStreamBuffer.into(String.fromCharCode.apply(null, data), deviceID, (channel + 1))
},
'+/device_save_path/+' (data, topic) {
taskInfo.deviceSavePath(String.fromCharCode.apply(null, data))
console.log(taskInfo.getTaskInfo())
// console.log(taskInfo.getTaskInfo())
},
'+/device_save_path_set/+' (data, topic) {
taskInfo.deviceSavePath(String.fromCharCode.apply(null, data))
console.log(taskInfo.getTaskInfo())
// console.log(taskInfo.getTaskInfo())
},
async '+/device_get_sd_storage/+' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const mesJson = JSON.parse(mes)
const storage = parseInt(mesJson.data) // unit: kb
const deviceID = parseInt(mesJson.device)
const libraryName = taskInfo.getDeviceInfoByRawID(deviceID).info.library_name
let sampleRate
if (libraryName !== 'Neulive3.5ex') {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE
} else {
const channelList = this.findAllIndexes(taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.CH_LIST, 1)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE * 2
} else {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE
}
}
// 經驗公式
// 3: 1 sample = 3 bytes
// /1000: sample rate unit to k
// 1.1: 10% buffer
const estimatedRemainTime = parseInt(storage / (3 * (sampleRate / 1000) * 1.1))
const showTime = this.GLOBAL.transTime(estimatedRemainTime * 1e6) // *1e6: s -> us
this.pageToast(`The SD card can still record ${showTime} with sampling rate ${(sampleRate / 1e3).toFixed(1) + 'k'}`)
},
async '+/get_sd_status/+' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const mesJson = JSON.parse(mes)
for (const device of this.$refs.devices_ref.deviceList) {
if (mesJson.device === taskInfo.getRawDeviceID(device.id)) {
device.sdCardStatus = mesJson.data.sd_mount
}
}
await taskInfo.updateSdCardStatus(mes)
this.refreshDevicesWindow()
},
'+/data-server/trigger_manager/#' (data, topic) {
let foundIndex
let mesJson
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
}
switch (action) {
case 'info_all': // control_ID/data-server/trigger_manager/info_all
this.triggerList = JSON.parse(mes).connected
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
case 'register': // control_ID/data-server/trigger_manager/register
mesJson = JSON.parse(mes)
foundIndex = this.triggerList.findIndex(o => o.mac === mesJson.mac)
if (foundIndex < 0) { // -1: not found
this.triggerList.push(mesJson)
} else {
this.triggerList[foundIndex] = mesJson
}
break
case 'remove': // control_ID/data-server/trigger_manager/remove/device_mac
this.triggerList.splice(foundIndex, 1)
break
case 'info': // control_ID/data-server/trigger_manager/info/device_mac
this.triggerList[foundIndex] = JSON.parse(mes)
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
case 'set_parameter': // control_ID/data-server/trigger_manager/set_parameter/device_mac
this.triggerList[foundIndex].parameter = JSON.parse(mes)
break
case 'runtime': // control_ID/data-server/trigger_manager/runtime/device_mac
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
default:
break
}
},
async '+/broadcast' (data, topic) {
const mes = String.fromCharCode.apply(null, data).split(':')
@@ -246,14 +322,20 @@ export default {
taskInfo.deviceConnected(mes[1])
break
case 'shutdown':
taskInfo.controllerShutdown()
taskInfo.controllerShutdown(this.deviceList)
this.showShutdownModal = true
break
case 'start':
if (!taskInfo.getDeviceInfoByRawID(parseInt(parseInt(mes[1]))).info.isSync) {
taskInfo.deviceStart(parseInt(mes[1]))
if (chartData.defaultChartGenerator(taskInfo.getDeviceIDByRaw(parseInt(parseInt(mes[1]))), null, 'device')) {
taskInfo.deviceStart(parseInt(parseInt(mes[1])))
if (!taskInfo.getDeviceInfoByRawID(parseInt(mes[1])).info.isSync) {
taskInfo.deviceStart(parseInt(mes[1]), this.deviceList)
const result = await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: taskInfo.getDeviceIDByRaw(parseInt(mes[1])),
_chartID: null,
_type: 'device',
})
if (result) {
taskInfo.deviceStart(parseInt(parseInt(mes[1])), this.deviceList)
}
}
break
@@ -261,7 +343,7 @@ export default {
if (mes[1] === 'all') {
return false
}
taskInfo.deviceStop(parseInt(mes[1]))
taskInfo.deviceStop(parseInt(mes[1]), this.deviceList)
break
case 'device_timeout':
this.pageToast('The device [' + taskInfo.getDeviceIDByRaw(parseInt(mes[1])) + '] is timeout, please check its status or parameters.')
@@ -277,7 +359,7 @@ export default {
default:
break
}
console.log(String.fromCharCode.apply(null, data), topic)
// console.log(String.fromCharCode.apply(null, data), topic)
},
},
methods: {
@@ -297,6 +379,9 @@ export default {
this.mqttSub(id + '/get_device_info/+')
this.mqttSub(id + '/device_parameter/+')
this.mqttSub(id + '/device_parameter_value/+')
this.mqttSub(id + '/device_get_sd_storage/+')
this.mqttSub(id + '/get_sd_status/+')
this.mqttSub(id + '/data-server/trigger_manager/#')
},
pageMqttUnSub: function (id) {
for (const device in taskInfo.getTaskInfo().deviceList) {
@@ -308,6 +393,9 @@ export default {
this.mqttUnSub(id + '/get_device_info/+')
this.mqttUnSub(id + '/device_parameter/+')
this.mqttUnSub(id + '/device_parameter_value/+')
this.mqttUnSub(id + '/device_get_sd_storage/+')
this.mqttUnSub(id + '/get_sd_status/+')
this.mqttUnSub(id + '/data-server/trigger_manager/#')
},
pageInit: async function () {
if (await this.$refs.devices_ref != null) {
@@ -321,11 +409,14 @@ export default {
// parameter: 'RECORDING_CH|AXIS_CH',
// content: '7|1',
// }))
console.log(taskInfo.getTaskInfo().controllerID)
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'hardware_device/0',
}))
await taskInfo.updateChartInfo()
this.chartNumber = taskInfo.getTaskInfo().chartNumber
this.taskContentChartNumber = Object.keys(this.chartData).length
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/scan', '{}')
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/info_all', '{}')
},
pageToast: function (mes) {
this.showToast(
@@ -337,10 +428,19 @@ export default {
},
)
},
findAllIndexes (arr, val) {
var index = []; var i = -1
while ((i = arr.indexOf(val, i + 1)) !== -1) {
index.push(i)
}
return index
},
refreshData: function () {
// TODO don't need to refresh chart with stop status
for (const chart in this.chartDataAll) {
chartData.refresh(parseInt(chart))
// [TODO] don't need to refresh chart with stop status
for (const chart in this.chartData) {
this.taskContentChartRefreshData({
chartID: parseInt(chart),
})
}
},
refreshDevicesWindow: function () {
@@ -348,58 +448,42 @@ export default {
if (this.$refs.devices_ref != null) {
this.$refs.devices_ref.refresh()
}
// api.task.update({
// device_config: taskInfo.getTaskInfo().deviceList,
// })
},
addChart: async function () {
const defaultID = {}
const update = async () => {
for (const chart in chartData.getAllChartsData()) {
defaultID[chart] = chartData.getAllChartsData()[chart].mappingID
}
chartData.init(this.chartNumber, 4000, 1, 1)
this.chartNumber++
this.chartKey++
}
await update()
for (const chart in chartData.getAllChartsData()) {
chartData.getAllChartsData()[chart].mappingID = defaultID[chart]
}
addChart: async function (isDefault) {
this.taskContentChartAdd({
chartID: this.taskContentChartNumber,
maxDots: 4000,
gridLength: 1,
})
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
this.taskContentChartRefreshPanel()
this.taskContentChartNumber++
if (!isDefault) {
await this.taskContentChartSaveToCache()
}
this.pageToast('The chart has been added.')
return this.chartNumber - 1
// chartData.updateGridList(0, 4)
return this.taskContentChartNumber - 1
},
refreshChartSetting: async function () {
const defaultID = {}
const update = async () => {
for (const chart in chartData.getAllChartsData()) {
defaultID[chart] = chartData.getAllChartsData()[chart].mappingID
}
this.chartKey++
}
await update()
for (const chart in chartData.getAllChartsData()) {
chartData.getAllChartsData()[chart].mappingID = defaultID[chart]
}
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
this.taskContentChartRefreshPanel()
},
initChart: function (chartID) {
chartData.reset(chartID)
this.taskContentChartReset({
chartID: chartID,
mappingID: null,
cardName: null,
})
this.pageToast('The chart has been Initialized.')
},
removeChartCheck: function (chartID) {
@@ -411,21 +495,20 @@ export default {
chartID = this.selectChartID
}
dataStreamBuffer.removeByChart(chartID)
if (chartData.getAllChartsData()[chartID] != null) {
if (this.chartData[chartID] != null) {
const defaultID = {}
const update = async () => {
for (const chart in chartData.getAllChartsData()) {
for (const chart in this.chartData) {
if (chart !== chartID) {
defaultID[chart] = chartData.getAllChartsData()[chart].mappingID
defaultID[chart] = this.chartData[chart].mappingID
}
}
delete chartData.getAllChartsData()[chartID]
this.chartKey++
delete this.chartData[chartID]
}
await update()
for (const chart in chartData.getAllChartsData()) {
chartData.getAllChartsData()[chart].mappingID = defaultID[chart]
for (const chart in this.chartData) {
this.chartData[chart].mappingID = defaultID[chart]
}
if (this.$refs.chart_ref != null) {
@@ -434,6 +517,8 @@ export default {
})
}
}
await this.taskContentChartSaveToCache()
this.pageToast('The chart has been removed.')
},
showDeviceSettingClick: function () {
@@ -448,15 +533,14 @@ export default {
},
addMappingChart: async function (deviceID) {
// if have chart with device ID
for (const chart in chartData.getAllChartsData()) {
if (chartData.getAllChartsData()[chart].mappingID === deviceID) {
console.log('000')
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === deviceID) {
return false
}
}
const chart = await this.addChart()
const chart = await this.addChart(true)
await this.$nextTick(async () => {
chartData.getAllChartsData()[chart].mappingID = deviceID
this.chartData[chart].mappingID = deviceID
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
@@ -468,20 +552,12 @@ export default {
addDevice: async function (deviceID) {
this.mqttSub(taskInfo.getTaskInfo().controllerID + '/data_server/device_data_stream/' + taskInfo.getRawDeviceID(deviceID) + '/+')
this.addMappingChart(deviceID)
// if (taskInfo.getDeviceInfo(deviceID).info.isSync) {
// chartData.defaultChartGenerator(deviceID, chart, 'device')
// }
// console.log(chartData.getAllChartsData())
// console.log(taskInfo.getTaskInfo())
// await api.task.update({
// device_config: taskInfo.getTaskInfo().deviceList,
// })
this.pageToast('The device [' + deviceID + '] has been registered.')
},
removeDevice: function (deviceID) {
this.mqttUnSub(taskInfo.getTaskInfo().controllerID + '/data_server/device_data_stream/' + taskInfo.getRawDeviceID(deviceID) + '/+')
for (const chart in chartData.getAllChartsData()) {
if (chartData.getAllChartsData()[chart].mappingID === deviceID) {
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === deviceID) {
dataStreamBuffer.removeByChart(chart)
this.removeChart(chart)
}
@@ -489,9 +565,9 @@ export default {
this.pageToast('The device [' + deviceID + '] has been removed.')
},
addFormula: async function (formulaID) {
const chart = await this.addChart()
const chart = await this.addChart(true)
await this.$nextTick(async () => {
chartData.getAllChartsData()[chart].mappingID = formulaID
this.chartData[chart].mappingID = formulaID
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
@@ -501,8 +577,8 @@ export default {
this.pageToast('The formula [' + formulaID + '] has been registered.')
},
removeFormula: function (formulaID) {
for (const chart in chartData.getAllChartsData()) {
if (chartData.getAllChartsData()[chart].mappingID === formulaID) {
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === formulaID) {
dataStreamBuffer.removeByChart(chart)
this.removeChart(chart)
}
@@ -510,7 +586,7 @@ export default {
this.pageToast('The formula [' + formulaID + '] has been removed.')
},
startAllClick: async function () {
for (const device of taskInfo.getDeviceListRegister()) {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
@@ -520,8 +596,35 @@ export default {
this.refreshDevicesWindow()
this.pageToast('The all devices have been started')
},
recordAllClick: async function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
await this.$refs.devices_ref.$refs.pdlist_ref.record(device)
}
}
this.refreshDevicesWindow()
this.pageToast('The all devices are recording')
},
stimulateAllClick: async function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
await this.$refs.devices_ref.$refs.pdlist_ref.stimulate(device)
}
}
this.refreshDevicesWindow()
this.pageToast('The all devices are stimulating')
},
startDevice: async function (device) {
chartData.defaultChartGenerator(device.id, null, 'device')
await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: device.id,
_chartID: null,
_type: 'device',
})
if (!taskInfo.getDeviceInfo(device.id).info.isSync) {
if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Neulive')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
@@ -530,7 +633,7 @@ export default {
instruction: 'record',
}))
} else if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Elite')) {
console.log('elite')
// console.log('elite')
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
@@ -541,11 +644,11 @@ export default {
device.isSync = true
// this.$emit('refreshSetting')
// ### show device info
console.log(taskInfo.getDeviceInfo(device.id))
// console.log(taskInfo.getDeviceInfo(device.id))
}
},
stopAllClick: function () {
for (const device of taskInfo.getDeviceListRegister()) {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.stopDevice(device)
} else {
@@ -573,22 +676,37 @@ export default {
device.stopping = true
}
},
checkStiStatus: function () {
this.$refs.devices_ref.checkStiStatus()
},
exit: function () {
this.$router.push({
path: '/',
})
},
...mapActions('taskContent', [
taskTypes.chart.initCustomizedFromCache,
taskTypes.chart.saveToCache,
taskTypes.chart.add,
taskTypes.chart.defaultChartGenerator,
taskTypes.chart.reset,
taskTypes.chart.refreshData,
taskTypes.chart.refreshChart,
taskTypes.chart.refreshPanel,
]),
...mapActions('device', [deviceTypes.triggerChart.setDisplay]),
},
watch: {
},
async mounted () {
this.deviceList = await taskInfo.updateTaskInfo(this.$route.params.taskID)
console.log(taskInfo.getTaskInfo())
await taskInfo.updateTaskInfo(this.$route.params.taskID)
// console.log(taskInfo.getTaskInfo())
// this.screenHeight = document.documentElement.clientHeight
this.pageMqttSub(taskInfo.getTaskInfo().controllerID)
console.log(taskInfo.getTaskInfo().controllerID)
// console.log(taskInfo.getTaskInfo().controllerID)
await this.pageInit()
// this.chartNumber = taskInfo.getTaskInfo().chartNumber
this.dataRefreshTimer = setInterval(this.refreshData, 100)
@@ -599,8 +717,8 @@ export default {
// }
},
destroyed () {
taskInfo.destroyed()
for (const chart in chartData.getAllChartsData()) {
taskInfo.destroyed(this.deviceList)
for (const chart in this.chartData) {
this.removeChart(chart)
}
clearInterval(this.dataRefreshTimer)
@@ -0,0 +1,614 @@
<template>
<div class="task-content">
<va-modal
v-model="showShutdownModal"
size="small"
:title="'SHUTDOWN'"
:message="'The controller shutdown.'"
:okText=" $t('modal.confirm') "
@ok="exit()"
@cancel="exit()"
/>
<va-modal
v-model="showRemoveChartModal"
size="small"
:title="'REMOVE CHART'"
:message="'The chart will be removed.'"
:okText=" $t('modal.confirm') "
@ok="removeChart(null)"
/>
<div class="row row-equal">
<div v-if="showDeviceSetting" class="flex md12 xl4 xs12 sm12">
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<task-devices-g-m :ref="'devices_ref'" @add="addDevice($event)" @remove="removeDevice($event)" @refreshSetting="refreshChartSetting($event)" class="pa-0" />
</div>
<!-- <div class="flex sm12 xl12 md12 xs12">
<task-formula :ref="'formula_ref'" @add="addFormula($event)" @remove="removeFormula($event)" class="pa-0" />
</div> -->
</div>
</div>
<div :class="chartAreaClass">
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<div class="flex sm12 xl12 md12 xs12" style="text-align: center;">
<va-card
stripe="primary"
>
<va-button class="no-dark-style-va-button" v-if="!showDeviceSetting" color="primary" target="_blank" @click="showDeviceSettingClick()" small>
SHOW DEVICES
</va-button>
<va-button class="no-dark-style-va-button" v-if="showDeviceSetting" color="primary" target="_blank" @click="hideDeviceSettingClick()" small>
HIDE DEVICES
</va-button>
<!-- <va-button class="" color="success" target="_blank" @click="startAllClick()" small>
START ALL
</va-button>
<va-button class="no-dark-style-va-button" color="danger" target="_blank" @click="stopAllClick()" small>
STOP ALL
</va-button> -->
<va-button class="no-dark-style-va-button" color="primary" target="_blank" @click.stop="addChart()" small>
ADD CHART
</va-button>
</va-card>
</div>
<div :key="chartKey">
<div
v-for="chart in chartData"
:key="chart.chartID"
>
<canvas-chart
:id="'chart' + chart.chartID"
:ref="'chart_ref'"
:cardheight="'400px'"
:cardwidth="'auto'"
:chartID="chart.chartID"
@init="initChart(chart.chartID)"
@remove="removeChartCheck(chart.chartID)"
/>
</div>
</div>
<div style="height: 150px;">
</div>
</div>
</div>
</div>
</div>
<task-content-modal />
</div>
</template>
<script>
import { dataStreamBuffer } from '../../../data/task/DataStreamBuffer'
import { taskInfo } from '../../../data/task/TaskInfo'
import CanvasChart from './chart/CanvasChart'
import TaskContentModal from './TaskContentModal.vue'
import { mapFields } from 'vuex-map-fields'
import TaskDevicesGM from './controller/neulive-gimer/TaskDevicesGM.vue'
import { mapActions } from 'vuex'
import taskTypes from '@/store/modules/task/content/types'
// TODO change chart data tag format
export default {
name: 'TaskContentGM',
components: {
CanvasChart,
TaskDevicesGM,
TaskContentModal,
},
computed: {
...mapFields('taskContent', [
'deviceList',
'showTaskModal',
'taskModalText',
'chartData',
]),
},
data () {
return {
// modal
showShutdownModal: false,
showRemoveChartModal: false,
// refresh timer
dataRefreshTimer: null,
// chart refresh key
chartKey: 0,
chartNumber: 0,
selectChartID: null,
showDeviceSetting: true,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl8', 'pa-0'],
getParmDone: false,
}
},
mqtt: {
async '+/get_device_info_all/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
this.refreshDevicesWindow()
},
async '+/get_device_info/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
this.refreshDevicesWindow()
this.getParmDone = true
this.checkStiStatus()
},
async '+/device_parameter/+' (data, topic) {
await taskInfo.deviceParameter(String.fromCharCode.apply(null, data))
this.refreshDevicesWindow()
},
async '+/device_parameter_value/+' (data, topic) {
await taskInfo.deviceParameter(String.fromCharCode.apply(null, data))
this.refreshDevicesWindow()
},
async '+/hardware_device/+' (data, topic) {
await taskInfo.deviceInfo(String.fromCharCode.apply(null, data), this.deviceList)
if (this.$refs.devices_ref != null) {
await this.$refs.devices_ref.loadingDone()
}
},
// listen datastream [ topic: controllerID/data_server/device_data_stream/deviceID/channel ]
async '+/data_server/device_data_stream/+/+' (data, topic) {
const topicSplit = topic.split('/')
if (this.getParmDone & !taskInfo.getDeviceInfoByRawID(parseInt(topicSplit[3])).info.isSync) {
const result = await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: taskInfo.getDeviceIDByRaw(parseInt(topicSplit[3])),
_chartID: null,
_type: 'device',
})
if (result) {
taskInfo.deviceStart(parseInt(topicSplit[3]), this.deviceList)
}
}
dataStreamBuffer.into(String.fromCharCode.apply(null, data), parseInt(topicSplit[3]), (parseInt(topicSplit[4]) + 1))
},
'+/device_save_path/+' (data, topic) {
taskInfo.deviceSavePath(String.fromCharCode.apply(null, data))
// console.log(taskInfo.getTaskInfo())
},
'+/device_save_path_set/+' (data, topic) {
taskInfo.deviceSavePath(String.fromCharCode.apply(null, data))
// console.log(taskInfo.getTaskInfo())
},
async '+/broadcast' (data, topic) {
const mes = String.fromCharCode.apply(null, data).split(':')
// let content
switch (mes[0]) {
case 'state':
switch (mes[1]) {
case 'device_available':
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'hardware_device/0',
}))
break
}
break
case 'disconnected':
taskInfo.deviceDisconnected(mes[1])
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'get_device_info/0',
device: parseInt(mes[1]),
}))
break
case 'connected':
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'get_device_info/0',
device: parseInt(mes[1]),
}))
taskInfo.deviceConnected(mes[1])
break
case 'shutdown':
taskInfo.controllerShutdown(this.deviceList)
this.showShutdownModal = true
break
case 'start':
if (!taskInfo.getDeviceInfoByRawID(parseInt(mes[1])).info.isSync) {
taskInfo.deviceStart(parseInt(mes[1]), this.deviceList)
const result = await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: taskInfo.getDeviceIDByRaw(parseInt(mes[1])),
_chartID: null,
_type: 'device',
})
if (result) {
taskInfo.deviceStart(parseInt(mes[1]), this.deviceList)
}
}
break
case 'stop':
if (mes[1] === 'all') {
return false
}
taskInfo.deviceStop(parseInt(mes[1]), this.deviceList)
break
case 'device_timeout':
this.pageToast('The device [' + taskInfo.getDeviceIDByRaw(parseInt(mes[1])) + '] is timeout, please check its status or parameters.')
break
case 'device_mission_done':
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: parseInt(mes[1]),
instruction: 'interrupt',
}))
this.pageToast('The device [' + taskInfo.getDeviceIDByRaw(parseInt(mes[1])) + '] mission is done.')
break
default:
break
}
// console.log(String.fromCharCode.apply(null, data), topic)
},
},
methods: {
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
pageMqttSub: function (id) {
this.mqttSub(id + '/hardware_device/+')
this.mqttSub(id + '/broadcast')
this.mqttSub(id + '/get_device_info_all/+')
this.mqttSub(id + '/get_device_info/+')
this.mqttSub(id + '/device_parameter/+')
this.mqttSub(id + '/device_parameter_value/+')
},
pageMqttUnSub: function (id) {
for (const device in taskInfo.getTaskInfo().deviceList) {
this.mqttUnSub(id + '/data_server/device_data_stream/' + device + '/+')
}
this.mqttUnSub(id + '/hardware_device/+')
this.mqttUnSub(id + '/broadcast')
this.mqttUnSub(id + '/get_device_info_all/+')
this.mqttUnSub(id + '/get_device_info/+')
this.mqttUnSub(id + '/device_parameter/+')
this.mqttUnSub(id + '/device_parameter_value/+')
},
pageInit: async function () {
if (await this.$refs.devices_ref != null) {
await this.$refs.devices_ref.loadingStart()
}
// let _parameter, _content
// this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
// header: 'device_parameter/0',
// device: 7,
// parameter: 'RECORDING_CH|AXIS_CH',
// content: '7|1',
// }))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'hardware_device/0',
}))
this.chartNumber = Object.keys(this.chartData).length
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
refreshData: function () {
// TODO don't need to refresh chart with stop status
for (const chart in this.chartData) {
this.taskContentChartRefreshData({
chartID: parseInt(chart),
})
}
},
refreshDevicesWindow: function () {
// console.log(taskInfo.getTaskInfo())
if (this.$refs.devices_ref != null) {
this.$refs.devices_ref.refresh()
}
},
checkStiStatus: function () {
this.$refs.devices_ref.checkStiStatus()
},
addChart: async function () {
const defaultID = {}
const update = async () => {
for (const chart in this.chartData) {
defaultID[chart] = this.chartData[chart].mappingID
}
this.taskContentChartAdd({
chartID: this.chartNumber,
maxDots: 4000,
gridLength: 1,
})
this.chartNumber++
this.chartKey++
}
await update()
for (const chart in this.chartData) {
this.chartData[chart].mappingID = defaultID[chart]
}
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
// this.pageToast('The chart has been added.')
return this.chartNumber - 1
},
refreshChartSetting: async function () {
const defaultID = {}
const update = async () => {
for (const chart in this.chartData) {
defaultID[chart] = this.chartData[chart].mappingID
}
this.chartKey++
}
await update()
for (const chart in this.chartData) {
this.chartData[chart].mappingID = defaultID[chart]
}
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
},
initChart: function (chartID) {
this.taskContentChartReset({
chartID: chartID,
mappingID: null,
cardName: null,
})
// this.pageToast('The chart has been Initialized.')
},
removeChartCheck: function (chartID) {
this.selectChartID = chartID
this.showRemoveChartModal = true
},
removeChart: async function (chartID) {
if (chartID == null) {
chartID = this.selectChartID
}
dataStreamBuffer.removeByChart(chartID)
if (this.chartData[chartID] != null) {
const defaultID = {}
const update = async () => {
for (const chart in this.chartData) {
if (chart !== chartID) {
defaultID[chart] = this.chartData[chart].mappingID
}
}
delete this.chartData[chartID]
this.chartKey++
}
await update()
for (const chart in this.chartData) {
this.chartData[chart].mappingID = defaultID[chart]
}
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
}
// this.pageToast('The chart has been removed.')
},
showDeviceSettingClick: function () {
this.showDeviceSetting = true
this.chartAreaClass[3] = 'md12'
this.chartAreaClass[4] = 'xl8'
},
hideDeviceSettingClick: function () {
this.showDeviceSetting = false
this.chartAreaClass[3] = 'md12'
this.chartAreaClass[4] = 'xl12'
},
addMappingChart: async function (deviceID) {
// if have chart with device ID
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === deviceID) {
return false
}
}
const chart = await this.addChart()
await this.$nextTick(async () => {
this.chartData[chart].mappingID = deviceID
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
})
return chart
},
addDevice: async function (deviceID) {
this.mqttSub(taskInfo.getTaskInfo().controllerID + '/data_server/device_data_stream/' + taskInfo.getRawDeviceID(deviceID) + '/+')
this.addMappingChart(deviceID)
this.pageToast('The device [' + deviceID + '] has been registered.')
},
removeDevice: function (deviceID) {
this.mqttUnSub(taskInfo.getTaskInfo().controllerID + '/data_server/device_data_stream/' + taskInfo.getRawDeviceID(deviceID) + '/+')
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === deviceID) {
dataStreamBuffer.removeByChart(chart)
this.removeChart(chart)
}
}
this.pageToast('The device [' + deviceID + '] has been removed.')
},
addFormula: async function (formulaID) {
const chart = await this.addChart()
await this.$nextTick(async () => {
this.chartData[chart].mappingID = formulaID
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.forEach(ref => {
ref.refreshMappingSelect()
})
}
})
this.pageToast('The formula [' + formulaID + '] has been registered.')
},
removeFormula: function (formulaID) {
for (const chart in this.chartData) {
if (this.chartData[chart].mappingID === formulaID) {
dataStreamBuffer.removeByChart(chart)
this.removeChart(chart)
}
}
this.pageToast('The formula [' + formulaID + '] has been removed.')
},
startAllClick: async function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
await this.$refs.devices_ref.$refs.pdlist_ref.start(device)
}
}
this.refreshDevicesWindow()
this.pageToast('The all devices have been started')
},
startDevice: async function (device) {
await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
_itemID: device.id,
_chartID: null,
_type: 'device',
})
if (!taskInfo.getDeviceInfo(device.id).info.isSync) {
if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Neulive')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'record',
}))
} else if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Elite')) {
// console.log('elite')
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'start',
}))
}
device.stopping = false
device.isSync = true
// this.$emit('refreshSetting')
// ### show device info
// console.log(taskInfo.getDeviceInfo(device.id))
}
},
stopAllClick: function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.stopDevice(device)
} else {
this.$refs.devices_ref.stopDevice(device)
}
}
this.pageToast('The all devices have been stopped')
},
stopDevice: async function (device) {
dataStreamBuffer.removeByDevice(taskInfo.getRawDeviceID(device.id))
if (taskInfo.getDeviceInfo(device.id).info.isSync) {
if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Neulive')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'stop_record',
}))
} else if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Elite')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'interrupt',
}))
}
device.stopping = true
}
},
exit: function () {
this.$router.push({
path: '/',
})
},
...mapActions('taskContent', [
taskTypes.chart.add,
taskTypes.chart.refreshData,
taskTypes.chart.defaultChartGenerator,
taskTypes.chart.reset,
]),
},
watch: {
},
async mounted () {
await taskInfo.updateTaskInfo(this.$route.params.taskID)
// console.log(taskInfo.getTaskInfo())
// this.screenHeight = document.documentElement.clientHeight
this.pageMqttSub(taskInfo.getTaskInfo().controllerID)
// console.log(taskInfo.getTaskInfo().controllerID)
await this.pageInit()
// this.chartNumber = taskInfo.getTaskInfo().chartNumber
this.dataRefreshTimer = setInterval(this.refreshData, 100)
// const _this = this
// window.onresize = function () { // define the event of window size changing
// // _this.screenWidth = document.documentElement.clientWidth
// _this.screenHeight = document.documentElement.clientHeight
// }
},
destroyed () {
taskInfo.destroyed(this.deviceList)
for (const chart in this.chartData) {
this.removeChart(chart)
}
clearInterval(this.dataRefreshTimer)
this.pageMqttUnSub(taskInfo.getTaskInfo().controllerID)
},
}
</script>
<style lang="scss">
// .area-style {
// height: 100%;
// overflow-y: hidden;
// overflow-x: hidden;
// @include media-breakpoint-only(xl) {
// height: 94vh;
// overflow-y: scroll;
// overflow-x: hidden;
// }
// }
.no-dark-style-va-button {
&:focus {
filter: brightness(100%);
}
}
</style>
@@ -0,0 +1,33 @@
<template>
<va-modal
v-model="showTaskModal"
size="small"
:title="''"
:message="taskModalText"
:okText=" $t('modal.confirm') "
:cancelText=" $t('modal.cancel') "
/>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
components: {
},
props: {
},
computed: {
...mapFields('taskContent', [
'showTaskModal',
'taskModalText',
]),
},
data () {
return {
}
},
methods: {
},
}
</script>
+230 -153
View File
@@ -2,8 +2,19 @@
<div class="flex" :class="cardclass">
<va-card>
<template slot="header">
<va-icon name="fa fa-area-chart mr-3" color="primary"/>
<h5 class="mt-0 mb-0">{{nameCard}}</h5>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<h5 class="mt-0 mb-0"><va-icon name="fa fa-area-chart mr-3" color="primary"/>{{nameCard}}</h5>
</div>
<div class="flex sm7 xl7 md7 xs7 pa-0 ma-0" style="text-align: right;">
<va-button class="pa-0 ma-0 circle-button" small color="dark" @click="remove()">
<va-icon
name="fa fa-times"
color="white"
/>
</va-button>
</div>
</div>
</template>
<va-tabs v-model="tabValue" class="mb-3" style="width: 100%; min-width: 100px;">
<va-tab>
@@ -12,106 +23,101 @@
<va-tab>
{{'SOURCE'}}
</va-tab>
<!-- <va-tab>
{{'SETTING'}}
</va-tab> -->
</va-tabs>
<div v-if="tabValue == 0" >
<div v-if="data.legend.data.length != 0">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" :auto-resize="true"></chart>
</div>
<div v-else>
Please add the source from device.
<div v-if="chartData[chartID].legend.data.length != 0">
<chart :key="taskContentChartKey" :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" :auto-resize="true"></chart>
</div>
</div>
<div v-else-if="tabValue == 1" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" >
<canvas-chart-source :chartID="chartID" :gridInfo="data.grid"></canvas-chart-source>
<canvas-chart-source :chartID="chartID" />
</div>
<div v-else :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" >
<canvas-chart-setting></canvas-chart-setting>
<!-- <canvas-chart-setting :chartID="chartID"></canvas-chart-setting> -->
</div>
<div class="flex row">
<div class="flex row xs12 md12 xl12 sm12" style="text-align: center;" >
<!-- <va-select
class="ma-1"
:on-change="mappingSelectChange()"
small
:label="'MAPPING'"
v-model="mappingSelect"
textBy="name"
:options="mappingOptions"
/> -->
<va-input
v-if="data.chartXMode === 'time'"
class="ma-1"
@keyup.enter="timeIntervalChange(timeIntervalInput)"
@blur="timeIntervalChange(timeIntervalInput)"
v-model="timeIntervalInput"
:label="'time-interval'"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
<va-input
v-if="data.chartXMode === 'value'"
class="ma-1"
@keyup.enter="xIntervalChange(xIntervalInput)"
@blur="xIntervalChange(xIntervalInput)"
v-model="xIntervalInput"
:label="'x-grid'"
>
<p slot="append" style="margin-right: 0;" small>
{{data.chartXUnit}}
</p>
</va-input>
<va-input
class="ma-1"
@keyup.enter="yIntervalChange(yIntervalInput)"
@blur="yIntervalChange(yIntervalInput)"
v-model="yIntervalInput"
:label="'y-grid'"
>
<p slot="append" style="margin-right: 0;" small>
{{data.chartYUnit}}
</p>
</va-input>
<va-input
v-if="data.chartDevice === 'neulive'"
class="ma-1"
@keyup.enter="threeAxisIntervalChange(threeAxisIntervalInput)"
@blur="threeAxisIntervalChange(threeAxisIntervalInput)"
v-model="threeAxisIntervalInput"
:label="'3axis-interval'"
>
<p slot="append" style="margin-right: 0;" small>
g
</p>
</va-input>
<!-- <va-button class="no-dark-style-va-button" small @click="init()">INIT</va-button> -->
<va-button small color="dark" @click="remove()">REMOVE</va-button>
</div>
<!-- <div class="xs2 md2 xl2 sm2" style="text-align: center;" >
<va-button class="no-dark-style-va-button" small @click="init()">INIT</va-button>
<va-button small color="dark" @click="remove()">REMOVE</va-button>
</div> -->
<div v-if="this.$refs.chart_ref != null">
<va-button small class="mb-3" @click="selectAllSeries">
SELECT ALL
</va-button>
<va-button small class="mb-3" @click="inverseSelectAllSeries">
INVERSE SELECT
</va-button>
</div>
<div class="row flex align--left ma-0 pa-0">
<va-input
v-if="chartData[chartID].chartXMode === 'time'"
class="mx-1"
@keyup.enter="timeIntervalChange(timeIntervalInput)"
@blur="timeIntervalChange(timeIntervalInput)"
v-model="timeIntervalInput"
:label="'time-grid'"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
<va-input
v-if="chartData[chartID].chartXMode === 'value'"
class="mx-1"
@keyup.enter="xIntervalChange(xIntervalInput)"
@blur="xIntervalChange(xIntervalInput)"
v-model="xIntervalInput"
:label="'x-grid'"
>
<p slot="append" style="margin-right: 0;" small>
{{chartData[chartID].chartXUnit}}
</p>
</va-input>
<va-input
class="mx-1"
@keyup.enter="yIntervalChange(yIntervalInput)"
@blur="yIntervalChange(yIntervalInput)"
v-model="yIntervalInput"
:label="'y-grid'"
>
<p slot="append" style="margin-right: 0;" small>
{{chartData[chartID].chartYUnit}}
</p>
</va-input>
<va-input
v-if="chartData[chartID].chartDevice === 'neulive'"
class="mx-1"
@keyup.enter="threeAxisIntervalChange(threeAxisIntervalInput)"
@blur="threeAxisIntervalChange(threeAxisIntervalInput)"
v-model="threeAxisIntervalInput"
:label="'3axis-grid'"
>
<p slot="append" style="margin-right: 0;" small>
g
</p>
</va-input>
</div>
<div class="row flex ma-0 pa-0">
<canvas-chart-filter :chartID="chartID" :filterIndex="0" />
<canvas-chart-filter :chartID="chartID" :filterIndex="1" />
<canvas-chart-filter :chartID="chartID" :filterIndex="2" />
</div>
</va-card>
<canvas-chart-filter-popup :ref="'filter_popup_ref'" :chartID="chartID" :chartRef="this.$refs.chart_ref" />
</div>
</template>
<script>
// import { InitChartsData, getChartsData } from '../../../../../data/task/ChartsManagement'
import CanvasChartSetting from './CanvasChartSetting'
import CanvasChartFilter from './CanvasChartFilter.vue'
import CanvasChartFilterPopup from './CanvasChartFilterPopup.vue'
import CanvasChartSource from './CanvasChartSource'
import { taskInfo } from '../../../../data/task/TaskInfo'
import { chartData } from '../../../../data/task/ChartsManagement'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import taskTypes from '@/store/modules/task/content/types'
export default {
name: 'CanvasChart',
components: {
CanvasChartSetting,
// CanvasChartSetting,
CanvasChartSource,
CanvasChartFilter,
CanvasChartFilterPopup,
},
props: {
chartID: {
@@ -133,18 +139,20 @@ export default {
default: 'auto',
},
},
created () {
computed: {
...mapFields('taskContent', [
'chartData',
'taskContentChartKey',
]),
},
data () {
return {
// chartData: InitChartsData(this.chartID, 2000, 1, 1),
data: null,
tabValue: 0,
chartKey: 0,
data: chartData.getChartsData(this.chartID),
resizeTimer: null,
mappingSelect: '',
mappingOptions: taskInfo.getMappingList(),
nameCard: chartData.getAllChartsData()[this.chartID].cardName,
nameCard: '',
initDone: false,
timeIntervalInput: 'auto',
threeAxisIntervalInput: 'auto',
@@ -152,64 +160,92 @@ export default {
yIntervalInput: 'auto',
}
},
computed: {
},
watch: {
},
methods: {
timeIntervalChange: function (val) {
chartData.getAllChartsData()[this.chartID].xAxis.forEach(x => {
if (x.type === 'time') {
x.interval = val * 1000
}
})
if (!isNaN(parseInt(val))) {
this.chartData[this.chartID].xAxis.forEach(x => {
if (x.type === 'time') {
x.interval = val * 1000
}
})
} else {
this.timeIntervalInput = 'auto'
this.chartData[this.chartID].xAxis.forEach(x => {
if (x.type === 'time') {
x.interval = 500 * 1000
}
})
}
},
xIntervalChange: function (val) {
chartData.getAllChartsData()[this.chartID].xAxis.forEach(x => {
if (x.type === 'value') {
x.interval = val * 1000
// const maxList = []
// const minList = []
// x.seriesIndex.forEach(i => {
// maxList.push(Math.max(...chartData.getAllChartsData()[this.chartID].series[i].data.map(p => p[0])))
// minList.push(Math.min(...chartData.getAllChartsData()[this.chartID].series[i].data.map(p => p[0])))
// })
// const max = Math.max(maxList)
// const min = Math.min(minList)
// const center = (max + min) / 2
// x.max = center + (max - min)
// x.min = center - (max - min)
// x.max = (parseInt(x.max / (val * 1000)) + 1) * (val * 1000)
// x.min = (parseInt(x.min / (val * 1000)) - 1) * (val * 1000)
}
})
xIntervalChange: async function (val) {
if (!isNaN(parseInt(val))) {
this.chartData[this.chartID].xAxis.forEach(x => {
if (x.type === 'value') {
x.interval = val * 1000
}
})
} else {
this.timeIntervalInput = 'auto'
this.chartData[this.chartID].xAxis.forEach(x => {
if (x.type === 'value') {
x.interval = null
}
})
}
await this.taskContentChartSaveToCache()
},
yIntervalChange: function (val) {
chartData.getAllChartsData()[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) <= 255) {
y.interval = val * 1000
const maxList = []
const minList = []
y.seriesIndex.forEach(i => {
maxList.push(Math.max(...chartData.getAllChartsData()[this.chartID].series[i].data.map(p => p[1])))
minList.push(Math.min(...chartData.getAllChartsData()[this.chartID].series[i].data.map(p => p[1])))
})
const max = Math.max(maxList)
const min = Math.min(minList)
const center = (max + min) / 2
y.max = center + (max - min)
y.min = center - (max - min)
y.max = (parseInt(y.max / (val * 1000)) + 1) * (val * 1000)
y.min = (parseInt(y.min / (val * 1000)) - 1) * (val * 1000)
}
})
yIntervalChange: async function (val) {
if (!isNaN(parseInt(val))) {
this.chartData[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) <= 255) {
if (this.chartData[this.chartID].chartYUnit === 'uV') {
y.interval = parseInt(val)
// console.log(y.interval)
} else {
y.interval = val * 1000
}
const maxList = []
const minList = []
y.seriesIndex.forEach(i => {
maxList.push(Math.max(...this.chartData[this.chartID].series[i].data.map(p => p[1])))
minList.push(Math.min(...this.chartData[this.chartID].series[i].data.map(p => p[1])))
})
const max = Math.max(maxList)
const min = Math.min(minList)
const center = (max + min) / 2
y.max = center + (max - min)
y.min = center - (max - min)
y.max = (parseInt(y.max / y.interval) + 1) * y.interval
y.min = (parseInt(y.min / y.interval) - 1) * y.interval
}
})
} else {
this.timeIntervalInput = 'auto'
this.chartData[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) <= 255) {
y.interval = null
y.max = 'dataMax'
y.min = 'dataMin'
}
})
}
await this.taskContentChartSaveToCache()
},
threeAxisIntervalChange: function (val) {
chartData.getAllChartsData()[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) >= 255) {
y.interval = val * 1
}
})
if (!isNaN(parseInt(val))) {
this.chartData[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) >= 255) {
y.interval = val * 1
}
})
} else {
this.timeIntervalInput = 'auto'
this.chartData[this.chartID].yAxis.forEach(y => {
if (parseInt(y.channel) >= 255) {
y.interval = null
}
})
}
},
init: function () {
this.mappingSelect = ''
@@ -230,44 +266,79 @@ export default {
this.mappingOptions.forEach(item => {
if (item.id === itemID) {
this.mappingSelect = item
chartData.getAllChartsData()[this.chartID].mappingID = item.id
this.chartData[this.chartID].mappingID = item.id
}
})
},
refreshMappingSelect: async function () {
this.setMappingSelect(chartData.getAllChartsData()[this.chartID].mappingID)
if (chartData.getAllChartsData()[this.chartID].mappingID !== -1 && chartData.getAllChartsData()[this.chartID].mappingID != null) {
this.setMappingSelect(this.chartData[this.chartID].mappingID)
if (this.chartData[this.chartID].mappingID !== -1 && this.chartData[this.chartID].mappingID != null) {
taskInfo.getMappingList().forEach(item => {
if (item.id === chartData.getAllChartsData()[this.chartID].mappingID) {
if (item.id === this.chartData[this.chartID].mappingID) {
this.setChartName(item.name)
}
})
}
},
setChartName: function (name) {
chartData.getAllChartsData()[this.chartID].cardName = name
this.chartData[this.chartID].cardName = name
this.nameCard = name
},
mappingSelectChange: function () {
if (this.initDone) {
if (this.mappingSelect.id != null) {
chartData.getAllChartsData()[this.chartID].mappingID = this.mappingSelect.id
} else {
chartData.getAllChartsData()[this.chartID].mappingID = -1
}
}
},
resize: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
},
showFilterPopup (params) {
this.$refs.filter_popup_ref.showPanel(params)
},
setCursor (params) {
this.$refs.chart_ref.chart._zr.setCursorStyle('copy')
},
legendChange (params) {
const activeLegendLen = Object.values(params.selected).filter(val => val === true).length
let index = 0
for (const [gridIndex, value] of Object.values(params.selected).entries()) { // [TODO] 技術債,目前real time chart一個grid只對應一條series
if (value === true) {
this.chartData[this.chartID].grid[gridIndex].height = (85 / activeLegendLen) + '%'
this.chartData[this.chartID].grid[gridIndex].top = index * (85 / activeLegendLen) + 1 + '%'
index++
} else {
this.chartData[this.chartID].grid[gridIndex].height = '0%'
this.chartData[this.chartID].grid[gridIndex].top = '0%'
}
}
},
selectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendAllSelect',
})
},
inverseSelectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendInverseSelect',
})
},
...mapActions('taskContent', [
taskTypes.chart.saveToCache,
]),
},
async mounted () {
this.data = this.chartData[this.chartID]
this.resizeTimer = setInterval(this.resize, 1000)
this.nameCard = chartData.getAllChartsData()[this.chartID].cardName
this.nameCard = this.chartData[this.chartID].cardName
await this.refreshMappingSelect()
this.initDone = true
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.chart._zr.on('click', this.showFilterPopup)
this.$refs.chart_ref.chart._zr.on('mousemove', this.setCursor)
this.$refs.chart_ref.chart.on('legendselectchanged', this.legendChange)
this.$refs.chart_ref.chart.on('legendselectall', this.legendChange)
this.$refs.chart_ref.chart.on('legendinverseselect', this.legendChange)
}
// this.timeIntervalInput = '500'
// this.timeIntervalChange('500')
},
destroyed () {
clearInterval(this.resizeTimer)
@@ -277,6 +348,12 @@ export default {
</script>
<style lang="scss">
.circle-button {
height: 25px;
width: 25px;
border-radius: '50%';
}
.no-dark-style-va-button {
&:focus {
filter: brightness(100%);
@@ -0,0 +1,207 @@
<template>
<div class="row flex pa-0 ma-0">
<div class="filter-input my-0 pa-0 mx-1">
<va-select
small
noClear
:label="'FILTER ' + (filterIndex + 1)"
v-model="selectFilter"
textBy="name"
:options="optionFilter"
/>
</div>
<va-input
:label="'order'"
class="mx-1 order-input"
v-model="order"
/>
<va-input
v-if="[1, 2, 3].indexOf(selectFilter.id) >= 0"
:label="'FREQ'"
class="mx-1 freq-input"
v-model="freq2"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MIN'"
class="mx-1 freq-input"
v-model="freq0"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MAX'"
class="mx-1 freq-input"
v-model="freq1"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-button
flat
small
:color="buttonColor"
class="circle-button pa-0 mx-0 filter-apply-button"
:icon="buttonIcon"
@click.stop="updateFilterInfo(filterIndex)"/>
</div>
</template>
<script>
import { taskInfo } from '@/data/task/TaskInfo'
import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
export default {
name: 'canvas-chart-filter',
props: {
chartID: {
type: Number,
required: true,
},
filterIndex: {
type: Number,
required: true,
},
},
computed: {
...mapFields('taskContent', [
'filter',
'chartData',
]),
buttonIcon () {
if (this.chartData[this.chartID].series.length > 0 && this.chartData[this.chartID].series.every(item => item != null)) {
return this.chartData[this.chartID].series.every(item => item.filter[this.filterIndex].filter != null) ? 'fa fa-stop' : 'fa fa-play'
}
return 'fa fa-play'
},
buttonColor () {
if (this.chartData[this.chartID].series.length > 0 && this.chartData[this.chartID].series.every(item => item != null)) {
return this.chartData[this.chartID].series.every(item => item.filter[this.filterIndex].filter != null) ? 'danger' : 'primary'
}
return 'primary'
},
},
data () {
return {
selectFilter: {
id: 1,
name: 'notch',
},
order: 20,
freq0: 60,
freq1: 60,
freq2: 60,
optionFilter: [
{
id: 1,
name: 'notch',
},
{
id: 2,
name: 'lowpass',
},
{
id: 3,
name: 'highpass',
},
{
id: 4,
name: 'bandstop',
},
{
id: 5,
name: 'bandpass',
},
],
}
},
methods: {
async updateFilterInfo (index) {
let closeFilter = false
for (let i = 0; i < this.chartData[this.chartID].series.length; i++) {
if (this.chartData[this.chartID].series[i].filter[this.filterIndex].filter != null) {
this.chartData[this.chartID].series[i].filter[this.filterIndex].filter = null
closeFilter = true
}
}
if (closeFilter) {
return false
}
let fs = 0
let sos = []
for (let i = 0; i < this.chartData[this.chartID].series.length; i++) {
const deviceID = this.chartData[this.chartID].series[i].yAxisSource[0]
// const _fs = taskInfo.getDeviceInfo(deviceID).parameterSet.SAMPLE_RATE
const _fs = 1000
const _chNum = taskInfo.getDeviceInfo(deviceID).parameterSet.CH_NUMBER
// console.log(_fs, _chNum)
if (fs !== _fs) {
fs = _fs
const ret = await api.analysis.IIRFilter({
type: this.selectFilter.name,
order: this.order,
fs: parseFloat(_fs / _chNum),
f0: parseFloat(this.freq0),
f1: parseFloat(this.freq1),
f2: parseFloat(this.freq2),
})
if (ret.status !== 200) {
return false
}
const _sosRet = ret.data.split('/')
const _sos = []
_sosRet.forEach((v, vi) => {
const _tmp = v.split(',')
const _arr = []
_tmp.forEach(s => {
_arr.push(parseFloat(s))
})
_sos.push(_arr)
})
sos = _sos
}
this.chartData[this.chartID].series[i].filter[index].type = this.selectFilter.name
this.chartData[this.chartID].series[i].filter[index].order = this.order
// sampling rate changed by channel number
this.chartData[this.chartID].series[i].filter[index].fs = parseFloat(_fs / _chNum)
this.chartData[this.chartID].series[i].filter[index].f0 = parseFloat(this.freq0)
this.chartData[this.chartID].series[i].filter[index].f1 = parseFloat(this.freq1)
this.chartData[this.chartID].series[i].filter[index].f2 = parseFloat(this.freq2)
this.chartData[this.chartID].series[i].filter[index].sos = sos
this.chartData[this.chartID].series[i].filter[index].filter = IIRFilter.init(sos)
}
// console.log(this.chartData[this.chartID])
},
},
}
</script>
<style lang='scss'>
.filter-input {
max-width: 100px;
}
.freq-input {
max-width: 80px;
}
.order-input {
max-width: 50px;
}
.filter-apply-button {
max-width: 25px;
}
</style>
@@ -0,0 +1,87 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showModal"
:title="'Grid ' + (gridIndex + 1)"
:position="position"
cancelText=""
okText=""
size="small"
>
<div class="row flex ma-0 pa-0">
<canvas-chart-individual-filter :chartID="chartID" :seriesIndex="gridIndex" :filterIndex="0" />
<canvas-chart-individual-filter :chartID="chartID" :seriesIndex="gridIndex" :filterIndex="1" />
<canvas-chart-individual-filter :chartID="chartID" :seriesIndex="gridIndex" :filterIndex="2" />
</div>
</va-modal>
</div>
</template>
<script>
import CanvasChartIndividualFilter from './CanvasChartIndividualFilter.vue'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'canvas-chart-filter-popup',
components: {
CanvasChartIndividualFilter,
},
props: {
chartID: {
type: Number,
required: true,
},
chartRef: {
type: Object,
},
},
computed: {
...mapFields('taskContent', [
'chartData',
]),
},
data () {
return {
showModal: false,
position: 'center',
gridIndex: 0,
}
},
methods: {
showPanel (params) {
if (this.chartRef != null) {
let _gridIndex = 0
for (_gridIndex = 0; _gridIndex < this.chartData[this.chartID].grid.length; _gridIndex++) {
if (this.chartRef.containPixel({ gridIndex: _gridIndex }, [params.offsetX, params.offsetY])) {
this.position = 'left'
this.showModal = true
break
}
}
this.gridIndex = _gridIndex
}
},
},
mounted () {
},
}
</script>
<style lang='scss'>
.filter-input {
max-width: 100px;
}
.freq-input {
max-width: 80px;
}
.order-input {
max-width: 50px;
}
.filter-apply-button {
max-width: 25px;
}
</style>
@@ -0,0 +1,213 @@
<template>
<div>
<div class="filter-input my-0 pa-0 mx-1">
<va-select
small
noClear
:label="'FILTER ' + (filterIndex + 1)"
v-model="selectFilter"
textBy="name"
:options="optionFilter"
/>
</div>
<va-input
:label="'order'"
class="mx-1 order-input"
v-model="order"
/>
<va-input
v-if="[1, 2, 3].indexOf(selectFilter.id) >= 0"
:label="'FREQ'"
class="mx-1 freq-input"
v-model="freq2"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MIN'"
class="mx-1 freq-input"
v-model="freq0"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MAX'"
class="mx-1 freq-input"
v-model="freq1"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-button
flat
small
:color="buttonColor"
class="circle-button pa-0 mx-0 filter-apply-button"
:icon="buttonIcon"
@click.stop="updateFilterInfo(filterIndex, seriesIndex)"
/>
</div>
</template>
<script>
import { taskInfo } from '@/data/task/TaskInfo'
import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
export default {
name: 'canvas-chart-filter-individual-filter',
props: {
chartID: {
type: Number,
required: true,
},
seriesIndex: {
type: Number,
required: true,
},
filterIndex: {
type: Number,
required: true,
},
},
computed: {
...mapFields('taskContent', [
'filter',
'chartData',
]),
buttonIcon () {
if (this.chartData[this.chartID].series[this.seriesIndex] != null) {
return this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].filter != null ? 'fa fa-stop' : 'fa fa-play'
}
return 'fa fa-play'
},
buttonColor () {
if (this.chartData[this.chartID].series[this.seriesIndex] != null) {
return this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].filter != null ? 'danger' : 'primary'
}
return 'fa fa-play'
},
},
data () {
return {
selectFilter: {
id: 1,
name: 'notch',
},
order: 20,
freq0: 0,
freq1: 0,
freq2: 0,
optionFilter: [
{
id: 1,
name: 'notch',
},
{
id: 2,
name: 'lowpass',
},
{
id: 3,
name: 'highpass',
},
{
id: 4,
name: 'bandstop',
},
{
id: 5,
name: 'bandpass',
},
],
}
},
methods: {
async updateFilterInfo (_filterIndex, _seriesIndex) {
let closeFilter = false
if (this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].filter != null) {
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].filter = null
closeFilter = true
}
if (closeFilter) {
return false
}
let fs = 0
let sos = []
const deviceID = this.chartData[this.chartID].series[_seriesIndex].yAxisSource[0]
// const _fs = taskInfo.getDeviceInfo(deviceID).parameterSet.SAMPLE_RATE
const _fs = 1000
const _chNum = taskInfo.getDeviceInfo(deviceID).parameterSet.CH_NUMBER
// console.log(_fs, _chNum)
if (fs !== _fs) {
fs = _fs
const ret = await api.analysis.IIRFilter({
type: this.selectFilter.name,
order: this.order,
fs: parseInt(_fs / _chNum),
f0: parseInt(this.freq0),
f1: parseInt(this.freq1),
f2: parseInt(this.freq2),
})
if (ret.status !== 200) {
return false
}
const _sosRet = ret.data.split('/')
const _sos = []
_sosRet.forEach((v, vi) => {
const _tmp = v.split(',')
const _arr = []
_tmp.forEach(s => {
_arr.push(parseFloat(s))
})
_sos.push(_arr)
})
sos = _sos
}
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].type = this.selectFilter.name
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].order = this.order
// sampling rate changed by channel number
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].fs = parseInt(_fs / _chNum)
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].f0 = parseInt(this.freq0)
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].f1 = parseInt(this.freq1)
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].f2 = parseInt(this.freq2)
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].sos = sos
this.chartData[this.chartID].series[_seriesIndex].filter[_filterIndex].filter = IIRFilter.init(sos)
},
},
mounted () {
this.selectFilter = this.optionFilter.find(item => item.name === this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].type)
this.freq0 = this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].f0
this.freq1 = this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].f1
this.freq2 = this.chartData[this.chartID].series[this.seriesIndex].filter[this.filterIndex].f2
},
}
</script>
<style lang='scss'>
.filter-input {
max-width: 100px;
}
.freq-input {
max-width: 80px;
}
.order-input {
max-width: 50px;
}
.filter-apply-button {
max-width: 25px;
}
</style>
@@ -1,90 +1,49 @@
<template>
<div>
<va-tree-root>
<va-tree-category isOpen label="Products">
<va-tree-node
v-for="product in products"
:key="product.id"
>
<div class="flex row align--center">
<va-input v-model="product.name" class="mb-0"/>
<va-icon
name="ion ion-md-close"
color="info"
class="ml-2 pa-1 shrink"
style="cursor: pointer;"
@click.native="removeProduct(product)"
/>
</div>
</va-tree-node>
<va-tree-node>
<va-button class="mb-2" @click="addProduct()">
Add new product
</va-button>
</va-tree-node>
</va-tree-category>
<va-tree-category label="Electronics">
<va-tree-node v-for="electronic in electronics" :key="electronic.id">
<va-input v-model="electronic.name" class="mb-0"/>
</va-tree-node>
</va-tree-category>
<va-tree-category isOpen label="Products">
<va-tree-node
v-for="product in products"
:key="product.id"
>
<div class="flex row align--center">
<va-input v-model="product.name" class="mb-0"/>
<va-icon
name="ion ion-md-close"
color="info"
class="ml-2 pa-1 shrink"
style="cursor: pointer;"
@click.native="removeProduct(product)"
/>
</div>
</va-tree-node>
<va-tree-node>
<va-button class="mb-2" @click="addProduct()">
Add new product
</va-button>
</va-tree-node>
</va-tree-category>
</va-tree-root>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm9 xl9 md9 xs9 px-0 mx-0">
<p class="display-6">filter<p/>
</div>
<div class="flex sm3 xl3 md3 xs3 pa-0 ma-0">
<va-toggle v-model="chartData[chartID].filter.open" small @click.native="() => {}"/>
</div>
<!-- <div class="flex sm12 xl12 md12 xs12 px-0 mx-0">
<p class="display-6">Protect recording channels<p/>
</div>
<div class="flex sm12 xl12 md12 xs12 px-0 mx-0">
<va-radio-button
v-for="(option, index) in optionProtectRec"
:key="index"
v-model="selectProtectRec"
:option="optionProtectRec[index]"
:label="optionProtectRec[index]"
/>
</div> -->
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'canvas-chart-setting',
props: {
chartID: {
type: Number,
required: true,
},
},
computed: {
...mapFields('taskContent', [
'chartData',
]),
},
data () {
return {
gridNumber: 8,
electronics: [
{ id: 1, name: 'Cellphones' },
{ id: 2, name: 'Camera Body Kits' },
{ id: 3, name: 'External HDDs' },
],
products: [
{ id: 4, name: 'Cables' },
{ id: 5, name: 'Monitors' },
{ id: 6, name: 'Keyboards' },
],
}
},
methods: {
addProduct () {
this.products.push({
id: Math.floor(Math.random() * 100000),
name: 'New product',
})
},
removeProduct (product) {
this.products = this.products.filter(productToFilter => productToFilter !== product)
},
},
}
</script>

Some files were not shown because too many files have changed in this diff Show More