Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/vis actions #18

Merged
merged 13 commits into from
Mar 25, 2024
27 changes: 20 additions & 7 deletions src/activity-log-tools/filter/activitydaycounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { getActivityLog } from '$:/plugins/linonetwo/activity-log-tools/log-file-tools/getActivityLog';
import { IFilterOperator, Tiddler } from 'tiddlywiki';
import { LogFileTypes } from '../log-file-types/LogFileTypes';
import { DAY_INTERVAL, LogFileTypes } from '../log-file-types/LogFileTypes';

/**
* Output comma separated numbers like the one in `LogFileTypes.DailyCount`.
Expand Down Expand Up @@ -65,9 +65,10 @@ export const activitydaycounts = ((source, operator): string[] => {
case LogFileTypes.Date: {
const dateCounts = new Map<number, number>();
items.forEach(dateString => {
const date = new Date(Number(dateString));
if (date >= filterRangeStartDate && date <= filterRangeEndDate) {
const dateKey = new Date(date.toISOString().split('T')[0]).getTime(); // Normalize to YYYY-MM-DD to count same day, then use number for key
const currentDate = new Date(Number(dateString));
if (currentDate >= filterRangeStartDate && currentDate <= filterRangeEndDate) {
// Normalize to YYYY-MM-DD to count same day, then use number for key
const dateKey = getLocaleDateKey(currentDate);
dateCounts.set(dateKey, (dateCounts.get(dateKey) ?? 0) + 1);
}
});
Expand All @@ -84,9 +85,11 @@ export const activitydaycounts = ((source, operator): string[] => {

intervals.reverse().forEach(interval => {
// Subtract the interval from the current date to find the event date
currentDate = new Date(currentDate.getTime() - interval * 24 * 60 * 60 * 1000); // Subtract interval days
currentDate = new Date(currentDate.getTime() - interval * DAY_INTERVAL); // Subtract interval days
if (currentDate >= filterRangeStartDate && currentDate <= filterRangeEndDate) {
const dateKey = new Date(currentDate.toISOString().split('T')[0]).getTime(); // Normalize to YYYY-MM-DD to count same day, then use number for key
// count activities by day, use locale date.
// Normalize to YYYY-MM-DD to count same day, then use number for key
const dateKey = getLocaleDateKey(currentDate);
dateCounts.set(dateKey, (dateCounts.get(dateKey) ?? 0) + 1);
}
});
Expand All @@ -105,7 +108,7 @@ export const activitydaycounts = ((source, operator): string[] => {
}) satisfies IFilterOperator;

function getRangeAndLogFile(tiddler: Tiddler | undefined, title: string, dateStartString: string | undefined, dateEndString: string | undefined) {
const logTiddlerType = tiddler?.fields['activity-log-file-type'] as LogFileTypes | undefined;
const logTiddlerType = tiddler?.fields['activity-log-type'] as LogFileTypes | undefined;
if (logTiddlerType === undefined || dateStartString === undefined || dateEndString === undefined) {
// activity log file should have a type, if it is not defined, we can't do anything.
return undefined;
Expand Down Expand Up @@ -144,3 +147,13 @@ function getSortedCount(filterRangeStartDate: Date, filterRangeEndDate: Date, da
.join(',');
return sortedCounts;
}

function getLocaleDateKey(date: Date): number {
const year = date.getFullYear();
const month = date.getMonth() + 1; // getMonth() is zero-indexed
const day = date.getDate();
// Create a new Date object with the local date components at midnight
const localDate = new Date(year, month - 1, day);
// Return the timestamp (in milliseconds) of the local date at midnight
return localDate.getTime();
}
5 changes: 5 additions & 0 deletions src/activity-log-tools/function/activity-log-title.tid
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
title: $:/plugins/linonetwo/activity-log-tools/function/activity-log-title
tags: $:/tags/Global

\function fallback-activity-log-title() [addsuffix[-Log]]
\function activity-log-title(sourceTitle) [<sourceTitle>get[activity-log]] ~[<sourceTitle>function[fallback-activity-log-title]]
6 changes: 3 additions & 3 deletions src/activity-log-tools/log-file-tools/addActivityToLog.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createActivityLog } from '$:/plugins/linonetwo/activity-log-tools/log-file-tools/createActivityLog';
import { IActivityLogFile, IDailyCountKey, IDayIntervalKey, LogFileTypes } from '../log-file-types/LogFileTypes';
import { DAY_INTERVAL, IActivityLogFile, IDailyCountKey, IDayIntervalKey, LogFileTypes } from '../log-file-types/LogFileTypes';

/** 30 days in ms */
const MAX_ACTIVITY_LOG_ITEM_DURATION = 30 * 24 * 60 * 60 * 1000;
const MAX_ACTIVITY_LOG_ITEM_DURATION = 30 * DAY_INTERVAL;

