super-time-tracker-ui 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +28 -0
- package/CLAUDE.md +1 -0
- package/README.md +36 -0
- package/app/api/backup/route.ts +39 -0
- package/app/api/entry/delete-bulk/route.ts +53 -0
- package/app/api/entry/move/route.ts +46 -0
- package/app/api/entry/move-bulk/route.ts +62 -0
- package/app/api/entry/route.ts +75 -0
- package/app/api/in/route.ts +38 -0
- package/app/api/note/route.ts +120 -0
- package/app/api/out/route.ts +31 -0
- package/app/api/sheet/route.ts +68 -0
- package/app/api/state/route.ts +16 -0
- package/app/api/tags/route.ts +75 -0
- package/app/color-palettes.css +260 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +140 -0
- package/app/layout.tsx +54 -0
- package/app/page.tsx +24 -0
- package/app/reporting/page.tsx +11 -0
- package/app/settings/data/page.tsx +9 -0
- package/app/settings/display/page.tsx +8 -0
- package/app/settings/page.tsx +12 -0
- package/app/settings/tags/page.tsx +13 -0
- package/bin/stt-ui.js +63 -0
- package/components/active-entry-panel.tsx +199 -0
- package/components/backup-restore-setting.tsx +168 -0
- package/components/check-in-form-collapsed-setting.tsx +44 -0
- package/components/check-in-form-collapsible.tsx +52 -0
- package/components/check-in-form.tsx +89 -0
- package/components/checkbox.tsx +75 -0
- package/components/checkout-button-group.tsx +73 -0
- package/components/chevron-icon.tsx +25 -0
- package/components/clear-tag-filters-on-sheet-change-setting.tsx +45 -0
- package/components/color-palette-setting.tsx +75 -0
- package/components/compact-lists-setting.tsx +42 -0
- package/components/confirm-before-checkout-setting.tsx +42 -0
- package/components/confirm-destructive-actions-setting.tsx +46 -0
- package/components/confirm-dialog-provider.tsx +71 -0
- package/components/confirm-dialog.tsx +90 -0
- package/components/data-settings-view.tsx +47 -0
- package/components/default-reporting-range-setting.tsx +56 -0
- package/components/default-reporting-sort-setting.tsx +45 -0
- package/components/default-sheet-session-setting.tsx +118 -0
- package/components/display-settings-view.tsx +75 -0
- package/components/duration-format-setting.tsx +40 -0
- package/components/entry-actions-menu.tsx +207 -0
- package/components/entry-edit-form.tsx +113 -0
- package/components/entry-list-bulk-bar.tsx +128 -0
- package/components/entry-list-sort-setting.tsx +41 -0
- package/components/entry-list.tsx +336 -0
- package/components/entry-notes-list.tsx +211 -0
- package/components/entry-tag-filter.tsx +99 -0
- package/components/format_datetime_hint.ts +8 -0
- package/components/format_time.ts +10 -0
- package/components/general-settings-view.tsx +40 -0
- package/components/hamburger-icon.tsx +21 -0
- package/components/note-edit-form.tsx +77 -0
- package/components/note-form.tsx +109 -0
- package/components/pencil-icon.tsx +21 -0
- package/components/reporting-date-range-picker.tsx +121 -0
- package/components/reporting-sort-controls.tsx +53 -0
- package/components/reporting-view.tsx +340 -0
- package/components/setting-radio-group.tsx +79 -0
- package/components/settings-nav.tsx +66 -0
- package/components/settings-page-layout.tsx +53 -0
- package/components/settings-saved-toast.tsx +57 -0
- package/components/sheet-actions-menu.tsx +108 -0
- package/components/sheet-sidebar.tsx +196 -0
- package/components/tag-autocomplete-input.tsx +183 -0
- package/components/tag-filter-mode-setting.tsx +47 -0
- package/components/tag-management-view.tsx +290 -0
- package/components/theme-mode-setting.tsx +44 -0
- package/components/theme-mode-system-listener.tsx +43 -0
- package/components/theme_switcher.tsx +38 -0
- package/components/time-format-setting.tsx +39 -0
- package/components/timer-in-title-setting.tsx +38 -0
- package/components/timer-show-seconds-setting.tsx +41 -0
- package/components/tracker-active-bar.tsx +76 -0
- package/components/tracker-app.tsx +338 -0
- package/components/tracker-breadcrumb.tsx +56 -0
- package/components/tracker-document-title.tsx +67 -0
- package/components/tracker-topbar.tsx +63 -0
- package/components/trash-icon.tsx +24 -0
- package/components/week-starts-on-setting.tsx +39 -0
- package/eslint.config.mjs +18 -0
- package/lib/add_note_to_entry.ts +65 -0
- package/lib/api_error_response.ts +10 -0
- package/lib/apply_accent_color.ts +12 -0
- package/lib/apply_color_palette.ts +12 -0
- package/lib/apply_compact_lists.ts +9 -0
- package/lib/apply_tag_autocomplete_selection.ts +26 -0
- package/lib/apply_theme.ts +8 -0
- package/lib/build_reporting_stats.ts +55 -0
- package/lib/build_resume_description.ts +15 -0
- package/lib/check_in_entry.ts +81 -0
- package/lib/check_out_entry.ts +75 -0
- package/lib/collect_known_tags.ts +22 -0
- package/lib/collect_tag_stats.ts +27 -0
- package/lib/collect_tags_from_entries.ts +35 -0
- package/lib/config.ts +9 -0
- package/lib/convert_json_db.ts +49 -0
- package/lib/delete_entries.ts +62 -0
- package/lib/delete_entry.ts +29 -0
- package/lib/delete_note_on_entry.ts +42 -0
- package/lib/delete_sheet.ts +30 -0
- package/lib/delete_tracker_action.ts +22 -0
- package/lib/edit_entry.ts +56 -0
- package/lib/edit_note_on_entry.ts +49 -0
- package/lib/ensure_dir_exists.ts +22 -0
- package/lib/entry_matches_tag_filter.ts +26 -0
- package/lib/fetch_tracker_state.ts +15 -0
- package/lib/filter_entries_by_tags.ts +20 -0
- package/lib/filter_known_tags.ts +20 -0
- package/lib/find_all_serialized_active_entries.ts +28 -0
- package/lib/find_serialized_active_entry.ts +12 -0
- package/lib/find_serialized_active_entry_for_sheet.ts +31 -0
- package/lib/find_sheet_with_active_entry.ts +16 -0
- package/lib/format_display_tag.ts +6 -0
- package/lib/format_duration.ts +45 -0
- package/lib/gen_db.ts +43 -0
- package/lib/get_active_panel_class_name.ts +20 -0
- package/lib/get_average_entry_ms.ts +13 -0
- package/lib/get_button_class_name.ts +24 -0
- package/lib/get_check_out_confirm_dialog.ts +19 -0
- package/lib/get_clipped_entry_duration_ms.ts +18 -0
- package/lib/get_compact_lists_snapshot.ts +15 -0
- package/lib/get_date_range_ms_from_inputs.ts +31 -0
- package/lib/get_delete_entries_confirm_dialog.ts +21 -0
- package/lib/get_delete_entry_confirm_dialog.ts +19 -0
- package/lib/get_delete_note_confirm_dialog.ts +21 -0
- package/lib/get_delete_sheet_confirm_dialog.ts +25 -0
- package/lib/get_entry_duration_ms.ts +14 -0
- package/lib/get_entry_row_key.ts +8 -0
- package/lib/get_initial_preferred_sheet_name.ts +34 -0
- package/lib/get_initial_reporting_range_inputs.ts +31 -0
- package/lib/get_input_class_name.ts +15 -0
- package/lib/get_merge_tags_confirm_dialog.ts +25 -0
- package/lib/get_period_range_ms.ts +43 -0
- package/lib/get_reporting_date_range_shortcut_inputs.ts +84 -0
- package/lib/get_reporting_period_totals.ts +39 -0
- package/lib/get_reporting_stats.ts +25 -0
- package/lib/get_restore_db_confirm_dialog.ts +14 -0
- package/lib/get_running_entry_key.ts +8 -0
- package/lib/get_serialized_entries_total_ms.ts +10 -0
- package/lib/get_sheet.ts +14 -0
- package/lib/get_sheet_report_stats.ts +22 -0
- package/lib/get_sheet_report_stats_for_range.ts +46 -0
- package/lib/get_sheet_tag_filter_snapshot.ts +22 -0
- package/lib/get_sheets_duration_in_range.ts +27 -0
- package/lib/get_tag_autocomplete_context.ts +32 -0
- package/lib/get_theme_snapshot.ts +16 -0
- package/lib/get_tracker_state.ts +67 -0
- package/lib/has_string_value.ts +6 -0
- package/lib/is_entry_in_day.ts +15 -0
- package/lib/is_idle_sheet_report.ts +8 -0
- package/lib/is_json_time_tracker_db.ts +14 -0
- package/lib/merge_tags_across_db.ts +79 -0
- package/lib/migrate_json_db.ts +56 -0
- package/lib/migrate_json_db_to_version_three.ts +51 -0
- package/lib/migrate_json_db_to_version_two.ts +50 -0
- package/lib/move_entries_to_sheet.ts +152 -0
- package/lib/move_entry_to_sheet.ts +82 -0
- package/lib/normalize_stored_tag.ts +16 -0
- package/lib/notify_settings_saved.ts +47 -0
- package/lib/parse_default_sheet_session_mode.ts +21 -0
- package/lib/parse_entry_from_input.ts +23 -0
- package/lib/parse_natural_language_date.ts +23 -0
- package/lib/parse_reporting_source_sheets.ts +22 -0
- package/lib/partition_sheet_report_stats.ts +30 -0
- package/lib/patch_tracker_action.ts +22 -0
- package/lib/persist_ui_preference.ts +18 -0
- package/lib/post_tracker_action.ts +22 -0
- package/lib/preferences/accent_color_preference.ts +21 -0
- package/lib/preferences/check_in_form_collapsed_preference.ts +20 -0
- package/lib/preferences/clear_tag_filters_on_sheet_change_preference.ts +20 -0
- package/lib/preferences/color_palette_preference.ts +21 -0
- package/lib/preferences/confirm_before_checkout_preference.ts +20 -0
- package/lib/preferences/confirm_destructive_actions_preference.ts +20 -0
- package/lib/preferences/default_reporting_range_preference.ts +21 -0
- package/lib/preferences/default_reporting_sort_preference.ts +24 -0
- package/lib/preferences/duration_format_preference.ts +19 -0
- package/lib/preferences/entry_list_sort_preference.ts +21 -0
- package/lib/preferences/tag_filter_mode_preference.ts +18 -0
- package/lib/preferences/theme_mode_preference.ts +18 -0
- package/lib/preferences/time_format_preference.ts +18 -0
- package/lib/preferences/timer_in_title_preference.ts +18 -0
- package/lib/preferences/timer_show_seconds_preference.ts +19 -0
- package/lib/preferences/week_starts_on_preference.ts +19 -0
- package/lib/prompt_check_out_at.ts +17 -0
- package/lib/prompt_entry_note.ts +14 -0
- package/lib/prune_sheet_tag_filter.ts +27 -0
- package/lib/read_db.ts +49 -0
- package/lib/read_db_backup_contents.ts +22 -0
- package/lib/read_document_compact_lists.ts +12 -0
- package/lib/read_document_theme.ts +14 -0
- package/lib/read_sheet_tag_filter.ts +26 -0
- package/lib/read_stored_active_sheet.ts +14 -0
- package/lib/read_stored_compact_lists.ts +24 -0
- package/lib/read_stored_default_sheet_fixed_name.ts +16 -0
- package/lib/read_stored_default_sheet_session_mode.ts +18 -0
- package/lib/read_stored_sheet_tag_filters.ts +28 -0
- package/lib/read_stored_theme.ts +18 -0
- package/lib/rename_sheet.ts +39 -0
- package/lib/rename_tag_across_db.ts +19 -0
- package/lib/resolve_active_sheet_name.ts +36 -0
- package/lib/resolve_session_preferred_sheet.ts +37 -0
- package/lib/resolve_theme.ts +18 -0
- package/lib/resolve_theme_mode_to_theme.ts +19 -0
- package/lib/restore_db_from_uploaded_json.ts +24 -0
- package/lib/serialize_entry.ts +27 -0
- package/lib/serialize_reporting_source_sheets.ts +19 -0
- package/lib/serialize_sheet_entries.ts +18 -0
- package/lib/set_accent_color.ts +12 -0
- package/lib/set_active_sheet.ts +18 -0
- package/lib/set_color_palette.ts +12 -0
- package/lib/set_compact_lists.ts +12 -0
- package/lib/set_default_sheet_fixed_name.ts +8 -0
- package/lib/set_default_sheet_session_mode.ts +11 -0
- package/lib/set_sheet_tag_filter.ts +13 -0
- package/lib/set_theme_mode.ts +19 -0
- package/lib/sheet_tag_filter_snapshots.ts +48 -0
- package/lib/sort_serialized_entries.ts +35 -0
- package/lib/sort_sheet_report_stats.ts +43 -0
- package/lib/subscribe_compact_lists.ts +25 -0
- package/lib/subscribe_sheet_tag_filters.ts +28 -0
- package/lib/subscribe_theme.ts +23 -0
- package/lib/sync_active_sheet_preference.ts +19 -0
- package/lib/tags_are_equal.ts +12 -0
- package/lib/theme_init_script.ts +11 -0
- package/lib/toggle_sheet_tag_filter.ts +28 -0
- package/lib/toggle_theme.ts +20 -0
- package/lib/types/confirm_dialog.ts +9 -0
- package/lib/types/data.ts +16 -0
- package/lib/types/generic_data.ts +25 -0
- package/lib/types/index.ts +2 -0
- package/lib/types/reporting.ts +59 -0
- package/lib/types/tag_management.ts +7 -0
- package/lib/types/theme.ts +3 -0
- package/lib/types/tracker_state.ts +39 -0
- package/lib/types/ui_preferences.ts +104 -0
- package/lib/types/ui_settings.ts +17 -0
- package/lib/ui_preference_store.ts +80 -0
- package/lib/ui_settings_init_script.ts +33 -0
- package/lib/use_check_in_form_collapsed.ts +18 -0
- package/lib/use_clear_tag_filters_on_sheet_change.ts +18 -0
- package/lib/use_confirm_before_checkout.ts +18 -0
- package/lib/use_confirm_destructive_actions.ts +18 -0
- package/lib/use_duration_format.ts +17 -0
- package/lib/use_entry_list_sort.ts +17 -0
- package/lib/use_tag_filter_mode.ts +17 -0
- package/lib/use_time_format.ts +17 -0
- package/lib/use_timer_in_title.ts +18 -0
- package/lib/use_timer_show_seconds.ts +18 -0
- package/lib/use_week_starts_on.ts +17 -0
- package/lib/validate_entry_times.ts +12 -0
- package/lib/week_starts_on_to_index.ts +8 -0
- package/lib/write_active_sheet_preference.ts +28 -0
- package/lib/write_db.ts +20 -0
- package/lib/write_sheet_tag_filter.ts +20 -0
- package/lib/write_stored_compact_lists.ts +15 -0
- package/lib/write_stored_default_sheet_fixed_name.ts +28 -0
- package/lib/write_stored_default_sheet_session_mode.ts +24 -0
- package/lib/write_stored_sheet_tag_filters.ts +18 -0
- package/lib/write_stored_theme.ts +12 -0
- package/next.config.ts +7 -0
- package/package.json +96 -0
- package/pnpm-workspace.yaml +7 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { THEME_STORAGE_KEY } from "@/lib/types/theme";
|
|
2
|
+
import { THEME_MODE_STORAGE_KEY } from "@/lib/types/ui_preferences";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Inline script that sets the theme before first paint to avoid flashing.
|
|
6
|
+
*/
|
|
7
|
+
export const theme_init_script = `(function(){try{var mk=${JSON.stringify(
|
|
8
|
+
THEME_MODE_STORAGE_KEY,
|
|
9
|
+
)};var tk=${JSON.stringify(
|
|
10
|
+
THEME_STORAGE_KEY,
|
|
11
|
+
)};var m=localStorage.getItem(mk);if(m!=='light'&&m!=='dark'&&m!=='system'){var legacy=localStorage.getItem(tk);m=legacy==='light'||legacy==='dark'?legacy:'system';localStorage.setItem(mk,m)}var resolve=function(mode){if(mode==='light'||mode==='dark')return mode;return window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark'};var t=resolve(m);document.documentElement.setAttribute('data-theme',t);localStorage.setItem(tk,t)}catch(e){document.documentElement.setAttribute('data-theme','dark')}})();`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { normalize_stored_tag } from '@/lib/normalize_stored_tag'
|
|
2
|
+
import { read_sheet_tag_filter } from '@/lib/read_sheet_tag_filter'
|
|
3
|
+
import { set_sheet_tag_filter } from '@/lib/set_sheet_tag_filter'
|
|
4
|
+
import { tags_are_equal } from '@/lib/tags_are_equal'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Toggles a tag in the active sheet's filter list.
|
|
8
|
+
*/
|
|
9
|
+
export function toggle_sheet_tag_filter(
|
|
10
|
+
sheet_name: string,
|
|
11
|
+
tag: string,
|
|
12
|
+
): void {
|
|
13
|
+
const normalized = normalize_stored_tag(tag)
|
|
14
|
+
const current = read_sheet_tag_filter(sheet_name)
|
|
15
|
+
const is_selected = current.some((filter_tag) =>
|
|
16
|
+
tags_are_equal(filter_tag, normalized),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if (is_selected) {
|
|
20
|
+
set_sheet_tag_filter(
|
|
21
|
+
sheet_name,
|
|
22
|
+
current.filter((filter_tag) => !tags_are_equal(filter_tag, normalized)),
|
|
23
|
+
)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set_sheet_tag_filter(sheet_name, [...current, normalized])
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { apply_theme } from "@/lib/apply_theme";
|
|
2
|
+
import { notify_theme_subscribers } from "@/lib/subscribe_theme";
|
|
3
|
+
import { read_document_theme } from "@/lib/read_document_theme";
|
|
4
|
+
import { theme_mode_preference } from "@/lib/preferences/theme_mode_preference";
|
|
5
|
+
import { type Theme } from "@/lib/types/theme";
|
|
6
|
+
import { write_stored_theme } from "@/lib/write_stored_theme";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Toggles the applied theme and sets the corresponding theme mode preference.
|
|
10
|
+
*/
|
|
11
|
+
export function toggle_theme(): void {
|
|
12
|
+
const current_theme = read_document_theme();
|
|
13
|
+
const next_theme: Theme = current_theme === "dark" ? "light" : "dark";
|
|
14
|
+
|
|
15
|
+
apply_theme(next_theme);
|
|
16
|
+
write_stored_theme(next_theme);
|
|
17
|
+
theme_mode_preference.write(next_theme);
|
|
18
|
+
theme_mode_preference.notify();
|
|
19
|
+
notify_theme_subscribers();
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ITimeSheet,
|
|
3
|
+
type ITimeSheetEntry,
|
|
4
|
+
type ITimeSheetEntryNote,
|
|
5
|
+
type ITimeTrackerDB,
|
|
6
|
+
} from "./generic_data";
|
|
7
|
+
|
|
8
|
+
export type TimeTrackerDB = ITimeTrackerDB<Date>;
|
|
9
|
+
export type TimeSheetEntry = ITimeSheetEntry<Date>;
|
|
10
|
+
export type TimeSheetEntryNote = ITimeSheetEntryNote<Date>;
|
|
11
|
+
export type TimeSheet = ITimeSheet<Date>;
|
|
12
|
+
|
|
13
|
+
export type JSONTimeTrackerDB = ITimeTrackerDB<number>;
|
|
14
|
+
export type JSONTimeSheetEntry = ITimeSheetEntry<number>;
|
|
15
|
+
export type JSONTimeSheetEntryNote = ITimeSheetEntryNote<number>;
|
|
16
|
+
export type JSONTimeSheet = ITimeSheet<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ITimeSheetEntryNote<T> {
|
|
2
|
+
timestamp: T;
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ITimeSheetEntry<T> {
|
|
7
|
+
id: number;
|
|
8
|
+
start: T;
|
|
9
|
+
end: T | null;
|
|
10
|
+
description: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
notes: ITimeSheetEntryNote<T>[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ITimeSheet<T> {
|
|
16
|
+
name: string;
|
|
17
|
+
entries: ITimeSheetEntry<T>[];
|
|
18
|
+
activeEntryID: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ITimeTrackerDB<T> {
|
|
22
|
+
sheets: ITimeSheet<T>[];
|
|
23
|
+
activeSheetName: string | null;
|
|
24
|
+
version: number;
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type SheetReportSort = 'duration' | 'name' | 'entry_count' | 'active_first'
|
|
2
|
+
|
|
3
|
+
export interface ReportingDateRangeInputs {
|
|
4
|
+
from_date: string
|
|
5
|
+
to_date: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ReportingDateRangeShortcut =
|
|
9
|
+
| 'today'
|
|
10
|
+
| 'yesterday'
|
|
11
|
+
| 'week'
|
|
12
|
+
| 'month'
|
|
13
|
+
| 'last_month'
|
|
14
|
+
| 'year'
|
|
15
|
+
| 'last_year'
|
|
16
|
+
|
|
17
|
+
export interface ReportingSourceEntry {
|
|
18
|
+
id: number
|
|
19
|
+
start: string
|
|
20
|
+
end: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReportingSourceSheet {
|
|
24
|
+
name: string
|
|
25
|
+
activeEntryID: number | null
|
|
26
|
+
entries: ReportingSourceEntry[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Time-tracking aggregates for a single sheet.
|
|
31
|
+
*/
|
|
32
|
+
export interface SheetReportStats {
|
|
33
|
+
sheetName: string
|
|
34
|
+
totalMs: number
|
|
35
|
+
entryCount: number
|
|
36
|
+
averageEntryMs: number
|
|
37
|
+
hasActiveEntry: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Time tracked within calendar periods, clipped to period boundaries.
|
|
42
|
+
*/
|
|
43
|
+
export interface ReportingPeriodTotals {
|
|
44
|
+
todayMs: number
|
|
45
|
+
weekMs: number
|
|
46
|
+
monthMs: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Cross-sheet reporting snapshot for the reporting view.
|
|
51
|
+
*/
|
|
52
|
+
export interface ReportingStats {
|
|
53
|
+
activeSheets: SheetReportStats[]
|
|
54
|
+
idleSheets: SheetReportStats[]
|
|
55
|
+
grandTotalMs: number
|
|
56
|
+
totalEntryCount: number
|
|
57
|
+
grandAverageEntryMs: number
|
|
58
|
+
periodTotals: ReportingPeriodTotals
|
|
59
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface SerializedNote {
|
|
2
|
+
timestamp: string
|
|
3
|
+
text: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface SerializedEntry {
|
|
7
|
+
id: number
|
|
8
|
+
description: string
|
|
9
|
+
start: string
|
|
10
|
+
end: string | null
|
|
11
|
+
tags: string[]
|
|
12
|
+
notes: SerializedNote[]
|
|
13
|
+
sheetName: string
|
|
14
|
+
durationMs: number
|
|
15
|
+
isActive: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SerializedSheet {
|
|
19
|
+
name: string
|
|
20
|
+
activeEntryID: number | null
|
|
21
|
+
entryCount: number
|
|
22
|
+
isActive: boolean
|
|
23
|
+
hasActiveEntry: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TrackerState {
|
|
27
|
+
dbPath: string
|
|
28
|
+
activeSheetName: string | null
|
|
29
|
+
sheets: SerializedSheet[]
|
|
30
|
+
knownTags: string[]
|
|
31
|
+
/** Running entry on the viewed sheet, if any. */
|
|
32
|
+
activeEntry: SerializedEntry | null
|
|
33
|
+
/** Any running entry (for the header bar and tab title). */
|
|
34
|
+
runningEntry: SerializedEntry | null
|
|
35
|
+
/** All running entries across sheets, in sheet order. */
|
|
36
|
+
runningEntries: SerializedEntry[]
|
|
37
|
+
activeSheetEntries: SerializedEntry[]
|
|
38
|
+
activeSheetTotalMs: number
|
|
39
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type ThemeMode = 'light' | 'dark' | 'system'
|
|
2
|
+
export const THEME_MODE_STORAGE_KEY = 'super-time-tracker-theme-mode'
|
|
3
|
+
export const THEME_MODE_DEFAULT: ThemeMode = 'system'
|
|
4
|
+
|
|
5
|
+
export type ColorPalette =
|
|
6
|
+
| 'default'
|
|
7
|
+
| 'midnight'
|
|
8
|
+
| 'warm'
|
|
9
|
+
| 'ocean'
|
|
10
|
+
| 'forest'
|
|
11
|
+
| 'contrast'
|
|
12
|
+
export const COLOR_PALETTE_STORAGE_KEY = 'super-time-tracker-color-palette'
|
|
13
|
+
export const COLOR_PALETTE_DEFAULT: ColorPalette = 'default'
|
|
14
|
+
export const COLOR_PALETTE_VALUES: ColorPalette[] = [
|
|
15
|
+
'default',
|
|
16
|
+
'midnight',
|
|
17
|
+
'warm',
|
|
18
|
+
'ocean',
|
|
19
|
+
'forest',
|
|
20
|
+
'contrast',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export type TimeFormat = '12h' | '24h'
|
|
24
|
+
export const TIME_FORMAT_STORAGE_KEY = 'super-time-tracker-time-format'
|
|
25
|
+
export const TIME_FORMAT_DEFAULT: TimeFormat = '12h'
|
|
26
|
+
|
|
27
|
+
export type DurationFormat = 'humanized' | 'clock' | 'decimal'
|
|
28
|
+
export const DURATION_FORMAT_STORAGE_KEY = 'super-time-tracker-duration-format'
|
|
29
|
+
export const DURATION_FORMAT_DEFAULT: DurationFormat = 'humanized'
|
|
30
|
+
|
|
31
|
+
export type WeekStartsOn = 'monday' | 'sunday'
|
|
32
|
+
export const WEEK_STARTS_ON_STORAGE_KEY = 'super-time-tracker-week-starts-on'
|
|
33
|
+
export const WEEK_STARTS_ON_DEFAULT: WeekStartsOn = 'monday'
|
|
34
|
+
|
|
35
|
+
export type DefaultReportingSort =
|
|
36
|
+
| 'duration'
|
|
37
|
+
| 'name'
|
|
38
|
+
| 'entry_count'
|
|
39
|
+
| 'active_first'
|
|
40
|
+
export const DEFAULT_REPORTING_SORT_STORAGE_KEY =
|
|
41
|
+
'super-time-tracker-default-reporting-sort'
|
|
42
|
+
export const DEFAULT_REPORTING_SORT_DEFAULT: DefaultReportingSort = 'duration'
|
|
43
|
+
|
|
44
|
+
export type CheckInFormCollapsed = 'true' | 'false'
|
|
45
|
+
export const CHECK_IN_FORM_COLLAPSED_STORAGE_KEY =
|
|
46
|
+
'super-time-tracker-check-in-form-collapsed'
|
|
47
|
+
export const CHECK_IN_FORM_COLLAPSED_DEFAULT: CheckInFormCollapsed = 'false'
|
|
48
|
+
|
|
49
|
+
export type ConfirmBeforeCheckout = 'true' | 'false'
|
|
50
|
+
export const CONFIRM_BEFORE_CHECKOUT_STORAGE_KEY =
|
|
51
|
+
'super-time-tracker-confirm-before-checkout'
|
|
52
|
+
export const CONFIRM_BEFORE_CHECKOUT_DEFAULT: ConfirmBeforeCheckout = 'false'
|
|
53
|
+
|
|
54
|
+
export type ConfirmDestructiveActions = 'true' | 'false'
|
|
55
|
+
export const CONFIRM_DESTRUCTIVE_ACTIONS_STORAGE_KEY =
|
|
56
|
+
'super-time-tracker-confirm-destructive-actions'
|
|
57
|
+
export const CONFIRM_DESTRUCTIVE_ACTIONS_DEFAULT: ConfirmDestructiveActions =
|
|
58
|
+
'true'
|
|
59
|
+
|
|
60
|
+
export type AccentColor =
|
|
61
|
+
| 'teal'
|
|
62
|
+
| 'blue'
|
|
63
|
+
| 'violet'
|
|
64
|
+
| 'rose'
|
|
65
|
+
| 'amber'
|
|
66
|
+
| 'emerald'
|
|
67
|
+
export const ACCENT_COLOR_STORAGE_KEY = 'super-time-tracker-accent-color'
|
|
68
|
+
export const ACCENT_COLOR_DEFAULT: AccentColor = 'teal'
|
|
69
|
+
export const ACCENT_COLOR_VALUES: AccentColor[] = [
|
|
70
|
+
'teal',
|
|
71
|
+
'blue',
|
|
72
|
+
'violet',
|
|
73
|
+
'rose',
|
|
74
|
+
'amber',
|
|
75
|
+
'emerald',
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
export type TimerShowSeconds = 'true' | 'false'
|
|
79
|
+
export const TIMER_SHOW_SECONDS_STORAGE_KEY =
|
|
80
|
+
'super-time-tracker-timer-show-seconds'
|
|
81
|
+
export const TIMER_SHOW_SECONDS_DEFAULT: TimerShowSeconds = 'false'
|
|
82
|
+
|
|
83
|
+
export type TimerInTitle = 'true' | 'false'
|
|
84
|
+
export const TIMER_IN_TITLE_STORAGE_KEY = 'super-time-tracker-timer-in-title'
|
|
85
|
+
export const TIMER_IN_TITLE_DEFAULT: TimerInTitle = 'true'
|
|
86
|
+
|
|
87
|
+
export type TagFilterMode = 'all' | 'any'
|
|
88
|
+
export const TAG_FILTER_MODE_STORAGE_KEY = 'super-time-tracker-tag-filter-mode'
|
|
89
|
+
export const TAG_FILTER_MODE_DEFAULT: TagFilterMode = 'all'
|
|
90
|
+
|
|
91
|
+
export type EntryListSort = 'newest' | 'oldest' | 'duration' | 'description'
|
|
92
|
+
export const ENTRY_LIST_SORT_STORAGE_KEY = 'super-time-tracker-entry-list-sort'
|
|
93
|
+
export const ENTRY_LIST_SORT_DEFAULT: EntryListSort = 'newest'
|
|
94
|
+
|
|
95
|
+
export type DefaultReportingRange = 'none' | 'today' | 'week'
|
|
96
|
+
export const DEFAULT_REPORTING_RANGE_STORAGE_KEY =
|
|
97
|
+
'super-time-tracker-default-reporting-range'
|
|
98
|
+
export const DEFAULT_REPORTING_RANGE_DEFAULT: DefaultReportingRange = 'none'
|
|
99
|
+
|
|
100
|
+
export type ClearTagFiltersOnSheetChange = 'true' | 'false'
|
|
101
|
+
export const CLEAR_TAG_FILTERS_ON_SHEET_CHANGE_STORAGE_KEY =
|
|
102
|
+
'super-time-tracker-clear-tag-filters-on-sheet-change'
|
|
103
|
+
export const CLEAR_TAG_FILTERS_ON_SHEET_CHANGE_DEFAULT: ClearTagFiltersOnSheetChange =
|
|
104
|
+
'false'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const COMPACT_LISTS_STORAGE_KEY = 'super-time-tracker-compact-lists'
|
|
2
|
+
export const SHEET_TAG_FILTERS_STORAGE_KEY = 'super-time-tracker-sheet-tag-filters'
|
|
3
|
+
export const ACTIVE_SHEET_STORAGE_KEY = 'super-time-tracker-active-sheet'
|
|
4
|
+
export const ACTIVE_SHEET_COOKIE_NAME = 'stt-active-sheet'
|
|
5
|
+
|
|
6
|
+
export type DefaultSheetSessionMode = 'last_viewed' | 'active_timer' | 'fixed'
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_SHEET_SESSION_MODE_STORAGE_KEY =
|
|
9
|
+
'super-time-tracker-default-sheet-session-mode'
|
|
10
|
+
export const DEFAULT_SHEET_SESSION_MODE_COOKIE_NAME = 'stt-default-sheet-session-mode'
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_SHEET_FIXED_NAME_STORAGE_KEY =
|
|
13
|
+
'super-time-tracker-default-sheet-fixed-name'
|
|
14
|
+
export const DEFAULT_SHEET_FIXED_NAME_COOKIE_NAME = 'stt-default-sheet-fixed-name'
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_SHEET_SESSION_MODE_DEFAULT: DefaultSheetSessionMode =
|
|
17
|
+
'last_viewed'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
type PreferenceListener = () => void
|
|
2
|
+
|
|
3
|
+
export interface UiPreferenceStore<T extends string> {
|
|
4
|
+
storage_key: string
|
|
5
|
+
default_value: T
|
|
6
|
+
is_valid: (value: string) => value is T
|
|
7
|
+
read: () => T
|
|
8
|
+
write: (value: T) => void
|
|
9
|
+
subscribe: (listener: PreferenceListener) => () => void
|
|
10
|
+
notify: () => void
|
|
11
|
+
get_snapshot: () => T
|
|
12
|
+
get_server_snapshot: () => T
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CreateUiPreferenceStoreArgs<T extends string> {
|
|
16
|
+
storage_key: string
|
|
17
|
+
default_value: T
|
|
18
|
+
is_valid: (value: string) => value is T
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a localStorage-backed preference store with subscribe/snapshot helpers.
|
|
23
|
+
*/
|
|
24
|
+
export function create_ui_preference_store<T extends string>(
|
|
25
|
+
args: CreateUiPreferenceStoreArgs<T>,
|
|
26
|
+
): UiPreferenceStore<T> {
|
|
27
|
+
const { default_value, is_valid, storage_key } = args
|
|
28
|
+
const listeners = new Set<PreferenceListener>()
|
|
29
|
+
|
|
30
|
+
const read = (): T => {
|
|
31
|
+
if (typeof window === 'undefined') {
|
|
32
|
+
return default_value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const raw = window.localStorage.getItem(storage_key)
|
|
37
|
+
|
|
38
|
+
if (raw !== null && is_valid(raw)) {
|
|
39
|
+
return raw
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Ignore storage failures.
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return default_value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const write = (value: T): void => {
|
|
49
|
+
try {
|
|
50
|
+
window.localStorage.setItem(storage_key, value)
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore storage failures.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const subscribe = (listener: PreferenceListener): (() => void) => {
|
|
57
|
+
listeners.add(listener)
|
|
58
|
+
return () => {
|
|
59
|
+
listeners.delete(listener)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const notify = (): void => {
|
|
64
|
+
listeners.forEach((listener) => {
|
|
65
|
+
listener()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
storage_key,
|
|
71
|
+
default_value,
|
|
72
|
+
is_valid,
|
|
73
|
+
read,
|
|
74
|
+
write,
|
|
75
|
+
subscribe,
|
|
76
|
+
notify,
|
|
77
|
+
get_snapshot: read,
|
|
78
|
+
get_server_snapshot: () => default_value,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { COMPACT_LISTS_STORAGE_KEY } from '@/lib/types/ui_settings'
|
|
2
|
+
import {
|
|
3
|
+
ACCENT_COLOR_STORAGE_KEY,
|
|
4
|
+
COLOR_PALETTE_DEFAULT,
|
|
5
|
+
COLOR_PALETTE_STORAGE_KEY,
|
|
6
|
+
COLOR_PALETTE_VALUES,
|
|
7
|
+
} from '@/lib/types/ui_preferences'
|
|
8
|
+
|
|
9
|
+
const accent_to_palette_migration: Record<string, string> = {
|
|
10
|
+
teal: 'default',
|
|
11
|
+
blue: 'ocean',
|
|
12
|
+
violet: 'midnight',
|
|
13
|
+
rose: 'warm',
|
|
14
|
+
amber: 'warm',
|
|
15
|
+
emerald: 'forest',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inline script that applies UI settings before first paint.
|
|
20
|
+
*/
|
|
21
|
+
export const ui_settings_init_script = `(function(){try{var k=${JSON.stringify(
|
|
22
|
+
COMPACT_LISTS_STORAGE_KEY,
|
|
23
|
+
)};var s=localStorage.getItem(k);var c=s==='true';document.documentElement.setAttribute('data-compact-lists',c?'true':'false');var pk=${JSON.stringify(
|
|
24
|
+
COLOR_PALETTE_STORAGE_KEY,
|
|
25
|
+
)};var pv=localStorage.getItem(pk);var palettes=${JSON.stringify(
|
|
26
|
+
COLOR_PALETTE_VALUES,
|
|
27
|
+
)};var migrate=${JSON.stringify(accent_to_palette_migration)};var ak=${JSON.stringify(
|
|
28
|
+
ACCENT_COLOR_STORAGE_KEY,
|
|
29
|
+
)};var p=palettes.indexOf(pv)>=0?pv:(migrate[localStorage.getItem(ak)]||${JSON.stringify(
|
|
30
|
+
COLOR_PALETTE_DEFAULT,
|
|
31
|
+
)});document.documentElement.setAttribute('data-palette',p);if(palettes.indexOf(pv)<0){localStorage.setItem(pk,p)}}catch(e){document.documentElement.setAttribute('data-compact-lists','false');document.documentElement.setAttribute('data-palette',${JSON.stringify(
|
|
32
|
+
COLOR_PALETTE_DEFAULT,
|
|
33
|
+
)})}})();`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { check_in_form_collapsed_preference } from '@/lib/preferences/check_in_form_collapsed_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the "collapse check-in form" preference (boolean).
|
|
9
|
+
*/
|
|
10
|
+
export function use_check_in_form_collapsed(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
check_in_form_collapsed_preference.subscribe,
|
|
13
|
+
check_in_form_collapsed_preference.get_snapshot,
|
|
14
|
+
check_in_form_collapsed_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { clear_tag_filters_on_sheet_change_preference } from '@/lib/preferences/clear_tag_filters_on_sheet_change_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the clear-tag-filters-on-sheet-change preference.
|
|
9
|
+
*/
|
|
10
|
+
export function use_clear_tag_filters_on_sheet_change(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
clear_tag_filters_on_sheet_change_preference.subscribe,
|
|
13
|
+
clear_tag_filters_on_sheet_change_preference.get_snapshot,
|
|
14
|
+
clear_tag_filters_on_sheet_change_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { confirm_before_checkout_preference } from '@/lib/preferences/confirm_before_checkout_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the confirm-before-checkout preference (boolean).
|
|
9
|
+
*/
|
|
10
|
+
export function use_confirm_before_checkout(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
confirm_before_checkout_preference.subscribe,
|
|
13
|
+
confirm_before_checkout_preference.get_snapshot,
|
|
14
|
+
confirm_before_checkout_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { confirm_destructive_actions_preference } from '@/lib/preferences/confirm_destructive_actions_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the confirm-destructive-actions preference (boolean).
|
|
9
|
+
*/
|
|
10
|
+
export function use_confirm_destructive_actions(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
confirm_destructive_actions_preference.subscribe,
|
|
13
|
+
confirm_destructive_actions_preference.get_snapshot,
|
|
14
|
+
confirm_destructive_actions_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { duration_format_preference } from '@/lib/preferences/duration_format_preference'
|
|
6
|
+
import { type DurationFormat } from '@/lib/types/ui_preferences'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to the duration format preference for live updates in client components.
|
|
10
|
+
*/
|
|
11
|
+
export function use_duration_format(): DurationFormat {
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
duration_format_preference.subscribe,
|
|
14
|
+
duration_format_preference.get_snapshot,
|
|
15
|
+
duration_format_preference.get_server_snapshot,
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { entry_list_sort_preference } from '@/lib/preferences/entry_list_sort_preference'
|
|
6
|
+
import { type EntryListSort } from '@/lib/types/ui_preferences'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to the entry list sort preference.
|
|
10
|
+
*/
|
|
11
|
+
export function use_entry_list_sort(): EntryListSort {
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
entry_list_sort_preference.subscribe,
|
|
14
|
+
entry_list_sort_preference.get_snapshot,
|
|
15
|
+
entry_list_sort_preference.get_server_snapshot,
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { tag_filter_mode_preference } from '@/lib/preferences/tag_filter_mode_preference'
|
|
6
|
+
import { type TagFilterMode } from '@/lib/types/ui_preferences'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to the tag filter match mode preference.
|
|
10
|
+
*/
|
|
11
|
+
export function use_tag_filter_mode(): TagFilterMode {
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
tag_filter_mode_preference.subscribe,
|
|
14
|
+
tag_filter_mode_preference.get_snapshot,
|
|
15
|
+
tag_filter_mode_preference.get_server_snapshot,
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { time_format_preference } from '@/lib/preferences/time_format_preference'
|
|
6
|
+
import { type TimeFormat } from '@/lib/types/ui_preferences'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to the time format preference for live updates in client components.
|
|
10
|
+
*/
|
|
11
|
+
export function use_time_format(): TimeFormat {
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
time_format_preference.subscribe,
|
|
14
|
+
time_format_preference.get_snapshot,
|
|
15
|
+
time_format_preference.get_server_snapshot,
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { timer_in_title_preference } from '@/lib/preferences/timer_in_title_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the timer-in-document-title preference.
|
|
9
|
+
*/
|
|
10
|
+
export function use_timer_in_title(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
timer_in_title_preference.subscribe,
|
|
13
|
+
timer_in_title_preference.get_snapshot,
|
|
14
|
+
timer_in_title_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
|
+
|
|
5
|
+
import { timer_show_seconds_preference } from '@/lib/preferences/timer_show_seconds_preference'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to the show-seconds-on-timer preference.
|
|
9
|
+
*/
|
|
10
|
+
export function use_timer_show_seconds(): boolean {
|
|
11
|
+
const value = useSyncExternalStore(
|
|
12
|
+
timer_show_seconds_preference.subscribe,
|
|
13
|
+
timer_show_seconds_preference.get_snapshot,
|
|
14
|
+
timer_show_seconds_preference.get_server_snapshot,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return value === 'true'
|
|
18
|
+
}
|