Skip to content

Commit 085f32d

Browse files
committed
Corsia Browser dedicata nella Timeline, occorrenze raggruppate per titolo
Separa l'attività dei browser (nuova isBrowserApp, ~35 nomi .exe) dalla corsia "Generale" della Timeline in una corsia propria, raggruppata per nome del browser invece che per singolo titolo — niente più muro di blocchi quasi identici per ogni pagina/episodio visitato, dato che il dominio reale del sito richiederebbe l'estensione browser ufficiale di ActivityWatch, non più raggiungibile da questo server in-process. Generalizza inoltre "Altre occorrenze" nel popup di dettaglio di un blocco: ora raggruppa per titolo, per qualunque corsia con un campo title distinto (non solo Browser), sostituendo la vecchia tabella "titolo -> durata totale" limitata a Generale. Ogni titolo raggruppato ha un pulsante per vedere solo gli screenshot dei suoi stessi orari. Corregge anche un bug reale: cliccare un'attività specifica di un browser da "Titoli finestra principali" evidenziava per sbaglio l'intera corsia (tutte le sessioni della giornata) invece della sola attività cliccata.
1 parent a12d83b commit 085f32d

9 files changed

Lines changed: 542 additions & 192 deletions

File tree

docs/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ quella della lingua attiva, per la versione in esecuzione (vedi
55
`src-tauri/src/about.rs`, che scarica questo file da GitHub Pages).
66
Non toccare questo formato senza aggiornare anche quel parser.
77

8+
## 0.1.14
9+
10+
### it
11+
- Nuovo: l'attività dei browser (Chrome, Firefox, Edge, Zen, Brave e molti altri) ha ora una corsia dedicata nella Timeline della Home, invece di riempire la corsia "Generale" con un blocco per ogni pagina/scheda visitata.
12+
- Nuovo: nel dettaglio di un blocco della Timeline, quando ci sono più titoli distinti (es. più pagine visitate nello stesso browser), "Altre occorrenze" li mostra ora raggruppati per titolo invece che come un unico elenco mescolato.
13+
- Nuovo: ogni titolo raggruppato ha un pulsante per vedere solo gli screenshot catturati durante gli orari di quel titolo specifico, non quelli dell'intero blocco.
14+
- Corretto un bug per cui, cliccando un'attività specifica di un browser da "Titoli finestra principali", la Timeline evidenziava per sbaglio tutta la corsia (tutte le sessioni della giornata) invece della sola attività cliccata.
15+
16+
### en
17+
- New: browser activity (Chrome, Firefox, Edge, Zen, Brave and many others) now gets its own dedicated lane in the Home Timeline, instead of filling the "General" lane with one block per page/tab visited.
18+
- New: in a Timeline block's detail popup, when there are multiple distinct titles (e.g. several pages visited in the same browser), "Other occurrences" now groups them by title instead of one mixed list.
19+
- New: each grouped title has a button to view only the screenshots captured during that specific title's own time ranges, not the whole block's.
20+
- Fixed a bug where clicking a specific browser activity from "Top Window Titles" incorrectly highlighted the entire lane (every session that day) instead of just the clicked activity.
21+
822
## 0.1.13
923

1024
### it

docs/changelog.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ <h1>Changelog</h1>
4242
errore mostrato.
4343
-->
4444
<div id="changelog-root">
45+
<section class="changelog-entry">
46+
<h2>0.1.14</h2>
47+
<ul>
48+
<li>New: browser activity (Chrome, Firefox, Edge, Zen, Brave and many others) now gets its own dedicated lane in the Home Timeline, instead of filling the "General" lane with one block per page/tab visited.</li>
49+
<li>New: in a Timeline block's detail popup, when there are multiple distinct titles (e.g. several pages visited in the same browser), "Other occurrences" now groups them by title instead of one mixed list.</li>
50+
<li>New: each grouped title has a button to view only the screenshots captured during that specific title's own time ranges, not the whole block's.</li>
51+
<li>Fixed a bug where clicking a specific browser activity from "Top Window Titles" incorrectly highlighted the entire lane (every session that day) instead of just the clicked activity.</li>
52+
</ul>
53+
</section>
4554
<section class="changelog-entry">
4655
<h2>0.1.13</h2>
4756
<ul>

