Skip to content

Commit 9f2a588

Browse files
committed
improve screenshots
1 parent 343ca7f commit 9f2a588

5 files changed

Lines changed: 86 additions & 64 deletions

File tree

src-lua/plugins/screenshots/init.lua

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ local ScreenshotPlugin = Class({
3131

3232
self.isRecordingGif = false
3333
self.lastFrameCaptured = 0
34+
self.currentGif = nil
3435
self.tempScreenshots = {}
3536
self.images = {}
3637
self.gifTime = 0
@@ -69,43 +70,44 @@ function ScreenshotPlugin:captureScreenshot()
6970
local timestamp = os.date("%Y%m%d-%H%M%S")
7071
local filename = string.format("%s/screenshot-%s.png", self.screenshotDirectory, timestamp)
7172

72-
love.graphics.captureScreenshot(function(img)
73-
local fileData = img:encode("png")
74-
local pngBytes = fileData:getString()
73+
love.graphics.captureScreenshot(filename)
7574

76-
local b64 = love.data.encode("string", "base64", pngBytes)
75+
local cwd = love.filesystem.getSaveDirectory()
7776

78-
table.insert(self.images, {
79-
type = "png",
80-
data = b64,
81-
name = filename,
82-
fps = 1,
83-
})
84-
if self.logger then
85-
self.logger:logger("[ScreenshotPlugin] Saved screenshot: " .. filename)
86-
end
87-
end)
77+
local path = cwd .. "/" .. filename
78+
79+
table.insert(self.images, {
80+
type = "png",
81+
name = filename,
82+
data = path,
83+
fps = 1,
84+
})
85+
86+
self.logger:logger("[ScreenshotPlugin] Saved screenshot: " .. filename)
8887

8988
return filename
9089
end
9190

9291
--- Capture one frame for GIF
9392
function ScreenshotPlugin:captureFrame()
94-
love.graphics.captureScreenshot(function(img)
95-
local fileData = img:encode("png")
96-
97-
local pngBytes = fileData:getString()
98-
99-
local b64 = love.data.encode("string", "base64", pngBytes)
100-
table.insert(self.tempScreenshots, b64)
101-
end)
93+
local cwd = love.filesystem.getSaveDirectory()
94+
local path = self.screenshotDirectory
95+
.. "/"
96+
.. self.currentGif
97+
.. "/"
98+
.. tostring(#self.tempScreenshots + 1)
99+
.. ".png"
100+
love.graphics.captureScreenshot(path)
101+
table.insert(self.tempScreenshots, cwd .. "/" .. path)
102102
end
103103

104104
--- Start recording GIF
105105
function ScreenshotPlugin:startGifRecording()
106106
if self.isRecordingGif then
107107
return
108108
end
109+
self.currentGif = tostring(os.time())
110+
love.filesystem.createDirectory(self.screenshotDirectory .. "/" .. self.currentGif)
109111
self.isRecordingGif = true
110112
self.tempScreenshots = {}
111113
self.gifTime = 0
@@ -123,7 +125,7 @@ function ScreenshotPlugin:stopGifRecording()
123125
end
124126
self.isRecordingGif = false
125127

126-
local gifName = string.format("%s/recording-%s.gif", self.screenshotDirectory, os.date("%Y%m%d-%H%M%S"))
128+
local gifName = self.screenshotDirectory .. "/" .. self.currentGif .. ".gif"
127129

128130
table.insert(self.images, {
129131
name = gifName,
@@ -133,6 +135,7 @@ function ScreenshotPlugin:stopGifRecording()
133135
})
134136

135137
self.tempScreenshots = {}
138+
self.currentGif = nil
136139

137140
if self.logger then
138141
self.logger:logger(

src/hooks/use-gif.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
33

44
export const useGif = (name: string, images: string[], fps: number, width: number, height: number) => {
55
const { data, isLoading, error } = useQuery<string>({
6-
queryKey: ['gif-create', ...images, name, fps, width, height],
6+
queryKey: ['gif-create', images, name, fps, width, height],
77
queryFn: async () => {
88
const gif = await createGif({
99
name,

src/hooks/use-logs.ts

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readTextFileLines } from '@tauri-apps/plugin-fs';
2-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2+
import { useMutation, useQuery } from '@tanstack/react-query';
33
import { ServerRoute } from '@/constants/server';
44
import { timeout } from '@/utils/timers';
55
import { useConfigStore } from '@/store/config';
@@ -58,7 +58,6 @@ export const useLogs = (): {
5858
clear: () => void;
5959
onScreenshotChange: () => void;
6060
} => {
61-
const queryClient = useQueryClient();
6261
const setDisconnected = useConfigStore((state) => state.setDisconnected);
6362
const disconnected = useConfigStore((state) => state.disconnected);
6463
const logFile = useConfigStore((state) => state.config?.outfile || '');
@@ -67,6 +66,7 @@ export const useLogs = (): {
6766
const { url: serverUrl, apiKey } = useServer();
6867
const [screenshotEnabled, setScreenshotEnabled] = useState(false);
6968
const sampleRate = useSampleRate();
69+
const [clearTime, setClearTime] = useState(0);
7070

7171
const enabled = useMemo(() => {
7272
if (overrideLogFile) {
@@ -79,13 +79,13 @@ export const useLogs = (): {
7979
const logFilePathname = overrideLogFile || logFile;
8080

8181
const { isPending, error, data, refetch } = useQuery({
82-
queryKey: ['logs', logFilePathname],
82+
queryKey: ['logs', logFilePathname, clearTime],
8383
queryFn: async (): Promise<Log[]> => {
8484
try {
8585
const dataLogs: Log[] = [];
8686

8787
if (isWeb()) {
88-
const response = await fetch(`/public/example.log`);
88+
const response = await fetch(logFilePathname);
8989
const raw = await response.text();
9090
const lines = raw.split('\n');
9191
for (const line of lines) {
@@ -108,7 +108,8 @@ export const useLogs = (): {
108108
console.log({ dataLogs });
109109

110110
const logs = unionBy<Log, string>(data || [], dataLogs, (item) => item.id) as Log[];
111-
return logs;
111+
112+
return logs.filter((log) => log.time > clearTime);
112113
} catch (e) {
113114
console.log('error', e);
114115
setDisconnected(true);
@@ -119,30 +120,6 @@ export const useLogs = (): {
119120
enabled: enabled,
120121
});
121122

122-
const mutation = useMutation({
123-
mutationFn: async () => {
124-
try {
125-
await timeout<Response>(
126-
3000,
127-
fetch(`${serverUrl}${ServerRoute.LOG}?action=clear`, {
128-
method: 'POST',
129-
headers: {
130-
'x-api-key': apiKey,
131-
},
132-
}),
133-
);
134-
135-
return [];
136-
} catch {
137-
setDisconnected(true);
138-
return [];
139-
}
140-
},
141-
onSuccess: () => {
142-
queryClient.invalidateQueries({ queryKey: ['logs'] });
143-
},
144-
});
145-
146123
const enableScreenshotsMutation = useMutation({
147124
mutationFn: async () => {
148125
try {
@@ -169,14 +146,9 @@ export const useLogs = (): {
169146
});
170147

171148
const clear = () => {
172-
mutation.mutate();
173-
174-
queryClient.cancelQueries({
175-
queryKey: ['logs'],
176-
exact: true,
177-
});
149+
const lastNow = data?.[data.length - 1]?.time || 0;
178150

179-
queryClient.setQueryData(['logs'], []);
151+
setClearTime(lastNow);
180152
};
181153

182154
const onScreenshotChange = () => {

src/pages/plugins/content.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
import { useGif } from '@/hooks/use-gif';
1111
import { GifType, PluginContentImageType, PluginContentProps, PluginDataType } from '@/hooks/use-plugin';
1212
import { downloadFile } from '@/utils/file';
13+
import { readFile } from '@tauri-apps/plugin-fs';
1314
import { DownloadIcon } from 'lucide-react';
15+
import { useEffect, useState } from 'react';
1416

1517
const DownloadButton = ({ url, extension }: { url?: string; extension: '.png' | '.gif' }) => {
1618
return (
@@ -49,6 +51,46 @@ export function PluginContentTypeGifImage({ name, width, fps, height, src, downl
4951
}
5052

5153
export function PluginContentTypeImage({ name, metadata, downloadable }: PluginContentImageType) {
54+
const [src, setSrc] = useState<string | string[] | null>(null);
55+
56+
useEffect(() => {
57+
if (!metadata.src) {
58+
return;
59+
}
60+
61+
if (metadata.type === 'gif') {
62+
const readImage = async () => {
63+
const urls: string[] = [];
64+
for (let i = 0; i < metadata.src.length; i++) {
65+
const uint8 = await readFile(metadata.src[i]);
66+
const blob = new Blob([uint8], { type: 'image/png' });
67+
const url = URL.createObjectURL(blob);
68+
urls.push(url);
69+
console.log({ url, index: i, src: metadata.src[i] });
70+
}
71+
setSrc(urls);
72+
};
73+
readImage();
74+
return;
75+
}
76+
77+
if (metadata.type === 'png') {
78+
const readImage = async () => {
79+
const uint8 = await readFile(metadata.src);
80+
const blob = new Blob([uint8], { type: 'image/png' });
81+
const url = URL.createObjectURL(blob);
82+
83+
setSrc(url);
84+
};
85+
86+
readImage();
87+
}
88+
}, [metadata.src, metadata.type]);
89+
90+
if (!src) {
91+
return null;
92+
}
93+
5294
if (metadata.type === 'gif') {
5395
return (
5496
<PluginContentTypeGifImage
@@ -57,17 +99,17 @@ export function PluginContentTypeImage({ name, metadata, downloadable }: PluginC
5799
width={metadata.width}
58100
height={metadata.height}
59101
downloadable={downloadable}
60-
src={metadata.src}
102+
src={src as string[]}
61103
fps={metadata.fps}
62104
/>
63105
);
64106
}
65-
const data = `data:image/png;base64,${metadata.src}`;
66107

108+
const url = src as string;
67109
return (
68110
<>
69-
<img className="object-scale-down max-h-full drop-shadow-md rounded-md m-auto" src={data} alt={name} />
70-
{downloadable && <DownloadButton url={data} extension=".png" />}
111+
<img className="object-scale-down max-h-full drop-shadow-md rounded-md m-auto" src={url} alt={name} />
112+
{downloadable && <DownloadButton url={url} extension=".png" />}
71113
</>
72114
);
73115
}

src/utils/assets.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ export async function createGif({
7373
reject(`Failed to load image at index ${index}`);
7474
};
7575

76+
if (src.startsWith('blob:')) {
77+
img.src = src;
78+
return;
79+
}
80+
7681
// Ensure it's a valid data URL
7782
img.src = src.startsWith('data:image') ? src : `data:image/png;base64,${src}`;
7883
});

0 commit comments

Comments
 (0)