opencode-session 0.1.0
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/LICENSE +21 -0
- package/package.json +46 -0
- package/src/config.ts +29 -0
- package/src/data/loader.ts +495 -0
- package/src/data/manager.ts +312 -0
- package/src/index.ts +56 -0
- package/src/types.ts +173 -0
- package/src/ui/app.ts +245 -0
- package/src/ui/components/confirm-dialog.ts +117 -0
- package/src/ui/components/detail-viewer.ts +307 -0
- package/src/ui/components/header.ts +62 -0
- package/src/ui/components/index.ts +8 -0
- package/src/ui/components/list-container.ts +97 -0
- package/src/ui/components/log-viewer.ts +217 -0
- package/src/ui/components/status-bar.ts +99 -0
- package/src/ui/components/tabbar.ts +79 -0
- package/src/ui/controllers/confirm-controller.ts +57 -0
- package/src/ui/controllers/index.ts +92 -0
- package/src/ui/controllers/log-controller.ts +173 -0
- package/src/ui/controllers/log-viewer-controller.ts +52 -0
- package/src/ui/controllers/main-controller.ts +142 -0
- package/src/ui/controllers/message-viewer-controller.ts +176 -0
- package/src/ui/controllers/orphan-controller.ts +125 -0
- package/src/ui/controllers/project-viewer-controller.ts +113 -0
- package/src/ui/controllers/session-viewer-controller.ts +181 -0
- package/src/ui/keybindings.ts +158 -0
- package/src/ui/state.ts +299 -0
- package/src/ui/views/base-view.ts +92 -0
- package/src/ui/views/index.ts +45 -0
- package/src/ui/views/log-list.ts +81 -0
- package/src/ui/views/main-view.ts +242 -0
- package/src/ui/views/orphan-list.ts +241 -0
- package/src/utils.ts +118 -0
package/src/ui/state.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { EventEmitter } from "events"
|
|
2
|
+
import type { ViewType, SessionInfo, ProjectInfo, LogFile } from "../types"
|
|
3
|
+
import type { LoadedData } from "../data/loader"
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// State Interface
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
export interface AppState {
|
|
10
|
+
view: ViewType
|
|
11
|
+
data: LoadedData
|
|
12
|
+
selectedIndices: Set<number>
|
|
13
|
+
currentIndex: number
|
|
14
|
+
expandedProjects: Set<string>
|
|
15
|
+
showConfirm: boolean
|
|
16
|
+
confirmMessage: string
|
|
17
|
+
confirmAction: (() => Promise<void>) | null
|
|
18
|
+
statusMessage: string
|
|
19
|
+
isLoading: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// State Events
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
export enum StateEvent {
|
|
27
|
+
VIEW_CHANGED = "view-changed",
|
|
28
|
+
DATA_LOADED = "data-loaded",
|
|
29
|
+
SELECTION_CHANGED = "selection-changed",
|
|
30
|
+
LOADING_CHANGED = "loading-changed",
|
|
31
|
+
STATUS_CHANGED = "status-changed",
|
|
32
|
+
CONFIRM_CHANGED = "confirm-changed",
|
|
33
|
+
EXPAND_CHANGED = "expand-changed",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// View Navigation
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
const VIEW_ORDER: ViewType[] = ["main", "orphans", "logs"]
|
|
41
|
+
|
|
42
|
+
export function getNextView(current: ViewType): ViewType {
|
|
43
|
+
const index = VIEW_ORDER.indexOf(current)
|
|
44
|
+
const nextIndex = (index + 1) % VIEW_ORDER.length
|
|
45
|
+
return VIEW_ORDER[nextIndex]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getPrevView(current: ViewType): ViewType {
|
|
49
|
+
const index = VIEW_ORDER.indexOf(current)
|
|
50
|
+
const prevIndex = (index - 1 + VIEW_ORDER.length) % VIEW_ORDER.length
|
|
51
|
+
return VIEW_ORDER[prevIndex]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// State Manager
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
function createInitialState(): AppState {
|
|
59
|
+
return {
|
|
60
|
+
view: "main",
|
|
61
|
+
data: { sessions: [], projects: [], logs: [] },
|
|
62
|
+
selectedIndices: new Set(),
|
|
63
|
+
currentIndex: 0,
|
|
64
|
+
expandedProjects: new Set(),
|
|
65
|
+
showConfirm: false,
|
|
66
|
+
confirmMessage: "",
|
|
67
|
+
confirmAction: null,
|
|
68
|
+
statusMessage: "",
|
|
69
|
+
isLoading: true,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class StateManager extends EventEmitter {
|
|
74
|
+
private state: AppState
|
|
75
|
+
|
|
76
|
+
constructor() {
|
|
77
|
+
super()
|
|
78
|
+
this.state = createInitialState()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- Getters ----
|
|
82
|
+
|
|
83
|
+
get view(): ViewType {
|
|
84
|
+
return this.state.view
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get data(): LoadedData {
|
|
88
|
+
return this.state.data
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get selectedIndices(): Set<number> {
|
|
92
|
+
return this.state.selectedIndices
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get currentIndex(): number {
|
|
96
|
+
return this.state.currentIndex
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get expandedProjects(): Set<string> {
|
|
100
|
+
return this.state.expandedProjects
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get showConfirm(): boolean {
|
|
104
|
+
return this.state.showConfirm
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get confirmMessage(): string {
|
|
108
|
+
return this.state.confirmMessage
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get confirmAction(): (() => Promise<void>) | null {
|
|
112
|
+
return this.state.confirmAction
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get statusMessage(): string {
|
|
116
|
+
return this.state.statusMessage
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get isLoading(): boolean {
|
|
120
|
+
return this.state.isLoading
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- Derived Getters ----
|
|
124
|
+
|
|
125
|
+
get sessions(): SessionInfo[] {
|
|
126
|
+
return this.state.data.sessions
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get orphanSessions(): SessionInfo[] {
|
|
130
|
+
return this.state.data.sessions.filter((s) => s.isOrphan)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get orphanProjects(): ProjectInfo[] {
|
|
134
|
+
return this.state.data.projects.filter((p) => p.isOrphan)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get projects(): ProjectInfo[] {
|
|
138
|
+
return this.state.data.projects
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get logs(): LogFile[] {
|
|
142
|
+
return this.state.data.logs
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get selectedCount(): number {
|
|
146
|
+
return this.state.selectedIndices.size
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- Setters with Events ----
|
|
150
|
+
|
|
151
|
+
setView(view: ViewType): void {
|
|
152
|
+
if (this.state.view !== view) {
|
|
153
|
+
this.state.view = view
|
|
154
|
+
this.state.selectedIndices = new Set()
|
|
155
|
+
this.state.currentIndex = 0
|
|
156
|
+
// Don't clear expanded projects when switching away - preserve state
|
|
157
|
+
this.emit(StateEvent.VIEW_CHANGED, view)
|
|
158
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
nextView(): void {
|
|
163
|
+
this.setView(getNextView(this.state.view))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
prevView(): void {
|
|
167
|
+
this.setView(getPrevView(this.state.view))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setData(data: LoadedData): void {
|
|
171
|
+
this.state.data = data
|
|
172
|
+
this.state.selectedIndices = new Set()
|
|
173
|
+
this.state.currentIndex = 0
|
|
174
|
+
// Clear expanded projects when data changes (projects may have been deleted)
|
|
175
|
+
this.state.expandedProjects = new Set()
|
|
176
|
+
this.emit(StateEvent.DATA_LOADED, data)
|
|
177
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setLoading(isLoading: boolean): void {
|
|
181
|
+
if (this.state.isLoading !== isLoading) {
|
|
182
|
+
this.state.isLoading = isLoading
|
|
183
|
+
this.emit(StateEvent.LOADING_CHANGED, isLoading)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
setStatus(message: string): void {
|
|
188
|
+
this.state.statusMessage = message
|
|
189
|
+
this.emit(StateEvent.STATUS_CHANGED, message)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setCurrentIndex(index: number): void {
|
|
193
|
+
this.state.currentIndex = index
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---- Selection Management ----
|
|
197
|
+
|
|
198
|
+
toggleSelection(index: number): void {
|
|
199
|
+
if (this.state.selectedIndices.has(index)) {
|
|
200
|
+
this.state.selectedIndices.delete(index)
|
|
201
|
+
} else {
|
|
202
|
+
this.state.selectedIndices.add(index)
|
|
203
|
+
}
|
|
204
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
selectAll(itemCount: number): void {
|
|
208
|
+
if (this.state.selectedIndices.size === itemCount) {
|
|
209
|
+
this.state.selectedIndices = new Set()
|
|
210
|
+
} else {
|
|
211
|
+
this.state.selectedIndices = new Set(
|
|
212
|
+
Array.from({ length: itemCount }, (_, i) => i)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
clearSelection(): void {
|
|
219
|
+
this.state.selectedIndices = new Set()
|
|
220
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getSelectedOrCurrentIndices(currentIndex: number): number[] {
|
|
224
|
+
if (this.state.selectedIndices.size > 0) {
|
|
225
|
+
return Array.from(this.state.selectedIndices)
|
|
226
|
+
}
|
|
227
|
+
if (currentIndex >= 0) {
|
|
228
|
+
return [currentIndex]
|
|
229
|
+
}
|
|
230
|
+
return []
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---- Project Expansion (for main view) ----
|
|
234
|
+
|
|
235
|
+
isProjectExpanded(projectId: string): boolean {
|
|
236
|
+
return this.state.expandedProjects.has(projectId)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
expandProject(projectId: string): void {
|
|
240
|
+
if (!this.state.expandedProjects.has(projectId)) {
|
|
241
|
+
this.state.expandedProjects.add(projectId)
|
|
242
|
+
this.emit(StateEvent.EXPAND_CHANGED, projectId)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
collapseProject(projectId: string): void {
|
|
247
|
+
if (this.state.expandedProjects.has(projectId)) {
|
|
248
|
+
this.state.expandedProjects.delete(projectId)
|
|
249
|
+
this.emit(StateEvent.EXPAND_CHANGED, projectId)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
toggleProjectExpand(projectId: string): void {
|
|
254
|
+
if (this.isProjectExpanded(projectId)) {
|
|
255
|
+
this.collapseProject(projectId)
|
|
256
|
+
} else {
|
|
257
|
+
this.expandProject(projectId)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
collapseAllProjects(): void {
|
|
262
|
+
if (this.state.expandedProjects.size > 0) {
|
|
263
|
+
this.state.expandedProjects = new Set()
|
|
264
|
+
this.state.selectedIndices = new Set()
|
|
265
|
+
this.emit(StateEvent.EXPAND_CHANGED, null)
|
|
266
|
+
this.emit(StateEvent.SELECTION_CHANGED)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---- Confirm Dialog ----
|
|
271
|
+
|
|
272
|
+
showConfirmDialog(message: string, action: () => Promise<void>): void {
|
|
273
|
+
this.state.showConfirm = true
|
|
274
|
+
this.state.confirmMessage = message
|
|
275
|
+
this.state.confirmAction = action
|
|
276
|
+
this.emit(StateEvent.CONFIRM_CHANGED, true)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
hideConfirmDialog(): void {
|
|
280
|
+
this.state.showConfirm = false
|
|
281
|
+
this.state.confirmMessage = ""
|
|
282
|
+
this.state.confirmAction = null
|
|
283
|
+
this.emit(StateEvent.CONFIRM_CHANGED, false)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async executeConfirmAction(): Promise<void> {
|
|
287
|
+
const action = this.state.confirmAction
|
|
288
|
+
this.hideConfirmDialog()
|
|
289
|
+
if (action) {
|
|
290
|
+
await action()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---- Reset ----
|
|
295
|
+
|
|
296
|
+
reset(): void {
|
|
297
|
+
this.state = createInitialState()
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { SelectOption } from "@opentui/core"
|
|
2
|
+
import type { ViewType } from "../../types"
|
|
3
|
+
import type { StateManager } from "../state"
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Delete Result Type
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
export interface DeleteResult {
|
|
10
|
+
deletedCount: number
|
|
11
|
+
freedBytes: number
|
|
12
|
+
errors: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// View Configuration
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface ViewConfig {
|
|
20
|
+
id: ViewType
|
|
21
|
+
title: string
|
|
22
|
+
supportsSelectAll: boolean
|
|
23
|
+
supportsDeleteAll: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Base View Abstract Class
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
// ViewItem is a union of all possible item types across views
|
|
31
|
+
// Each view can return any type that can be used in the UI
|
|
32
|
+
export type ViewItem = unknown
|
|
33
|
+
|
|
34
|
+
export abstract class BaseView {
|
|
35
|
+
protected state: StateManager
|
|
36
|
+
|
|
37
|
+
constructor(state: StateManager) {
|
|
38
|
+
this.state = state
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* View configuration
|
|
43
|
+
*/
|
|
44
|
+
abstract readonly config: ViewConfig
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the items for this view
|
|
48
|
+
*/
|
|
49
|
+
abstract getItems(): ViewItem[]
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convert items to SelectOption format for display
|
|
53
|
+
*/
|
|
54
|
+
abstract getOptions(selectedIndices: Set<number>): SelectOption[]
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the confirmation message for deletion
|
|
58
|
+
*/
|
|
59
|
+
abstract getDeleteMessage(count: number, totalSize: number): string
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Execute deletion of items at the given indices
|
|
63
|
+
*/
|
|
64
|
+
abstract executeDelete(indices: number[]): Promise<DeleteResult>
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the title with counts
|
|
68
|
+
*/
|
|
69
|
+
getTitle(selectedCount: number): string {
|
|
70
|
+
const items = this.getItems()
|
|
71
|
+
const { title } = this.config
|
|
72
|
+
let result = `${title} (${items.length} total`
|
|
73
|
+
if (selectedCount > 0) {
|
|
74
|
+
result += `, ${selectedCount} selected`
|
|
75
|
+
}
|
|
76
|
+
result += ")"
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Calculate total size of selected items
|
|
82
|
+
* Each view should override this with proper type handling
|
|
83
|
+
*/
|
|
84
|
+
abstract getTotalSize(indices: number[]): number
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the number of items in this view
|
|
88
|
+
*/
|
|
89
|
+
getItemCount(): number {
|
|
90
|
+
return this.getItems().length
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ViewType } from "../../types"
|
|
2
|
+
import type { StateManager } from "../state"
|
|
3
|
+
import { BaseView } from "./base-view"
|
|
4
|
+
import { MainView } from "./main-view"
|
|
5
|
+
import { OrphanListView } from "./orphan-list"
|
|
6
|
+
import { LogListView } from "./log-list"
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Exports
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export { BaseView, type ViewConfig, type DeleteResult, type ViewItem } from "./base-view"
|
|
13
|
+
export { MainView, type ListItem, type ProjectItem, type SessionItem } from "./main-view"
|
|
14
|
+
export { OrphanListView } from "./orphan-list"
|
|
15
|
+
export { LogListView } from "./log-list"
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// View Factory
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export type ViewMap = {
|
|
22
|
+
main: MainView
|
|
23
|
+
orphans: OrphanListView
|
|
24
|
+
logs: LogListView
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createViews(state: StateManager): ViewMap {
|
|
28
|
+
return {
|
|
29
|
+
main: new MainView(state),
|
|
30
|
+
orphans: new OrphanListView(state),
|
|
31
|
+
logs: new LogListView(state),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getView(views: ViewMap, viewType: ViewType): BaseView {
|
|
36
|
+
return views[viewType]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getMainView(views: ViewMap): MainView {
|
|
40
|
+
return views.main
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getOrphanView(views: ViewMap): OrphanListView {
|
|
44
|
+
return views.orphans
|
|
45
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { SelectOption } from "@opentui/core"
|
|
2
|
+
import type { LogFile } from "../../types"
|
|
3
|
+
import type { StateManager } from "../state"
|
|
4
|
+
import { formatBytes, formatDate } from "../../utils"
|
|
5
|
+
import { deleteLogs } from "../../data/manager"
|
|
6
|
+
import { BaseView, type ViewConfig, type DeleteResult } from "./base-view"
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Log Files List View
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export class LogListView extends BaseView {
|
|
13
|
+
readonly config: ViewConfig = {
|
|
14
|
+
id: "logs",
|
|
15
|
+
title: "Log Files",
|
|
16
|
+
supportsSelectAll: true,
|
|
17
|
+
supportsDeleteAll: true,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(state: StateManager) {
|
|
21
|
+
super(state)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getItems(): LogFile[] {
|
|
25
|
+
return this.state.logs
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getOptions(selectedIndices: Set<number>): SelectOption[] {
|
|
29
|
+
const logs = this.getItems()
|
|
30
|
+
return logs.map((log, index) => {
|
|
31
|
+
const selected = selectedIndices.has(index) ? "[x]" : "[ ]"
|
|
32
|
+
const date = formatDate(log.date)
|
|
33
|
+
const size = formatBytes(log.sizeBytes)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
name: `${selected} ${log.filename}`,
|
|
37
|
+
description: `${date} | ${size}`,
|
|
38
|
+
value: log.path,
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getDeleteMessage(count: number, totalSize: number): string {
|
|
44
|
+
return `Delete ${count} log file(s)? (${formatBytes(totalSize)}) [y/N]`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getDeleteAllMessage(totalSize: number): string {
|
|
48
|
+
const count = this.getItemCount()
|
|
49
|
+
return `Delete ALL ${count} log file(s)? (${formatBytes(totalSize)}) [y/N]`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getTotalSize(indices: number[]): number {
|
|
53
|
+
const logs = this.getItems()
|
|
54
|
+
return indices.reduce((sum, index) => {
|
|
55
|
+
const log = logs[index]
|
|
56
|
+
return log ? sum + log.sizeBytes : sum
|
|
57
|
+
}, 0)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async executeDelete(indices: number[]): Promise<DeleteResult> {
|
|
61
|
+
const logs = this.getItems()
|
|
62
|
+
const toDelete = indices.map((i) => logs[i]).filter(Boolean)
|
|
63
|
+
|
|
64
|
+
let deletedCount = 0
|
|
65
|
+
let freedBytes = 0
|
|
66
|
+
const errors: string[] = []
|
|
67
|
+
|
|
68
|
+
const results = await deleteLogs(toDelete)
|
|
69
|
+
|
|
70
|
+
for (const result of results) {
|
|
71
|
+
if (result.success) {
|
|
72
|
+
deletedCount++
|
|
73
|
+
freedBytes += result.bytesFreed
|
|
74
|
+
} else if (result.error) {
|
|
75
|
+
errors.push(`${result.filename}: ${result.error}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { deletedCount, freedBytes, errors }
|
|
80
|
+
}
|
|
81
|
+
}
|