src/components/AiChatWidget.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,9 @@ export default {
289289
white-space: pre-wrap;
290290
}
291291
292-
// Vue 2 (non Vue 3): ::v-deep, non :deep() — vedi lo stesso pattern già
293-
// usato in ScreenshotGalleryModal.vue per raggiungere markup iniettato
294-
// dinamicamente (lì un SVG, qui l'HTML prodotto da marked via v-html).
292+
// Vue 2 (non Vue 3): ::v-deep, non :deep() — stesso pattern già usato
293+
// altrove nell'app per raggiungere markup iniettato dinamicamente
294+
// (qui l'HTML prodotto da marked via v-html).
295295
.ai-chat-markdown {
296296
::v-deep p {
297297
margin: 0 0 8px;

src/components/HomeTimelineSection.vue

Lines changed: 295 additions & 140 deletions
Large diffs are not rendered by default.

src/components/ScreenshotGalleryModal.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,12 @@ div
210210
// matching timestamps between the Top Window Titles breakdown and the
211211
// screenshot strip by hand. This groups screenshots by whichever window
212212
// title was actually active when each was taken (via titleSegments'
213-
// real start/end times, see HomeTimelineSection.vue's
214-
// selectedBlockTitleSegments()), and opens a larger lightbox with
215-
// Prev/Next navigation across every screenshot in the block, not just
216-
// the ones in the group you clicked from.
213+
// real start/end times — the caller, TimelineBlockDetailModal.vue,
214+
// passes only the segments/screenshots for whichever title's "N foto"
215+
// link was clicked, already scoped to that title's own occurrences),
216+
// and opens a larger lightbox with Prev/Next navigation across every
217+
// screenshot passed in, not just the ones in the group you clicked
218+
// from within the lightbox.
217219
import moment from 'moment';
218220
import 'vue-awesome/icons/angle-left';
219221
import 'vue-awesome/icons/angle-right';

src/components/TimelineBlockDetailModal.vue

Lines changed: 161 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,44 @@ div
1414
span.block-detail-label {{ $t('home.timelineBlockDetail.duration') }}
1515
span.block-detail-value {{ formatDuration(block.end.diff(block.start, 'seconds')) }}
1616

17-
div.block-detail-subhead(v-if="occurrences.length > 1") {{ $t('home.timelineBlockDetail.otherOccurrences') }}
18-
table.block-detail-table(v-if="occurrences.length > 1")
17+
div.block-detail-subhead-row(v-if="occurrencesByTitle.length || occurrences.length > 1")
18+
span.block-detail-subhead {{ $t('home.timelineBlockDetail.otherOccurrences') }}
19+
// Solo nel caso non raggruppato — quando i titoli sono raggruppati,
20+
// il pulsante vive accanto a ciascun titolo invece che qui.
21+
span.block-detail-photos-btn(
22+
v-if="!occurrencesByTitle.length && screenshotsFor(occurrences).length"
23+
@click="openGallery(displayName, occurrences)"
24+
) {{ $t('home.timelineBlockDetail.viewPhotos', { count: screenshotsFor(occurrences).length }) }}
25+
template(v-if="occurrencesByTitle.length")
26+
div.block-detail-title-group(v-for="group in occurrencesByTitle" :key="group.title")
27+
div.block-detail-title-group-head
28+
span.block-detail-title-group-name {{ group.title }}
29+
// Filtrato SOLO agli orari di questo titolo — richiesta
30+
// esplicita: non tutte le foto del blocco, solo quelle cadute
31+
// dentro una delle fasce orarie elencate qui sotto.
32+
span.block-detail-photos-btn(
33+
v-if="screenshotsFor(group.occurrences).length"
34+
@click="openGallery(group.title, group.occurrences)"
35+
) {{ $t('home.timelineBlockDetail.viewPhotos', { count: screenshotsFor(group.occurrences).length }) }}
36+
table.block-detail-table.block-detail-title-group-table
37+
tbody
38+
tr(v-for="(occ, i) in group.occurrences" :key="i")
39+
td {{ occ.start.format('HH:mm') }} – {{ occ.end.format('HH:mm') }}
40+
td {{ formatDuration(occ.end.diff(occ.start, 'seconds')) }}
41+
table.block-detail-table(v-else-if="occurrences.length > 1")
1942
tbody
2043
tr(v-for="(occ, i) in occurrences" :key="i")
2144
td {{ occ.start.format('HH:mm') }} – {{ occ.end.format('HH:mm') }}
2245
td {{ formatDuration(occ.end.diff(occ.start, 'seconds')) }}
2346

24-
div.block-detail-subhead(v-if="titleBreakdown.length") {{ $t('home.timelineBlockDetail.topWindowTitles') }}
25-
table.block-detail-table(v-if="titleBreakdown.length")
26-
tbody
27-
tr(v-for="(t, i) in titleBreakdown" :key="i")
28-
td {{ t.title }}
29-
td {{ formatDuration(t.duration) }}
30-
3147
div.edit-modal-actions
32-
// Explicit request: no more thumbnails dumped inline here (they
33-
// read as disconnected from *when* in the block they happened) —
34-
// a single button instead, opening a dedicated gallery that groups
35-
// them by which window title was active, so "show me the
36-
// screenshots of activity X" doesn't require mentally matching
37-
// timestamps by hand.
38-
div.pill-btn-ghost(v-if="screenshots.length" @click="showGallery = true") {{ $t('home.timelineBlockDetail.viewScreenshots', { count: screenshots.length }) }}
3948
div.pill-btn-ghost(@click="$emit('close')") {{ $t('home.timelineBlockDetail.close') }}
4049

4150
screenshot-gallery-modal(
4251
v-if="showGallery"
43-
:screenshots="screenshots"
44-
:title-segments="titleSegments"
45-
:display-name="displayName"
52+
:screenshots="galleryScreenshots"
53+
:title-segments="galleryTitleSegments"
54+
:display-name="galleryTitle"
4655
@close="showGallery = false"
4756
)
4857
</template>
@@ -52,7 +61,7 @@ div
5261
@import '../style/modals.css';
5362
5463
.block-detail-modal {
55-
width: 400px;
64+
width: 480px;
5665
max-height: 75vh;
5766
overflow-y: auto;
5867
}
@@ -79,11 +88,42 @@ div
7988
font-weight: var(--font-weight-semibold);
8089
}
8190
91+
.block-detail-subhead-row {
92+
display: flex;
93+
align-items: baseline;
94+
justify-content: space-between;
95+
gap: 10px;
96+
margin: 16px 0 8px;
97+
}
98+
8299
.block-detail-subhead {
83100
font-size: var(--font-size-xs);
84101
font-weight: var(--font-weight-bold);
85102
color: var(--color-text-dim);
86-
margin: 16px 0 8px;
103+
}
104+
105+
// Vero pulsante a pillola a destra del titolo (non più un link a
106+
// testo semplice, giudicato brutto) — apre la galleria filtrata solo
107+
// agli orari di QUEL titolo (vedi screenshotsFor()/openGallery()
108+
// sotto), non a tutto il blocco.
109+
.block-detail-photos-btn {
110+
display: inline-flex;
111+
align-items: center;
112+
flex-shrink: 0;
113+
font-size: var(--font-size-xs);
114+
font-weight: var(--font-weight-semibold);
115+
color: var(--color-text-dim);
116+
background-color: var(--color-surface2);
117+
border: 1px solid var(--color-border);
118+
border-radius: var(--radius-pill);
119+
padding: 3px 10px;
120+
cursor: pointer;
121+
white-space: nowrap;
122+
}
123+
124+
.block-detail-photos-btn:hover {
125+
color: var(--color-text);
126+
border-color: var(--color-accent1);
87127
}
88128
89129
.block-detail-table {
@@ -106,6 +146,60 @@ div
106146
.block-detail-table tr:last-child td {
107147
border-bottom: none;
108148
}
149+
150+
.block-detail-title-group + .block-detail-title-group {
151+
margin-top: 12px;
152+
}
153+
154+
.block-detail-title-group-head {
155+
display: flex;
156+
align-items: baseline;
157+
justify-content: space-between;
158+
gap: 10px;
159+
margin-bottom: 4px;
160+
}
161+
162+
.block-detail-title-group-name {
163+
font-size: var(--font-size-sm);
164+
font-weight: var(--font-weight-semibold);
165+
color: var(--color-text);
166+
}
167+
168+
// Righe orario leggermente rientrate, con una barra verticale a
169+
// sinistra allineata col titolo sopra — richiesta estetica esplicita.
170+
// Il padding va sulla prima cella, non sulla <table>: il padding di un
171+
// elemento <table> non crea spazio visibile prima del contenuto delle
172+
// celle (a differenza di un div), motivo per cui riga e barra
173+
// risultavano incollate nel primo tentativo.
174+
.block-detail-title-group-table {
175+
margin-left: 0;
176+
border-left: 2px solid var(--color-border);
177+
}
178+
179+
.block-detail-title-group-table td:first-child {
180+
padding-left: 10px;
181+
}
182+
183+
// La riga di separazione tra un orario e l'altro (border-bottom, da
184+
// .block-detail-table td) partiva dal bordo sinistro della cella —
185+
// stesso punto della barra verticale qui sopra, formando una "T" ad
186+
// ogni riga. Sostituita con una linea disegnata via gradiente, che
187+
// parte più a destra (stesso rientro del testo) invece di toccare la
188+
// barra.
189+
.block-detail-title-group-table td {
190+
border-bottom: none;
191+
}
192+
193+
.block-detail-title-group-table tr {
194+
background-image: linear-gradient(to right, transparent 10px, var(--color-border) 10px);
195+
background-position: bottom;
196+
background-repeat: no-repeat;
197+
background-size: 100% 1px;
198+
}
199+
200+
.block-detail-title-group-table tr:last-child {
201+
background-image: none;
202+
}
109203
</style>
110204

111205
<script lang="ts">
@@ -120,6 +214,12 @@ import { formatDuration } from '~/util/projectTime';
120214
import { displayNameForApp } from '~/util/appNames';
121215
import { getHomeClient } from '~/util/awclient';
122216
217+
interface Screenshot {
218+
filename: string;
219+
time: string;
220+
timestamp: moment.Moment;
221+
}
222+
123223
export default {
124224
name: 'TimelineBlockDetailModal',
125225
components: {
@@ -129,34 +229,40 @@ export default {
129229
block: { type: Object, required: true },
130230
laneName: { type: String, required: true },
131231
occurrences: { type: Array, default: () => [] },
132-
// Per-title time breakdown within this block's own range — computed
133-
// by the parent (HomeTimelineSection.vue's selectedBlockTitleBreakdown(),
134-
// which has the raw window events this needs), empty for anything
135-
// that isn't a whole Generale-lane app block.
136-
titleBreakdown: { type: Array, default: () => [] },
137-
// Same source data as titleBreakdown but with real start/end moments
138-
// instead of just totals — handed straight through to the gallery
139-
// (see selectedBlockTitleSegments()), which needs actual timestamps
140-
// to match each screenshot to the title active when it was taken.
141-
titleSegments: { type: Array, default: () => [] },
232+
// Stessa lista di occurrences, ma raggruppata per titolo — quando
233+
// presente sostituisce del tutto la tabella piatta sopra (solo per
234+
// le corsie i cui eventi grezzi hanno un campo title distinto, vedi
235+
// selectedOccurrencesByTitle() in HomeTimelineSection.vue).
236+
occurrencesByTitle: { type: Array, default: () => [] },
142237
},
143238
data() {
144239
return {
145-
// Desktop screenshots (aw-watcher-screenshot) captured during this
146-
// block's own time range — not scoped to `laneName`, since a
147-
// screenshot is evidence of what was on screen regardless of
148-
// which lane the clicked block belongs to.
149-
screenshots: [] as { filename: string; time: string; timestamp: moment.Moment }[],
150-
// Explicit request: no more inline thumbnails/lightbox here — a
151-
// single button opens the dedicated gallery instead (see
152-
// ScreenshotGalleryModal.vue).
240+
// Screenshot di TUTTA la giornata coperta da occurrences/
241+
// occurrencesByTitle (non solo l'intervallo del blocco cliccato,
242+
// vedi screenshotRange) — caricati una sola volta, poi filtrati
243+
// per titolo al volo in screenshotsFor() invece di rifare una
244+
// richiesta di rete ad ogni click su un pulsante diverso.
245+
screenshots: [] as Screenshot[],
153246
showGallery: false,
247+
galleryTitle: '',
248+
galleryScreenshots: [] as Screenshot[],
249+
galleryTitleSegments: [] as { title: string; start: moment.Moment; end: moment.Moment }[],
154250
};
155251
},
156252
computed: {
157253
displayName(): string {
158254
return displayNameForApp(this.block.key);
159255
},
256+
// L'intervallo da interrogare per gli screenshot: non solo
257+
// block.start/end (una singola occorrenza), ma l'estremo min/max fra
258+
// TUTTE le occorrenze del giorno — necessario perché ogni pulsante
259+
// "N foto" può aprire la galleria per un titolo comparso ore prima o
260+
// dopo l'istanza di blocco effettivamente cliccata.
261+
screenshotRange(): { start: moment.Moment; end: moment.Moment } {
262+
const starts = [this.block.start, ...this.occurrences.map((o: any) => o.start)];
263+
const ends = [this.block.end, ...this.occurrences.map((o: any) => o.end)];
264+
return { start: moment.min(starts), end: moment.max(ends) };
265+
},
160266
},
161267
watch: {
162268
block: {
@@ -177,8 +283,8 @@ export default {
177283
try {
178284
// Detached client — see getHomeClient() for why.
179285
events = await getHomeClient().getEvents('aw-watcher-screenshot', {
180-
start: this.block.start.toDate(),
181-
end: this.block.end.toDate(),
286+
start: this.screenshotRange.start.toDate(),
287+
end: this.screenshotRange.end.toDate(),
182288
limit: -1,
183289
});
184290
} catch {
@@ -196,6 +302,20 @@ export default {
196302
// top-to-bottom as "earliest in this block" first.
197303
.reverse();
198304
},
305+
// Solo gli screenshot caduti dentro una qualunque delle fasce
306+
// orarie passate — usato sia per decidere se mostrare il pulsante
307+
// (nessuna foto = nessun pulsante) sia per popolare la galleria.
308+
screenshotsFor(occs: { start: moment.Moment; end: moment.Moment }[]): Screenshot[] {
309+
return this.screenshots.filter((s: Screenshot) =>
310+
occs.some(o => !s.timestamp.isBefore(o.start) && s.timestamp.isBefore(o.end))
311+
);
312+
},
313+
openGallery(title: string, occs: { start: moment.Moment; end: moment.Moment }[]) {
314+
this.galleryTitle = title;
315+
this.galleryScreenshots = this.screenshotsFor(occs);
316+
this.galleryTitleSegments = occs.map(o => ({ title, start: o.start, end: o.end }));
317+
this.showGallery = true;
318+
},
199319
},
200320
};
201321
</script>

src/i18n/locales/en.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,8 +665,7 @@ Clear all rules from the editor, leaving it as \`[]\` (or empty), and save.`,
665665
time: 'Time',
666666
duration: 'Duration',
667667
otherOccurrences: 'Other occurrences today',
668-
topWindowTitles: 'Top Window Titles',
669-
viewScreenshots: 'View {count} screenshots',
668+
viewPhotos: '{count} photos',
670669
close: 'Close',
671670
},
672671
calendarPicker: {

src/i18n/locales/it.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,7 @@ Cancella tutte le regole dall'editor, lasciandolo con \`[]\` (o vuoto), e salva.
670670
time: 'Orario',
671671
duration: 'Durata',
672672
otherOccurrences: 'Altre occorrenze oggi',
673-
topWindowTitles: 'Top Window Titles',
674-
viewScreenshots: 'Vedi {count} screenshot',
673+
viewPhotos: '{count} foto',
675674
close: 'Chiudi',
676675
},
677676
calendarPicker: {

0 commit comments

Comments
 (0)