|
1 | 1 | <script setup lang="ts">
|
2 |
| -import { computed, watch, watchEffect } from 'vue' |
3 |
| -import { useQuery, useQueryClient } from '@tanstack/vue-query' |
4 |
| -import { emptyTimeEntry } from './utils/timeEntries.ts' |
5 |
| -import { useStorage } from '@vueuse/core' |
6 |
| -import { getAllProjects } from './utils/projects.ts' |
7 |
| -import { getAllTasks } from './utils/tasks.ts' |
8 |
| -import { TimeTrackerStartStop, ProjectBadge } from '@solidtime/ui' |
9 |
| -import { ChevronRightIcon } from '@heroicons/vue/16/solid' |
10 |
| -import { useLiveTimer } from './utils/liveTimer.ts' |
11 |
| -import { time } from '@solidtime/ui' |
12 |
| -import { useMyMemberships } from './utils/myMemberships.ts' |
13 | 2 | import { isLoggedIn } from './utils/oauth.ts'
|
14 |
| -import { dayjs } from './utils/dayjs.ts' |
15 |
| -import { sendEventToWindow } from './utils/events.ts' |
16 | 3 | import { showMainWindow } from './utils/window.ts'
|
17 |
| -
|
18 |
| -const { liveTimer, startLiveTimer, stopLiveTimer } = useLiveTimer() |
19 |
| -const { currentOrganizationId } = useMyMemberships() |
20 |
| -
|
21 |
| -const isRunning = computed( |
22 |
| - () => currentTimeEntry.value.start !== '' && currentTimeEntry.value.start !== null |
23 |
| -) |
24 |
| -
|
25 |
| -const organizationIdToLoad = computed(() => { |
26 |
| - if (currentTimeEntry.value.organization_id && currentTimeEntry.value.organization_id !== '') { |
27 |
| - return currentTimeEntry.value.organization_id |
28 |
| - } |
29 |
| - return currentOrganizationId.value |
30 |
| -}) |
31 |
| -
|
32 |
| -const currentOrganizationLoaded = computed(() => !!organizationIdToLoad.value) |
33 |
| -
|
34 |
| -const currentTimeEntry = useStorage('currentTimeEntry', { ...emptyTimeEntry }) |
35 |
| -const { data: projectsResponse } = useQuery({ |
36 |
| - queryKey: ['projects', organizationIdToLoad], |
37 |
| - queryFn: () => getAllProjects(organizationIdToLoad.value), |
38 |
| - enabled: currentOrganizationLoaded, |
39 |
| -}) |
40 |
| -
|
41 |
| -const { data: tasksResponse } = useQuery({ |
42 |
| - queryKey: ['tasks', organizationIdToLoad], |
43 |
| - queryFn: () => getAllTasks(organizationIdToLoad.value), |
44 |
| - enabled: currentOrganizationLoaded, |
45 |
| -}) |
46 |
| -
|
47 |
| -const { data: currentTimeEntryTasksResponse } = useQuery({ |
48 |
| - queryKey: ['tasks', currentTimeEntry.value.organization_id], |
49 |
| - queryFn: () => getAllTasks(currentTimeEntry.value.organization_id), |
50 |
| - enabled: currentOrganizationLoaded, |
51 |
| -}) |
52 |
| -
|
53 |
| -const lastTimeEntry = useStorage('lastTimeEntry', { ...emptyTimeEntry }) |
54 |
| -
|
55 |
| -const tasks = computed(() => { |
56 |
| - if (isRunning.value) { |
57 |
| - return currentTimeEntryTasksResponse.value?.data |
58 |
| - } |
59 |
| - return tasksResponse.value?.data |
60 |
| -}) |
61 |
| -const projects = computed(() => { |
62 |
| - return projectsResponse.value?.data |
63 |
| -}) |
64 |
| -
|
65 |
| -const shownDescription = computed(() => { |
66 |
| - if (isRunning.value) { |
67 |
| - return currentTimeEntry.value.description !== '' |
68 |
| - ? currentTimeEntry.value.description |
69 |
| - : currentTask.value?.name |
70 |
| - } else if (!isRunning.value) { |
71 |
| - return lastTimeEntry.value.description !== '' |
72 |
| - ? lastTimeEntry.value.description |
73 |
| - : currentTask.value?.name |
74 |
| - } |
75 |
| - return null |
76 |
| -}) |
77 |
| -const currentTask = computed(() => { |
78 |
| - if (isRunning.value) { |
79 |
| - return tasks.value?.find((task) => task.id === currentTimeEntry.value.task_id) |
80 |
| - } else { |
81 |
| - return tasks.value?.find((task) => task.id === lastTimeEntry.value.task_id) |
82 |
| - } |
83 |
| -}) |
84 |
| -const shownProject = computed(() => { |
85 |
| - if (isRunning.value) { |
86 |
| - return projects.value?.find((project) => project.id === currentTimeEntry.value.project_id) |
87 |
| - } else { |
88 |
| - return projects.value?.find((project) => project.id === lastTimeEntry.value.project_id) |
89 |
| - } |
90 |
| -}) |
91 |
| -
|
92 |
| -const queryClient = useQueryClient() |
93 |
| -
|
94 |
| -// invalidate queries if we encounter projects or tasks that are not in the store |
95 |
| -// because stores are currently not synced between mini and main window |
96 |
| -// (future, currentlyexperimental: https://tanstack.com/query/latest/docs/framework/vue/plugins/createPersister) |
97 |
| -watch(currentTimeEntry, () => { |
98 |
| - console.log('currentTimeEntry changed') |
99 |
| - if ( |
100 |
| - currentTimeEntry.value.project_id && |
101 |
| - projects.value && |
102 |
| - !projects.value.some((project) => project.id === currentTimeEntry.value.project_id) |
103 |
| - ) { |
104 |
| - console.log('project invalidate') |
105 |
| - queryClient.invalidateQueries({ queryKey: ['projects'] }) |
106 |
| - } |
107 |
| - if ( |
108 |
| - currentTimeEntry.value.task_id && |
109 |
| - tasks.value && |
110 |
| - !tasks.value.some((task) => task.id === currentTimeEntry.value.task_id) |
111 |
| - ) { |
112 |
| - queryClient.invalidateQueries({ queryKey: ['tasks'] }) |
113 |
| - } |
114 |
| -}) |
115 |
| -watch(lastTimeEntry, () => { |
116 |
| - if ( |
117 |
| - lastTimeEntry.value.project_id && |
118 |
| - projects.value && |
119 |
| - !projects.value.some((project) => project.id === lastTimeEntry.value.project_id) |
120 |
| - ) { |
121 |
| - queryClient.invalidateQueries({ queryKey: ['projects'] }) |
122 |
| - } |
123 |
| - if ( |
124 |
| - lastTimeEntry.value.task_id && |
125 |
| - tasks.value && |
126 |
| - !tasks.value.some((task) => task.id === lastTimeEntry.value.task_id) |
127 |
| - ) { |
128 |
| - queryClient.invalidateQueries({ queryKey: ['tasks'] }) |
129 |
| - } |
130 |
| -}) |
131 |
| -
|
132 |
| -watchEffect(() => { |
133 |
| - if (isRunning.value) { |
134 |
| - startLiveTimer() |
135 |
| - } else { |
136 |
| - stopLiveTimer() |
137 |
| - } |
138 |
| -}) |
139 |
| -
|
140 |
| -function onToggleButtonPress(newState: boolean) { |
141 |
| - if (newState) { |
142 |
| - sendEventToWindow('main', 'startTimer') |
143 |
| - } else { |
144 |
| - showMainWindow() |
145 |
| - sendEventToWindow('main', 'stopTimer') |
146 |
| - } |
147 |
| -} |
| 4 | +import MiniControls from './components/MiniControls.vue' |
148 | 5 |
|
149 | 6 | function focusMainWindow() {
|
150 | 7 | showMainWindow()
|
151 | 8 | }
|
152 |
| -
|
153 |
| -const currentTimer = computed(() => { |
154 |
| - if (liveTimer.value && currentTimeEntry.value.start) { |
155 |
| - const startTime = dayjs(currentTimeEntry.value.start) |
156 |
| - const diff = liveTimer.value.diff(startTime, 'seconds') |
157 |
| - return time.formatDuration(diff) |
158 |
| - } |
159 |
| - return '00:00:00' |
160 |
| -}) |
161 | 9 | </script>
|
162 | 10 |
|
163 | 11 | <template>
|
164 | 12 | <div v-if="!isLoggedIn" @click="focusMainWindow">Log in with solidtime</div>
|
165 |
| - <div |
166 |
| - v-else |
167 |
| - class="h-screen relative w-screen border-border-secondary border bg-primary rounded-[10px] text-white py-1 flex items-center cursor-default justify-between select-none"> |
168 |
| - <div class="text-sm text-muted flex items-center relative flex-1 min-w-0"> |
169 |
| - <div class="pl-1 pr-1 z-20 relative block" style="-webkit-app-region: drag"> |
170 |
| - <svg |
171 |
| - class="h-5" |
172 |
| - viewBox="0 0 25 25" |
173 |
| - fill="none" |
174 |
| - xmlns="http://www.w3.org/2000/svg" |
175 |
| - data-tauri-drag-region> |
176 |
| - <path |
177 |
| - fill-rule="evenodd" |
178 |
| - clip-rule="evenodd" |
179 |
| - d="M9.5 8C10.3284 8 11 7.32843 11 6.5C11 5.67157 10.3284 5 9.5 5C8.67157 5 8 5.67157 8 6.5C8 7.32843 8.67157 8 9.5 8ZM9.5 14C10.3284 14 11 13.3284 11 12.5C11 11.6716 10.3284 11 9.5 11C8.67157 11 8 11.6716 8 12.5C8 13.3284 8.67157 14 9.5 14ZM11 18.5C11 19.3284 10.3284 20 9.5 20C8.67157 20 8 19.3284 8 18.5C8 17.6716 8.67157 17 9.5 17C10.3284 17 11 17.6716 11 18.5ZM15.5 8C16.3284 8 17 7.32843 17 6.5C17 5.67157 16.3284 5 15.5 5C14.6716 5 14 5.67157 14 6.5C14 7.32843 14.6716 8 15.5 8ZM17 12.5C17 13.3284 16.3284 14 15.5 14C14.6716 14 14 13.3284 14 12.5C14 11.6716 14.6716 11 15.5 11C16.3284 11 17 11.6716 17 12.5ZM15.5 20C16.3284 20 17 19.3284 17 18.5C17 17.6716 16.3284 17 15.5 17C14.6716 17 14 17.6716 14 18.5C14 19.3284 14.6716 20 15.5 20Z" |
180 |
| - fill="currentColor" |
181 |
| - data-tauri-drag-region /> |
182 |
| - </svg> |
183 |
| - </div> |
184 |
| - <div |
185 |
| - class="cursor-pointer rounded-lg flex items-center shrink min-w-0" |
186 |
| - @click="focusMainWindow"> |
187 |
| - <div class="flex items-center flex-1 space-x-0.5 min-w-0"> |
188 |
| - <ProjectBadge |
189 |
| - class="px-0 whitespace-nowrap overflow-ellipsis" |
190 |
| - :border="false" |
191 |
| - :color="shownProject?.color" |
192 |
| - :name="shownProject?.name ?? 'No Project'"></ProjectBadge> |
193 |
| - <div class="flex text-xs flex-1 truncate items-center space-x-0.5 shrink"> |
194 |
| - <ChevronRightIcon class="w-4 shrink-0 text-muted"></ChevronRightIcon> |
195 |
| - <span |
196 |
| - class="truncate shrink text-muted opacity-50 hover:opacity-100 transition-opacity min-w-0" |
197 |
| - >{{ shownDescription ?? 'No Description' }}</span |
198 |
| - > |
199 |
| - </div> |
200 |
| - </div> |
201 |
| - </div> |
202 |
| - <div class="flex-1 h-6 w-full" style="-webkit-app-region: drag"></div> |
203 |
| - </div> |
204 |
| - <div class="pr-1 flex items-center space-x-1"> |
205 |
| - <div |
206 |
| - class="text-xs font-semibold text-muted px-2 w-[65px] text-left" |
207 |
| - style="-webkit-app-region: drag"> |
208 |
| - {{ currentTimer }} |
209 |
| - </div> |
210 |
| - <TimeTrackerStartStop |
211 |
| - :active="isRunning" |
212 |
| - size="small" |
213 |
| - @changed="onToggleButtonPress"></TimeTrackerStartStop> |
214 |
| - </div> |
215 |
| - </div> |
| 13 | + <MiniControls v-else></MiniControls> |
216 | 14 | </template>
|
217 | 15 |
|
218 | 16 | <style scoped></style>
|
0 commit comments