export function addActivityToLog(activityLog: IActivityLogFile | undefined, newActivityTimestamp: number) {
if (activityLog === undefined) return;
Expand Down Expand Up @@ -86,7 +86,7 @@ export function addActivityToLog(activityLog: IActivityLogFile | undefined, newA
* Minimum interval is `0.00001` days (1 min), but we can store it based on interval, if it is larger than 1 day, then store with `toFixed(1)`, if is between 1 hour and 1 day, then store with `toFixed(2)` for `0.04` (1/24), if is between 1 min and 1 hour, then store with `toFixed(5)` for `0.00001`.
*/
function formatDayInterval(intervalInMs: number): string {
const intervalInDay = intervalInMs / (24 * 60 * 60 * 1000);
const intervalInDay = intervalInMs / (DAY_INTERVAL);
if (intervalInDay >= 1) {
return intervalInDay.toFixed(1);
} else if (intervalInDay >= 1 / 24) {
Expand Down
3 changes: 2 additions & 1 deletion src/activity-log-tools/log-file-tools/createActivityLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function createActivityLog(logTiddlerType: LogFileTypes, fields: Partial<
type: 'application/x-tiddler-dictionary',
text: '',
'hide-body': 'yes',
'activity-log-file-type': logTiddlerType,
'activity-log-type': logTiddlerType,
tags: [...fields?.tags ?? [], '$:/Tags/ActivityLog'],
} satisfies IActivityLogTiddlerFields,
);
}
Expand Down
7 changes: 4 additions & 3 deletions src/activity-log-tools/log-file-types/LogFileTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ITiddlerFields } from 'tiddlywiki';

/**
* We save event log in text of tiddler with tag `$:/Tags/Gamification/RealityEventLog`, and use data tiddler with fields ends with a UTC time number (`DailyCount123time` `DayInterval`).
* Actions can use `reality-event-log` field to point to the log file containing the data described below.
* We save event log in text of tiddler with tag `$:/Tags/ActivityLog`, and use data tiddler with fields ends with a UTC time number (`DailyCount123time` `DayInterval`).
* Actions can use `activity-log` field to point to the log file containing the data described below.
*
* ```tid
* hide-body: yes
Expand Down Expand Up @@ -46,6 +46,7 @@ export enum LogFileTypes {
DayInterval = 'day-interval',
}

export const DAY_INTERVAL = 24 * 60 * 60 * 1000;
export type IDailyCountKey = `${LogFileTypes.DailyCount}${number}`;
export type IDayIntervalKey = `${LogFileTypes.DayInterval}${number}`;
export type IDateKey = string;
Expand Down Expand Up @@ -76,6 +77,6 @@ export interface IActivityLogTiddlerFields extends ITiddlerFields {
/**
* Indicate the format of event log file. Default to `date`.
*/
'activity-log-file-type': LogFileTypes;
'activity-log-type': LogFileTypes;
type: 'application/x-tiddler-dictionary';
}
4 changes: 2 additions & 2 deletions src/early-sleep-early-work/events/actions/early-sleep.tid
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ debounce-duration: 43200
reality-event-amount: 1
reality-event-message: <<lingo Event/EarlySleep/Message $:/plugins/linonetwo/early-sleep-early-work/language/>>
reality-event-type: SmallReward
activity-log-file-type: day-interval
reality-event-log: $:/plugins/linonetwo/early-sleep-early-work/events/logs/actions/early-sleep
activity-log-type: day-interval
activity-log: $:/plugins/linonetwo/early-sleep-early-work/events/logs/actions/early-sleep
icon: {{$:/plugins/linonetwo/early-sleep-early-work/icons/early-sleep}}
caption: <<lingo Event/EarlySleep/Caption $:/plugins/linonetwo/early-sleep-early-work/language/>>
description: <<lingo Event/EarlySleep/Description $:/plugins/linonetwo/early-sleep-early-work/language/>>
Expand Down
3 changes: 2 additions & 1 deletion src/early-sleep-early-work/events/event-logs/logs.multids
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
title: $:/plugins/linonetwo/early-sleep-early-work/events/logs/
tags: $:/Tags/Gamification/RealityEventLog
tags: $:/Tags/ActivityLog
hide-body: yes
type: application/x-tiddler-dictionary
activity-log-type: day-interval

