generated from MaxGripe/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMain.fs
190 lines (178 loc) · 10.3 KB
/
Main.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Core UI logic and components
namespace TaskTimeTracker
open Avalonia
open Avalonia.Controls
open Avalonia.Controls.Primitives
open Avalonia.FuncUI
open Avalonia.FuncUI.DSL
open Avalonia.Layout
open System
open System.Timers
module Main =
let view () =
Component(fun ctx ->
// States
let runningTaskIndex = ctx.useState None
let tasks = ctx.useState (Persistence.loadTasksFromFile())
let timerState = ctx.useState<Timer option>(None)
let autoSaveTimer = new Timer(60000.0)
autoSaveTimer.AutoReset <- true
// Timer handler for task time tracking
let timerHandler = ElapsedEventHandler(fun _ _ ->
match runningTaskIndex.Current with
| Some index ->
tasks.Set(
tasks.Current
|> List.mapi (fun i (name, elapsedTime) ->
if i = index then (name, elapsedTime + TimeSpan.FromSeconds(1.0))
else (name, elapsedTime)
)
)
| None ->
match timerState.Current with
| Some t -> t.Enabled <- false
| None -> ()
)
// Timer handler for auto-saving tasks
let autoSaveHandler = ElapsedEventHandler(fun _ _ ->
Persistence.saveTasksToFile tasks.Current
)
autoSaveTimer.Elapsed.AddHandler(autoSaveHandler)
autoSaveTimer.Start()
// Create Timer once and store it in state
ctx.useEffect(
(fun () ->
let timer = new Timer(1000.0)
timer.AutoReset <- true
timer.Elapsed.AddHandler(timerHandler)
timerState.Set(Some timer)
{ new IDisposable with
member _.Dispose() =
timer.Stop()
timer.Elapsed.RemoveHandler(timerHandler)
timer.Dispose()
autoSaveTimer.Stop()
autoSaveTimer.Elapsed.RemoveHandler(autoSaveHandler)
Persistence.saveTasksToFile tasks.Current
}
),
[ EffectTrigger.AfterInit ]
)
let removeTask index =
tasks.Set(tasks.Current |> List.indexed |> List.filter (fun (i, _) -> i <> index) |> List.map snd)
match runningTaskIndex.Current with
| Some i when i = index ->
runningTaskIndex.Set(None)
match timerState.Current with
| Some t -> t.Enabled <- false
| None -> ()
| _ -> ()
let addTask () =
tasks.Set(tasks.Current @ [("New task", TimeSpan.Zero)])
let resetTimers () =
tasks.Set(tasks.Current |> List.map (fun (n, _) -> (n, TimeSpan.Zero)))
let togglePlayPause index =
match runningTaskIndex.Current with
| Some i when i = index ->
runningTaskIndex.Set(None)
timerState.Current |> Option.iter (fun t -> t.Enabled <- false)
| _ ->
runningTaskIndex.Set(Some index)
timerState.Current |> Option.iter (fun t -> t.Stop(); t.Start())
// Calculate total elapsed time
let totalElapsedTime =
tasks.Current
|> List.fold (fun acc (_, elapsedTime) -> acc + elapsedTime) TimeSpan.Zero
DockPanel.create [
DockPanel.children [
ScrollViewer.create [
ScrollViewer.verticalScrollBarVisibility ScrollBarVisibility.Auto
ScrollViewer.content (
StackPanel.create [
StackPanel.margin (Thickness(10.0))
StackPanel.children (
(tasks.Current |> List.indexed |> List.map (fun (index, (task, elapsedTime)) ->
Grid.create [
Grid.columnDefinitions "50, *, 50, 100"
Grid.horizontalAlignment HorizontalAlignment.Stretch
Grid.children [
Button.create [
Button.content "🚮"
Button.margin (Thickness(0.0, 0.0, 10.0, 0.0))
Button.onClick (fun _ -> removeTask index)
Grid.column 0
]
TextBox.create [
TextBox.text task
TextBox.fontSize 18.0
TextBox.margin (Thickness(0.0, 5.0))
TextBox.horizontalAlignment HorizontalAlignment.Stretch
TextBox.onTextChanged (fun newText ->
tasks.Set(
tasks.Current |> List.mapi (fun i (n, t) ->
if i = index then (newText, t) else (n, t)
)
)
)
TextBox.onGotFocus (fun args ->
let textBox = args.Source :?> TextBox
if textBox.Text = "New task" then textBox.Clear()
)
Grid.column 1
]
Button.create [
Button.content (if runningTaskIndex.Current = Some index then "⏸️" else "⏯️")
Button.margin (Thickness(5.0, 0.0, 0.0, 0.0))
Button.horizontalAlignment HorizontalAlignment.Right
Button.onClick (fun _ -> togglePlayPause index)
Grid.column 2
]
TextBlock.create [
TextBlock.text $"%02d{elapsedTime.Hours}:%02d{elapsedTime.Minutes}:%02d{elapsedTime.Seconds}"
TextBlock.margin (Thickness(10.0, 0.0, 10.0, 0.0))
TextBlock.fontSize 18.0
TextBlock.verticalAlignment VerticalAlignment.Center
TextBlock.horizontalAlignment HorizontalAlignment.Right
Grid.column 3
]
]
]
)) @ [
Grid.create [
Grid.columnDefinitions "auto, *"
Grid.children [
StackPanel.create [
StackPanel.orientation Orientation.Horizontal
StackPanel.children [
Button.create [
Button.content "🆕"
Button.margin (Thickness(0.0, 10.0, 10.0, 0.0))
Button.horizontalAlignment HorizontalAlignment.Left
Button.onClick (fun _ -> addTask ())
]
Button.create [
Button.content "🔄"
Button.margin (Thickness(0.0, 10.0, 10.0, 0.0))
Button.horizontalAlignment HorizontalAlignment.Left
Button.onClick (fun _ -> resetTimers ())
]
TextBlock.create [
TextBlock.text $"Total time: %02d{totalElapsedTime.Hours}:%02d{totalElapsedTime.Minutes}:%02d{totalElapsedTime.Seconds}"
TextBlock.fontSize 18.0
TextBlock.verticalAlignment VerticalAlignment.Center
TextBlock.horizontalAlignment HorizontalAlignment.Left
]
]
Grid.column 0
Grid.columnSpan 2
]
]
]
]
)
]
)
]
]
]
)