From ac119abda011098b817233749da0dfb95a5476c1 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.comā©> Date: Mon, 15 Jan 2024 00:02:22 +0100 Subject: [PATCH] chronlog: new app chronlog: use local timezone, not UTC chronlog: fix lint warns + some logic --- apps/chronlog/ChangeLog | 1 + apps/chronlog/README.md | 53 +++++ apps/chronlog/app-icon.js | 1 + apps/chronlog/app.js | 376 ++++++++++++++++++++++++++++++++++++ apps/chronlog/app.png | Bin 0 -> 3047 bytes apps/chronlog/dump.png | Bin 0 -> 2578 bytes apps/chronlog/dump1.png | Bin 0 -> 3068 bytes apps/chronlog/dump2.png | Bin 0 -> 2801 bytes apps/chronlog/dump3.png | Bin 0 -> 3027 bytes apps/chronlog/dump4.png | Bin 0 -> 3367 bytes apps/chronlog/dump5.png | Bin 0 -> 3599 bytes apps/chronlog/dump6.png | Bin 0 -> 3603 bytes apps/chronlog/metadata.json | 14 ++ 13 files changed, 445 insertions(+) create mode 100644 apps/chronlog/ChangeLog create mode 100644 apps/chronlog/README.md create mode 100644 apps/chronlog/app-icon.js create mode 100644 apps/chronlog/app.js create mode 100644 apps/chronlog/app.png create mode 100644 apps/chronlog/dump.png create mode 100644 apps/chronlog/dump1.png create mode 100644 apps/chronlog/dump2.png create mode 100644 apps/chronlog/dump3.png create mode 100644 apps/chronlog/dump4.png create mode 100644 apps/chronlog/dump5.png create mode 100644 apps/chronlog/dump6.png create mode 100644 apps/chronlog/metadata.json diff --git a/apps/chronlog/ChangeLog b/apps/chronlog/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/chronlog/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/chronlog/README.md b/apps/chronlog/README.md new file mode 100644 index 000000000..7c542cf73 --- /dev/null +++ b/apps/chronlog/README.md @@ -0,0 +1,53 @@ +# Chrono Logger + +Record times active on a task, course, work or anything really. + +**Disclaimer:** No one is responsible for any loss of data you recorded with this app. If you run into problems please report as advised under **Requests** below. + +With time on your side and a little help from your friends - you'll surely triumph over Lavos in the end! + +![dump](dump.png) ![dump1](dump1.png) ![dump2](dump2.png) ![dump3](dump3.png) ![dump4](dump4.png) ![dump5](dump5.png) ![dump6](dump6.png) + + +## Usage + +Click the large green button to log the start of your activity. Click the now red button again to log that you stopped. + +## Features + +- Saves to file on every toggling of the active state. + - csv file contents looks like: + ``` + 1,Start,2024-03-02T15:18:09 GMT+0200 + 2,Note,Critical hit! + 3,Stop,2024-03-02T15:19:17 GMT+0200 + ``` +- Add annotations to the log. +- Create and switch between multiple logs. +- Sync log files to an Android device through Gadgetbridge (Needs pending code changes to Gadgetbridge). +- App state is restored when you start the app again. + +## Controls + +- Large button to toggle active state. +- Menu icon to access additional functionality. +- Hardware button exits menus, closes the app on the main screen. + +## TODO and notes + +- Delete individual tasks/logs through the app? +- Reset everything through the app? +- Scan for chronlog storage files that somehow no longer have tasks associated with it? +- Complete the Gadgetbridge side of things for sync. +- Sync to iOS? +- Inspect log files through the app, similarly to Recorder app? +- Changes to Android file system permissions makes it not always trivial to access the synced files. + + +## Requests + +Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions. + +## Creator + +[thyttan](https://github.com/thyttan) diff --git a/apps/chronlog/app-icon.js b/apps/chronlog/app-icon.js new file mode 100644 index 000000000..dc25e4b5b --- /dev/null +++ b/apps/chronlog/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///gElq3X0ELJf4AiitAAYMBqgKEgNVrgEBmtVCAQABgtVr/Agf1qtQEQlpq6QB6tpEgkVywLDywLEq2uyoLB6wEBBZAECBYda32lBYIECBZ9W3wjDAgILPquWqoACAgILEtILDAgKOEAAyQCRwIAGSAUVBY6ECBZYGD7WnAoYLF9WrBYupAoWq1QECtQLBtWdBYt21QLC1LfBBYVfA4ILBlWq1f9rWVv/q1WoBYMKCgOvTYP6AoOgBYMCAoIAFwCQCBY6nDGAIAEFwQkIEQZVCBQZRCAAcGBYeQBYoYDCwwYECw5KC0gKIAH4APA=")) diff --git a/apps/chronlog/app.js b/apps/chronlog/app.js new file mode 100644 index 000000000..827ca14e1 --- /dev/null +++ b/apps/chronlog/app.js @@ -0,0 +1,376 @@ +// TODO: +// - Add more /*LANG*/ tags for translations. +// - Check if there are chronlog storage files that should be added to tasks. + +{ + const storage = require("Storage"); + let appData = storage.readJSON("chronlog.json", true) || { + currentTask : "default", + tasks : { + default: { + file : "chronlog_default.csv", // Existing default task log file + state : "stopped", + lineNumber : 0, + lastLine : "", + lastSyncedLine : "", + }, + // Add more tasks as needed + }, + }; + let currentTask = appData.currentTask; + let tasks = appData.tasks; + delete appData; + + let themeColors = g.theme; + + let logEntry; // Avoid previous lint warning + + // Function to draw the Start/Stop button with play and pause icons + let drawButton = ()=>{ + var btnWidth = g.getWidth() - 40; + var btnHeight = 50; + var btnX = 20; + var btnY = (g.getHeight() - btnHeight) / 2; + var cornerRadius = 25; + + var isStopped = tasks[currentTask].state === "stopped"; + g.setColor(isStopped ? "#0F0" : "#F00"); // Set color to green when stopped and red when started + + // Draw rounded corners of the button + g.fillCircle(btnX + cornerRadius, btnY + cornerRadius, cornerRadius); + g.fillCircle(btnX + btnWidth - cornerRadius, btnY + cornerRadius, cornerRadius); + g.fillCircle(btnX + cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius); + g.fillCircle(btnX + btnWidth - cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius); + + // Draw rectangles to fill in the button + g.fillRect(btnX + cornerRadius, btnY, btnX + btnWidth - cornerRadius, btnY + btnHeight); + g.fillRect(btnX, btnY + cornerRadius, btnX + btnWidth, btnY + btnHeight - cornerRadius); + + g.setColor(themeColors.bg); // Set icon color to contrast against the button's color + + // Center the icon within the button + var iconX = btnX + btnWidth / 2; + var iconY = btnY + btnHeight / 2; + + if (isStopped) { + // Draw play icon + var playSize = 10; // Side length of the play triangle + var offset = playSize / Math.sqrt(3) - 3; + g.fillPoly([ + iconX - playSize, iconY - playSize + offset, + iconX - playSize, iconY + playSize + offset, + iconX + playSize * 2 / Math.sqrt(3), iconY + offset + ]); + } else { + // Draw pause icon + var barWidth = 5; // Width of pause bars + var barHeight = btnHeight / 2; // Height of pause bars + var barSpacing = 5; // Spacing between pause bars + g.fillRect(iconX - barSpacing / 2 - barWidth, iconY - barHeight / 2, iconX - barSpacing / 2, iconY + barHeight / 2); + g.fillRect(iconX + barSpacing / 2, iconY - barHeight / 2, iconX + barSpacing / 2 + barWidth, iconY + barHeight / 2); + } + }; + + let drawHamburgerMenu = ()=>{ + var x = g.getWidth() / 2; // Center the hamburger menu horizontally + var y = (7/8)*g.getHeight(); // Position it near the bottom + var lineLength = 18; // Length of the hamburger lines + var spacing = 6; // Space between the lines + + g.setColor(themeColors.fg); // Set color to foreground color for the icon + // Draw three horizontal lines + for (var i = -1; i <= 1; i++) { + g.fillRect(x - lineLength/2, y + i * spacing - 1, x + lineLength/2, y + i * spacing + 1); + } + }; + + // Function to draw the task name centered between the widget field and the start/stop button + let drawTaskName = ()=>{ + g.setFont("Vector", 20); // Set a smaller font for the task name display + + // Calculate position to center the task name horizontally + var x = (g.getWidth()) / 2; + + // Calculate position to center the task name vertically between the widget field and the start/stop button + var y = g.getHeight()/4; // Center vertically + + g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color + g.drawString(currentTask, x, y); // Draw the task name centered on the screen + }; + + // Function to draw the last log entry of the current task + let drawLastLogEntry = ()=>{ + g.setFont("Vector", 10); // Set a smaller font for the task name display + + // Calculate position to center the log entry horizontally + var x = (g.getWidth()) / 2; + + // Calculate position to place the log entry properly between the start/stop button and hamburger menu + var btnBottomY = (g.getHeight() + 50) / 2; // Y-coordinate of the bottom of the start/stop button + var menuBtnYTop = g.getHeight() * (5 / 6); // Y-coordinate of the top of the hamburger menu button + var y = btnBottomY + (menuBtnYTop - btnBottomY) / 2 + 2; // Center vertically between button and menu + + g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color + g.drawString(g.wrapString(tasks[currentTask].lastLine, 150).join("\n"), x, y); + }; + + /* + // Helper function to read the last log entry from the current task's log file + let updateLastLogEntry = ()=>{ + var filename = tasks[currentTask].file; + var file = require("Storage").open(filename, "r"); + var lastLine = ""; + var line; + while ((line = file.readLine()) !== undefined) { + lastLine = line; // Keep reading until the last line + } + tasks[currentTask].lastLine = lastLine; + }; + */ + + // Main UI drawing function + let drawMainMenu = ()=>{ + g.clear(); + Bangle.drawWidgets(); // Draw any active widgets + g.setColor(themeColors.bg); // Set color to theme's background color + g.fillRect(Bangle.appRect); // Fill the app area with the background color + + drawTaskName(); // Draw the centered task name + drawLastLogEntry(); // Draw the last log entry of the current task + drawButton(); // Draw the Start/Stop toggle button + drawHamburgerMenu(); // Draw the hamburger menu button icon + + //g.flip(); // Send graphics to the display + }; + + // Function to toggle the active state + let toggleChronlog = ()=>{ + var dateObj = new Date(); + var dateObjStrSplit = dateObj.toString().split(" "); + var currentTime = dateObj.getFullYear().toString() + "-" + (dateObj.getMonth()<10?"0":"") + dateObj.getMonth().toString() + "-" + (dateObj.getDate()<10?"0":"") + dateObj.getDate().toString() + "T" + (dateObj.getHours()<10?"0":"") + dateObj.getHours().toString() + ":" + (dateObj.getMinutes()<10?"0":"") + dateObj.getMinutes().toString() + ":" + (dateObj.getSeconds()<10?"0":"") + dateObj.getSeconds().toString() + " " + dateObjStrSplit[dateObjStrSplit.length-1]; + + tasks[currentTask].lineNumber = Number(tasks[currentTask].lineNumber) + 1; + logEntry = tasks[currentTask].lineNumber + (tasks[currentTask].state === "stopped" ? ",Start," : ",Stop,") + currentTime + "\n"; + var filename = tasks[currentTask].file; + + // Open the appropriate file and append the log entry + var file = require("Storage").open(filename, "a"); + file.write(logEntry); + tasks[currentTask].lastLine = logEntry; + + // Toggle the state and update the button text + tasks[currentTask].state = tasks[currentTask].state === "stopped" ? "started" : "stopped"; + drawMainMenu(); // Redraw the main UI + }; + + // Define the touch handler function for the main menu + let handleMainMenuTouch = (button, xy)=>{ + var btnTopY = (g.getHeight() - 50) / 2; + var btnBottomY = btnTopY + 50; + var menuBtnYTop = (7/8)*g.getHeight() - 15; + var menuBtnYBottom = (7/8)*g.getHeight() + 15; + var menuBtnXLeft = (g.getWidth() / 2) - 15; + var menuBtnXRight = (g.getWidth() / 2) + 15; + + // Detect if the touch is within the toggle button area + if (xy.x >= 20 && xy.x <= (g.getWidth() - 20) && xy.y > btnTopY && xy.y < btnBottomY) { + toggleChronlog(); + } + // Detect if the touch is within the hamburger menu button area + else if (xy.x >= menuBtnXLeft && xy.x <= menuBtnXRight && xy.y >= menuBtnYTop && xy.y <= menuBtnYBottom) { + showMenu(); + } + }; + + // Function to attach the touch event listener + let setMainUI = ()=>{ + Bangle.setUI({ + mode: "custom", + back: load, + touch: handleMainMenuTouch + }); + }; + + let saveAppState = ()=>{ + let appData = { + currentTask : currentTask, + tasks : tasks, + }; + require("Storage").writeJSON("chronlog.json", appData); + }; + // Set up a listener for the 'kill' event + E.on('kill', saveAppState); + + // Function to switch to a selected task + let switchTask = (taskName)=>{ + currentTask = taskName; // Update the current task + + // Reinitialize the UI elements + setMainUI(); + drawMainMenu(); // Redraw UI to reflect the task change and the button state + }; + + // Function to create a new task + let createNewTask = ()=>{ + // Prompt the user to input the task's name + require("textinput").input({ + text: "" // Default empty text for new task + }).then(result => { + var taskName = result; // Store the result from text input + if (taskName) { + if (tasks.hasOwnProperty(taskName)) { + // Task already exists, handle this case as needed + E.showAlert(/*LANG*/"Task already exists", "Error").then(drawMainMenu); + } else { + // Create a new task log file for the new task + var filename = "chronlog_" + taskName.replace(/\W+/g, "_") + ".csv"; + tasks[taskName] = { + file : filename, + state : "stopped", + lineNumber : 0, + lastLine : "", + lastSyncedLine : "", + }; + + currentTask = taskName; + + setMainUI(); + drawMainMenu(); // Redraw UI with the new task + } + } else { + setMainUI(); + drawMainMenu(); // User cancelled, redraw main menu + } + }).catch(e => { + console.log("Text input error", e); + setMainUI(); + drawMainMenu(); // In case of error also redraw main menu + }); + }; + + // Function to display the list of tasks for selection + let chooseTask = ()=>{ + // Construct the tasks menu from the tasks object + var taskMenu = { + "": { "title": /*LANG*/"Choose Task", + "back" : function() { + setMainUI(); // Reattach when the menu is closed + drawMainMenu(); // Cancel task selection + } + } + }; + for (var taskName in tasks) { + if (!tasks.hasOwnProperty(taskName)) continue; + taskMenu[taskName] = (function(name) { + return function() { + switchTask(name); + }; + })(taskName); + } + + // Add a menu option for creating a new task + taskMenu[/*LANG*/"Create New Task"] = createNewTask; + + E.showMenu(taskMenu); // Display the task selection + }; + + // Function to annotate the current or last work session + let annotateTask = ()=>{ + + // Prompt the user to input the annotation text + require("textinput").input({ + text: "" // Default empty text for annotation + }).then(result => { + var annotationText = result.trim(); + if (annotationText) { + // Append annotation to the last or current log entry + tasks[currentTask].lineNumber ++; + var annotatedEntry = tasks[currentTask].lineNumber + /*LANG*/",Note," + annotationText + "\n"; + var filename = tasks[currentTask].file; + var file = require("Storage").open(filename, "a"); + file.write(annotatedEntry); + tasks[currentTask].lastLine = annotatedEntry; + setMainUI(); + drawMainMenu(); // Redraw UI after adding the annotation + } else { + // User cancelled, so we do nothing and just redraw the main menu + setMainUI(); + drawMainMenu(); + } + }).catch(e => { + console.log("Annotation input error", e); + setMainUI(); + drawMainMenu(); // In case of error also redraw main menu + }); + }; + + let syncToAndroid = (taskName, isFullSync)=>{ + let mode = "a"; + if (isFullSync) mode = "w"; + let lastSyncedLine = tasks[taskName].lastSyncedLine || 0; + let taskNameValidFileName = taskName.replace(" ","_"); // FIXME: Should use something similar to replaceAll using a regular expression to catch all illegal characters. + + let storageFile = require("Storage").open("chronlog_"+taskNameValidFileName+".csv", "r"); + let contents = storageFile.readLine(); + let lineNumber = contents ? contents.slice(0, contents.indexOf(",")) : 0; + let shouldSyncLine = ()=>{return (contents && (isFullSync || (Number(lineNumber)>Number(lastSyncedLine))));}; + let doSyncLine = (mde)=>{Bluetooth.println(JSON.stringify({t:"file", n:"chronlog_"+taskNameValidFileName+".csv", c:contents, m:mde}));}; + + if (shouldSyncLine()) doSyncLine(mode); + contents = storageFile.readLine(); + while (contents) { + lineNumber = contents.slice(0, contents.indexOf(",")); // Could theoretically do with `lineNumber++`, but this is more robust in case numbering in file ended up irregular. + if (shouldSyncLine()) doSyncLine("a"); + contents = storageFile.readLine(); + } + tasks[taskName].lastSyncedLine = lineNumber; + }; + + // Function to display the list of tasks for selection + let syncTasks = ()=>{ + let isToDoFullSync = false; + // Construct the tasks menu from the tasks object + var syncMenu = { + "": { "title": /*LANG*/"Sync Tasks", + "back" : function() { + setMainUI(); // Reattach when the menu is closed + drawMainMenu(); // Cancel task selection + } + } + }; + syncMenu[/*LANG*/"Full Resyncs"] = { + value: !!isToDoFullSync, // !! converts undefined to false + onchange: ()=>{ + isToDoFullSync = !isToDoFullSync + }, + } + for (var taskName in tasks) { + if (!tasks.hasOwnProperty(taskName)) continue; + syncMenu[taskName] = (function(name) { + return function() {syncToAndroid(name,isToDoFullSync);}; + })(taskName); + } + + E.showMenu(syncMenu); // Display the task selection + }; + + let showMenu = ()=>{ + var menu = { + "": { "title": /*LANG*/"Menu", + "back": function() { + setMainUI(); // Reattach when the menu is closed + drawMainMenu(); // Redraw the main UI when closing the menu + }, + }, + /*LANG*/"Annotate": annotateTask, // Now calls the real annotation function + /*LANG*/"Change Task": chooseTask, // Opens the task selection screen + /*LANG*/"Sync to Android": syncTasks, + }; + E.showMenu(menu); + }; + + Bangle.loadWidgets(); + drawMainMenu(); // Draw the main UI when the app starts + // When the application starts, attach the touch event listener + setMainUI(); +} diff --git a/apps/chronlog/app.png b/apps/chronlog/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c21a147eac583c4db5a0edfe574fcc8bba86f02e GIT binary patch literal 3047 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 literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump.png b/apps/chronlog/dump.png new file mode 100644 index 0000000000000000000000000000000000000000..0c40b190e9cd618776e8fcde8ba71983c5626fa8 GIT binary patch literal 2578 zcmd5-dpOkF8ea1=GZ@5{NN(9iZn0BQF=I1@W|Aqngp4`1?Q+PiWZcGRt0)>_BIB}) zX-6?8cQaEzrPOvy<+2gu(oV)@lG~PYW}oxd`Q!Y5*7L0OJ?neE_xaZPp7;H}j1#WT zN|+rO0Duzl=n*%mt@(S)p`|Ccaf%`}h;wev1W?_r{ZV>Bo+A+5<)kY~E-)Pcc>(c= zgFB5fS9~pIa*v#2?&jun#JdN+&b=~3MtG7mY?oTM$aZzE} zq~~W2un?Y)G)f5`U5K&JnIv_RdN|Yec)V}Cy@5Gdn$eU)U z(&PSCb2#i&%tT)#h!m-tMk~O*E77Z86l&B3=IOqOdN-@EvBe&7F=iG-25n1ES??<+ z42ZtIzelb85`&^CK3aO6Ko`4@zP#{zkHi~w9eC}e3zza64Ft{j6Z6YwMpM8|_c383 zE3?aE+;W;!i} zxJq#Q5Lrtf-5o%!{xsWUKoaF;#?uXM%*iE0cE68U7RKuhEb5G&KecCGqvIbaWH8L> zDnxdV6uce#2wa(-6#Q!=0l;Mzm)r&-pUvUgY&UMwkoxy=P6f8kfd&J=q)rUBsuN;X ze0_el7%&=Z6PDV{rp~3-WSl}iZ0~E^H7_HY41Ml@(Ld#wMUFm$$>zOFS+E@19`aYi z_`S~sVeH_iLXP4R<~3}an|SQ`8d}@=VE}sh#z4ymeEl{m>LByi(=FgMU7d#<#tsE?6I}Y$p}-(9pL2x@Dq|hyWK< z3q!-z09DKH(@Z%4w(?#3tude`tC|+;2BWClU>(w!L)6=cR?o#Zj&tLS4*FPk#V}T1 zSm_Rhwl;V5?U+Q}9KdzFh$-C8*m_$CFBZ0El>Pjs3EKTtckrZ@Pag||tDKFW0|l5Q ziVez)uLyIPL3)olMj8venSATqX3P4`j&OExLvU%RenQ>7&6+8yU+;s9sO!B z;Q1E18z@HAAn7+IHf6K0kC%y;i_{GnVN|`zkox2*9=bcfZk)vSU)xUz+4bA%&EZ5#z54tKPoMbQ=b#*TsMr=GniUXIH`uD9LJ?ob! z>c8VN@#m~>yMt<}FnM*@+(nX1ZH{Gzhx=L!dHt0k=1#8qdo@{0?W&A7m1XtOd7icX zj)meM!mO(l>S8VH6TbC&+Pi(__;hBTtAO_@w5g*iHJY52T`(tL62i2G^>8g#$`QZ7 zAXPIdWk}u|EmDqH?A@2afFu{F`c#y>G9D<}6`kusHmH!;2PbFtj?XQ0^hLH2;(xV$|@ zd5?&qJhC-au!RZ>?V5~0yb=s+DUag6|IqM6k1EPiSb)-#5$Mr1iOu0z**%g`S&DOd zGC+@hX%!=RcoM-5AVUy4l(yuvC?cWKH$8In+G&2I4<1$ z-!Zn&u!5?YE!(_L+L}s?-&`fqMi4_~-B*VY5)xwpJzu?Cff!YfztYx;9pAE^hk2SA zp;@}VZ9AjG>T9=Xhx=HUAxNxKVT3JOXanXGzS+P-*%9(3i(bOW?JQ$MD5N95ey`xd zwzca(CPGXvUsr77aT;<*yU+RV7Jrv$m7!gB_cFR~paB}YjS1Uzu!VaJQf%wx(`6!bn}yRk3a5qp3Tu zEAb2jVt}3~P_cc=SEpLXMen45d5e;+$vlIK@w|W|_4U>Yt$01yeX4%XN+%tP>QRU? z#j=`Z&DlI7$XZE)Gf&G}qA67aAWM!~l7NE=mw#!OgdIGLkeJ=`O(OV*xq+ zw?GU`B1{BjK|tUX7vv$87b~r;YX=I@2yqD_`A>N+2pZuiC{ZE5RAU&WA^+n#O*}gC zK&s|}>Fx(4sZevWo4lp-fIRK0jMFd4F#%*bd3fuD$t_5dmMu;7x64dEgKdQYk{$1E zc}T~Cr>g3KFf|~uYT0)`2LcbU*ob~}iMs|04mnx7eE{?GuLj(X~zpKq<4W fH8lbM{R5Gnr5_JpF?>e)AOMJtu1Bf~)QkTD#Sx}T literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump1.png b/apps/chronlog/dump1.png new file mode 100644 index 0000000000000000000000000000000000000000..04f625f04ff80cd5e14739d3b3557aafe2679f5b GIT binary patch literal 3068 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 literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump2.png b/apps/chronlog/dump2.png new file mode 100644 index 0000000000000000000000000000000000000000..be07916595e5d0a8abf22e8fd5f4350b961fa457 GIT binary patch literal 2801 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 literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump3.png b/apps/chronlog/dump3.png new file mode 100644 index 0000000000000000000000000000000000000000..eeeba525fb5e261eee0abba9b553a118ab867a42 GIT binary patch literal 3027 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 UfA=|G?q>&tHnTP@H^$%nAEE+X)c^nh literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump5.png b/apps/chronlog/dump5.png new file mode 100644 index 0000000000000000000000000000000000000000..debf919cc77e26d04cde2995d97a6e1928cbfdaf GIT binary patch literal 3599 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 literal 0 HcmV?d00001 diff --git a/apps/chronlog/dump6.png b/apps/chronlog/dump6.png new file mode 100644 index 0000000000000000000000000000000000000000..29a06b68f9b23dfceb3b2abc63bfcc22a694d329 GIT binary patch literal 3603 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 z