actions/early-sleep:
generators/pre-early-sleep:
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ caption: <<lingo Configs/PrepareSleep/Enable/Caption $:/plugins/linonetwo/early-
description: <<lingo Configs/PrepareSleep/Enable/Description $:/plugins/linonetwo/early-sleep-early-work/language/>>
icon: {{$:/plugins/linonetwo/early-sleep-early-work/icons/pre-early-sleep}}
reality-event-trigger-filter: [!is[system]!is[draft]tag{$:/plugins/linonetwo/early-sleep-early-work/configs/AllowedPreSleepEventTypes}field:calendarEntry[yes]starthourlatethan{$:/plugins/linonetwo/early-sleep-early-work/configs/PeriodToPrepareSleep/Start}endhourearlythan{$:/plugins/linonetwo/early-sleep-early-work/configs/PeriodToPrepareSleep/End}]
activity-log-file-type: day-interval
reality-event-log: $:/plugins/linonetwo/early-sleep-early-work/events/logs/generators/pre-early-sleep
activity-log-type: day-interval
activity-log: $:/plugins/linonetwo/early-sleep-early-work/events/logs/generators/pre-early-sleep
find-duplicate: debounce
debounce-duration: 43200
reality-event-amount: 1
Expand Down
4 changes: 4 additions & 0 deletions src/ego-store-lite/language/en-GB/Translations.multids
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ CreateAction/Description/Caption: Description
CreateAction/Description/Description: Describe the action in detail.
CreateAction/Enabled/Caption: Enabled
CreateAction/Enabled/Description: Toggle to enable or disable the action.
EditAction/Caption: Edit Action
EditGoods/Caption: Edit Goods
VisualizeAction/Caption: Visual Analysis
OpenTiddler: Open Tiddler
ActionsList/ReceiveRewardWithCount: Click to receive <<count-reality-events>> rewards.
Store/Title: Store
CreateGoods/Caption: Create Goods
Expand Down
4 changes: 4 additions & 0 deletions src/ego-store-lite/language/en-GB/Visualization.multids
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: $:/plugins/linonetwo/ego-store-lite/language/en-GB/

Gauges/LineCountsWeeks/Caption: Daily counts line chart 4 weeks
Gauges/LineCountsWeeks/Description: Calculates the total number of daily counts, based on clock in logs, and draws a weekly line chart showing the last four weeks in total.
4 changes: 4 additions & 0 deletions src/ego-store-lite/language/zh-Hans/Translations.multids
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ CreateAction/Description/Caption: 描述
CreateAction/Description/Description: 详细描述行动。
CreateAction/Enabled/Caption: 启用
CreateAction/Enabled/Description: 切换以启用或禁用行动。
EditAction/Caption: 编辑行动
EditGoods/Caption: 编辑商品
VisualizeAction/Caption: 可视化分析
OpenTiddler: 打开条目
ActionsList/ReceiveRewardWithCount: 点击接收 <<count-reality-events>> 份奖励
Store/Title: 商店
CreateGoods/Caption: 创建商品
Expand Down
4 changes: 4 additions & 0 deletions src/ego-store-lite/language/zh-Hans/Visualization.multids
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: $:/plugins/linonetwo/ego-store-lite/language/zh-Hans/

Gauges/LineCountsWeeks/Caption: 近四周每日次数折线图
Gauges/LineCountsWeeks/Description: 根据打卡记录,计算每日总次数,画出每周的折线图,总共展示最近四周的情况。
2 changes: 1 addition & 1 deletion src/ego-store-lite/plugin.info
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"parent-plugin": "$:/plugins/linonetwo/tw-gamification",
"plugin-type": "plugin",
"version": "0.1.0",
"dependents": "$:/plugins/linonetwo/simple-layout-launcher $:/plugins/linonetwo/tw-gamification",
"dependents": "$:/plugins/linonetwo/simple-layout-launcher $:/plugins/linonetwo/tw-gamification $:/plugins/linonetwo/visualization-dashboard",
"readme": "<<lingo Readme $:/plugins/linonetwo/ego-store-lite/language/>>",
"list": "Readme config tree"
}
45 changes: 44 additions & 1 deletion src/ego-store-lite/tiddlywiki-ui/actions-list/actions-list.tid
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,55 @@ caption: {{$:/plugins/linonetwo/tw-gamification/icons/action}} <<lingo ActionsLi

