3 Commits

Author SHA1 Message Date
messypy
51c00f2a75 Fallback history export to VRChat logs
All checks were successful
build-windows-exe / build (push) Successful in 1m40s
2026-07-28 00:37:56 +09:00
messypy
6d318af329 Give settings export controls more space
All checks were successful
build-windows-exe / build (push) Successful in 1m46s
2026-07-28 00:31:42 +09:00
messypy
a017170c71 Align settings controls
All checks were successful
build-windows-exe / build (push) Successful in 2m9s
2026-07-28 00:20:57 +09:00
2 changed files with 82 additions and 24 deletions

View File

@@ -342,6 +342,45 @@ func historyEventsFromSnapshot() []historyEventRecord {
return out return out
} }
func historyEventsWithFallback() []historyEventRecord {
events := historyEventsFromSnapshot()
if len(events) > 0 {
return events
}
users, ok := currentUsersFromVRChatLog()
if !ok || len(users) == 0 {
return nil
}
out := make([]historyEventRecord, 0, len(users))
for _, user := range users {
name := strings.TrimSpace(user.Name)
if name == "" {
continue
}
if !user.LastJoin.IsZero() {
out = append(out, historyEventRecord{
At: user.LastJoin,
Kind: "join",
Name: name,
})
}
if !user.Present && !user.LastLeave.IsZero() {
out = append(out, historyEventRecord{
At: user.LastLeave,
Kind: "leave",
Name: name,
})
}
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].At.Equal(out[j].At) {
return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name)
}
return out[i].At.Before(out[j].At)
})
return out
}
func decodeHistoryEventsSnapshot(b []byte) []historyEventRecord { func decodeHistoryEventsSnapshot(b []byte) []historyEventRecord {
type snapshot struct { type snapshot struct {
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
@@ -408,7 +447,7 @@ func historyVisitsForDay(app *guiApp, day time.Time) []historyDetailRow {
if !historyInQueryRange(day, q) { if !historyInQueryRange(day, q) {
return nil return nil
} }
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
return historyVisitsForDayFromData(app, day, visits, events, q) return historyVisitsForDayFromData(app, day, visits, events, q)
} }
@@ -502,7 +541,7 @@ func historyCalendarCellsForMonth(app *guiApp) []historyCalendarCell {
monthStart = time.Now() monthStart = time.Now()
} }
monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location()) monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location())
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
return historyCalendarCellsForMonthFromData(app, monthStart, visits, events, q) return historyCalendarCellsForMonthFromData(app, monthStart, visits, events, q)
} }
@@ -591,7 +630,7 @@ func exportHistoryDay(app *guiApp, day time.Time, selected map[string]bool) (str
} }
func exportHistoryDayFromRequest(req historyExportRequest) (string, error) { func exportHistoryDayFromRequest(req historyExportRequest) (string, error) {
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
rows := append([]historyDetailRow(nil), req.Rows...) rows := append([]historyDetailRow(nil), req.Rows...)
if len(rows) == 0 { if len(rows) == 0 {
@@ -795,7 +834,7 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
if b == nil { if b == nil {
return return
} }
guests := readGuestSnapshot() guests := guestSnapshotForExport()
dayStart, dayEnd := historyDayBounds(day) dayStart, dayEnd := historyDayBounds(day)
type guestLine struct { type guestLine struct {
name string name string
@@ -848,6 +887,24 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
} }
} }
func guestSnapshotForExport() []userState {
guests := readGuestSnapshot()
if len(guests) > 0 {
out := make([]userState, 0, len(guests))
for _, guest := range guests {
out = append(out, userState{
Name: guest.Name,
Present: guest.Present,
LastJoin: guest.LastJoin,
LastLeave: guest.LastLeave,
})
}
return out
}
users, _ := currentUsersFromJoinLeave()
return users
}
func cloneHistorySelection(selected map[string]bool) map[string]bool { func cloneHistorySelection(selected map[string]bool) map[string]bool {
if len(selected) == 0 { if len(selected) == 0 {
return nil return nil
@@ -887,7 +944,7 @@ func refreshHistoryCache(app *guiApp, force bool) bool {
if !force && app.historyCacheLoaded && !app.historyReloadPending { if !force && app.historyCacheLoaded && !app.historyReloadPending {
return false return false
} }
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
q := historyQueryFromApp(app) q := historyQueryFromApp(app)
app.historyRows = historyRows() app.historyRows = historyRows()

View File

@@ -102,7 +102,7 @@ const (
leftPaneHeight = 560 leftPaneHeight = 560
rightPaneWidth = windowWidth - leftPaneWidth rightPaneWidth = windowWidth - leftPaneWidth
settingsPaneWidth = rightPaneWidth settingsPaneWidth = rightPaneWidth
settingsPaneHeight = leftPaneHeight settingsPaneHeight = 700
clrMainBg = 0x00311c0b clrMainBg = 0x00311c0b
clrHeaderBg = 0x004d2c08 clrHeaderBg = 0x004d2c08
clrNavBg = 0x0024150b clrNavBg = 0x0024150b
@@ -1633,10 +1633,10 @@ func resizeGUIForSettings(app *guiApp) {
return return
} }
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + leftPaneHeight + bottomBarHeight windowHeight := contentTop + settingsPaneHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags) setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags)
if app.settingsPaneHwnd != 0 { if app.settingsPaneHwnd != 0 {
setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), windowWidth, uintptr(leftPaneHeight), resizeFlags) setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), windowWidth, uintptr(settingsPaneHeight), resizeFlags)
} }
} }
@@ -1867,16 +1867,17 @@ func paintSettingsPane(hwnd uintptr, app *guiApp) uintptr {
drawPaneText(hdc, 24, 68, "表示", app.hFont, clrMutedText) drawPaneText(hdc, 24, 68, "表示", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost) drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost)
drawSettingsWideButton(hdc, settingsAutoUnmuteRect(), "Leave unmute", onOffBool(app.autoUnmuteOnLeave), app.hFont, app.autoUnmuteOnLeave) drawSettingsWideButton(hdc, settingsAutoUnmuteRect(), "Leave unmute", onOffBool(app.autoUnmuteOnLeave), app.hFont, app.autoUnmuteOnLeave)
drawPaneText(hdc, 24, 190, "文字サイズ", app.hFont, clrMutedText) drawPaneText(hdc, 24, 190, "更新間隔", app.hFont, clrMutedText)
drawSettingsRefreshControl(hdc, app) drawSettingsRefreshControl(hdc, app)
drawPaneText(hdc, 24, 314, "文字サイズ", app.hFont, clrMutedText)
drawSettingsFontControl(hdc, app) drawSettingsFontControl(hdc, app)
drawPaneText(hdc, 24, 374, "履歴 Export", app.hFont, clrMutedText) drawPaneText(hdc, 24, 400, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 396, "出力先フォルダ", app.hFont, clrMutedText) drawPaneText(hdc, 24, 430, "出力先フォルダ", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime) drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime)
drawSettingsWideButton(hdc, settingsHistoryExportWorldRect(), "World", onOffBool(app.historyExportIncludeWorld), app.hFont, app.historyExportIncludeWorld) drawSettingsWideButton(hdc, settingsHistoryExportWorldRect(), "World", onOffBool(app.historyExportIncludeWorld), app.hFont, app.historyExportIncludeWorld)
drawSettingsWideButton(hdc, settingsHistoryExportJoinLeaveRect(), "Join/Leave", onOffBool(app.historyExportIncludeJoinLeave), app.hFont, app.historyExportIncludeJoinLeave) drawSettingsWideButton(hdc, settingsHistoryExportJoinLeaveRect(), "Join/Leave", onOffBool(app.historyExportIncludeJoinLeave), app.hFont, app.historyExportIncludeJoinLeave)
drawSettingsWideButton(hdc, settingsHistoryExportCustomToggleRect(), "Custom", onOffBool(app.historyExportCustomEnabled), app.hFont, app.historyExportCustomEnabled) drawSettingsWideButton(hdc, settingsHistoryExportCustomToggleRect(), "Custom", onOffBool(app.historyExportCustomEnabled), app.hFont, app.historyExportCustomEnabled)
drawPaneText(hdc, 24, 516, "Custom 条件", app.hFont, clrMutedText) drawPaneText(hdc, 24, 618, "Custom 条件", app.hFont, clrMutedText)
return 0 return 0
} }
@@ -1917,35 +1918,35 @@ func settingsFontUpRect() winRect {
} }
func settingsHistoryExportDirRect() winRect { func settingsHistoryExportDirRect() winRect {
return winRect{Left: 116, Top: 420, Right: 636, Bottom: 444} return winRect{Left: 24, Top: 458, Right: 636, Bottom: 490}
} }
func settingsHistoryExportBrowseRect() winRect { func settingsHistoryExportBrowseRect() winRect {
return winRect{Left: 644, Top: 420, Right: 736, Bottom: 444} return winRect{Left: 644, Top: 458, Right: 736, Bottom: 490}
} }
func settingsHistoryExportTimeRect() winRect { func settingsHistoryExportTimeRect() winRect {
return winRect{Left: 24, Top: 452, Right: 360, Bottom: 482} return winRect{Left: 24, Top: 510, Right: 360, Bottom: 546}
} }
func settingsHistoryExportWorldRect() winRect { func settingsHistoryExportWorldRect() winRect {
return winRect{Left: 376, Top: 452, Right: 736, Bottom: 482} return winRect{Left: 376, Top: 510, Right: 736, Bottom: 546}
} }
func settingsHistoryExportJoinLeaveRect() winRect { func settingsHistoryExportJoinLeaveRect() winRect {
return winRect{Left: 24, Top: 488, Right: 360, Bottom: 518} return winRect{Left: 24, Top: 558, Right: 360, Bottom: 594}
} }
func settingsHistoryExportCustomToggleRect() winRect { func settingsHistoryExportCustomToggleRect() winRect {
return winRect{Left: 376, Top: 488, Right: 736, Bottom: 518} return winRect{Left: 376, Top: 558, Right: 736, Bottom: 594}
} }
func settingsHistoryExportCustomRect() winRect { func settingsHistoryExportCustomRect() winRect {
return winRect{Left: 116, Top: 536, Right: 636, Bottom: 558} return winRect{Left: 24, Top: 646, Right: 636, Bottom: 678}
} }
func settingsHistoryExportCustomSaveRect() winRect { func settingsHistoryExportCustomSaveRect() winRect {
return winRect{Left: 644, Top: 536, Right: 736, Bottom: 558} return winRect{Left: 644, Top: 646, Right: 736, Bottom: 678}
} }
func handleSettingsPaneClick(app *guiApp, x, y int32, getWindowText, setWindowText *syscall.LazyProc) bool { func handleSettingsPaneClick(app *guiApp, x, y int32, getWindowText, setWindowText *syscall.LazyProc) bool {
@@ -2035,9 +2036,9 @@ func drawSettingsRefreshControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsRefreshValueRect(), uint32(0x0038281a), border) drawSettingsBox(hdc, settingsRefreshValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border) drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border)
drawSettingsWideButton(hdc, settingsRefreshUnitRect(), "\u5358\u4f4d", refreshIntervalUnitLabel(app.refreshIntervalUnit), font, true) drawSettingsWideButton(hdc, settingsRefreshUnitRect(), "\u5358\u4f4d", refreshIntervalUnitLabel(app.refreshIntervalUnit), font, true)
drawCenteredPaneText(hdc, settingsRefreshDownRect(), "A-", font, clrText) drawCenteredPaneText(hdc, settingsRefreshDownRect(), "-", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshValueRect(), refreshIntervalValueLabel(app.refreshIntervalValue, app.refreshIntervalUnit), font, clrMutedText) drawCenteredPaneText(hdc, settingsRefreshValueRect(), refreshIntervalValueLabel(app.refreshIntervalValue, app.refreshIntervalUnit), font, clrMutedText)
drawCenteredPaneText(hdc, settingsRefreshUpRect(), "A+", font, clrText) drawCenteredPaneText(hdc, settingsRefreshUpRect(), "+", font, clrText)
} }
func refreshIntervalValueLabel(value int, unit string) string { func refreshIntervalValueLabel(value int, unit string) string {
@@ -2662,7 +2663,7 @@ func drawSettingsFontControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsFontValueRect(), uint32(0x0038281a), border) drawSettingsBox(hdc, settingsFontValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsFontUpRect(), fill, border) drawSettingsBox(hdc, settingsFontUpRect(), fill, border)
drawCenteredPaneText(hdc, settingsFontDownRect(), "A-", font, clrText) drawCenteredPaneText(hdc, settingsFontDownRect(), "A-", font, clrText)
drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize), font, clrMutedText) drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize)+" pt", font, clrMutedText)
drawCenteredPaneText(hdc, settingsFontUpRect(), "A+", font, clrText) drawCenteredPaneText(hdc, settingsFontUpRect(), "A+", font, clrText)
} }
@@ -2945,7 +2946,7 @@ func eventRows(users []userState) []guiEventRow {
} }
func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow { func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow {
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
if len(events) == 0 { if len(events) == 0 {
users, _ := currentUsersFromJoinLeave() users, _ := currentUsersFromJoinLeave()
return eventRows(users) return eventRows(users)