\import [[$:/plugins/linonetwo/tw-gamification/tiddlywiki-ui/procedure/reality-event-type-display]]

\procedure lingo-base() $:/plugins/linonetwo/ego-store-lite/language/
\procedure drop-down-menu-select-state() $:/state/ego-store-lite/actions-list/menu-selected
\procedure on-drop-down-menu-select()
<% if [<drop-down-menu-select-state>get[text]match[edit]] %>
<$action-createtiddler
$basetitle="$:/state/ego-store-lite/editAction"
$overwrite="yes"
$template=<<currentTiddler>>
draft.title={{!!title}}
draft.of=""
>
<$action-sendmessage
$message="tm-modal"
$param="$:/plugins/linonetwo/ego-store-lite/tiddlywiki-ui/create/modal/edit-action/edit-action
/>
</$action-createtiddler>
<% elseif [<drop-down-menu-select-state>get[text]match[visual]] %>
<$action-sendmessage
$message="tm-modal"
$param="$:/plugins/linonetwo/ego-store-lite/tiddlywiki-ui/visualization/modal/action"
targetTiddler=<<currentTiddler>>
/>
<% elseif [<drop-down-menu-select-state>get[text]match[open]] %>
<$action-createtiddler $basetitle="$:/layout" text="" $overwrite="yes">
<$action-navigate $to=<<currentTiddler>> />
<$action-sendmessage
$message="tm-notify"
$param="$:/plugins/linonetwo/tw-gamification/tiddlywiki-ui/notification/on-reality-action-event"
message=<<currentTiddler>>
/>
</$action-createtiddler>
<% endif %>
<$action-deletetiddler $tiddler=<<drop-down-menu-select-state>>/>
\end

{{$:/plugins/linonetwo/ego-store-lite/tiddlywiki-ui/actions-list/receive-reward}}

<div class="ego-store-lite-game-buttons-list">
<$list filter="[all[shadows+tiddlers]tag[$:/Tags/Gamification/RealityAction]!is[draft]]">
<div class="ego-store-lite-game-buttons-list-item" data-title=<<currentTiddler>>>
<div class="ego-store-lite-game-buttons-list-item-edit">…</div>
<div class="ego-store-lite-game-buttons-list-item-edit">
<$select tiddler=<<drop-down-menu-select-state>> actions=<<on-drop-down-menu-select>>>
<option value="edit"><<lingo EditAction/Caption>></option>
<option value="visual"><<lingo VisualizeAction/Caption>></option>
<option value="open"><<lingo OpenTiddler>></option>
</$select>
<div>…</div>
</div>

<$button>
<$action-sendmessage
$message="tm-reality-action-event"
Expand Down
33 changes: 29 additions & 4 deletions src/ego-store-lite/tiddlywiki-ui/actions-list/buttons-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,42 @@
opacity: 0.3;
aspect-ratio: 1;
width: 1em;
position: absolute;
top: 0;
right: 0;
overflow: hidden;
padding: 1.5em;
display: flex;
flex-direction: row;
justify-content: center;
align-content: center;
cursor: cell;
}
.ego-store-lite-game-buttons-list-item-edit > div {
position: absolute;
top: 0.5em;
right: 0.5em;
right: 1em;
z-index: 0;
}
.ego-store-lite-game-buttons-list-item-edit:hover {
opacity: 1;
.ego-store-lite-game-buttons-list-item-edit select {
width: 300px;
position: absolute;
opacity: 0;
height: 100%;
top: 0;
cursor: cell;
transition: opacity 0.3s;
z-index: 1;
}
.ego-store-lite-game-buttons-list-item-edit:active {
.ego-store-lite-game-buttons-list-item-edit select:hover {
opacity: 0.7;
}
.ego-store-lite-game-buttons-list-item-edit select:active {
opacity: 0.2;
}

.ego-store-lite-game-buttons-list-description {
user-select: none;
overflow: auto;
max-height: 110px;
}
Loading
Loading