From 6b3acfa4c392984eaec87d2e7cc07a852cac4c1e Mon Sep 17 00:00:00 2001 From: Neko Date: Tue, 13 Jan 2026 04:31:58 +0800 Subject: [PATCH] chore(electron-screen-capture): improve the UI, added comments about bugs, refactored with libs & utils (#941) --------- Co-authored-by: Makito <5277268+sumimakito@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Makito --- apps/stage-tamagotchi/package.json | 2 +- .../pages/devtools/screen-capture.vue | 263 +++++++++++++----- packages/electron-screen-capture/package.json | 7 +- packages/electron-screen-capture/src/index.ts | 8 + .../electron-screen-capture/src/main/index.ts | 96 +++++-- .../electron-screen-capture/src/main/utils.ts | 7 +- .../src/vue/use-electron-screen-capture.ts | 12 +- pnpm-lock.yaml | 20 +- pnpm-workspace.yaml | 2 + 9 files changed, 308 insertions(+), 109 deletions(-) diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index e4da095c6..58769d836 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -162,7 +162,7 @@ "builder-util-runtime": "catalog:", "cac": "^6.7.14", "csstype": "^3.2.3", - "electron": "^39.2.7", + "electron": "catalog:", "electron-builder": "^26.4.0", "electron-vite": "^5.0.0", "less": "^4.5.1", diff --git a/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue b/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue index 46d34feaf..5c04a978e 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue @@ -3,8 +3,8 @@ import type { SerializableDesktopCapturerSource } from '@proj-airi/electron-scre import type { SourcesOptions } from 'electron' import { useElectronScreenCapture } from '@proj-airi/electron-screen-capture/vue' -import { Button } from '@proj-airi/ui' -import { onBeforeUnmount, onMounted, ref } from 'vue' +import { Button, SelectTab } from '@proj-airi/ui' +import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useI18n } from 'vue-i18n' import WithScreenCapture from '../../components/WithScreenCapture.vue' @@ -14,9 +14,13 @@ interface ScreenCaptureSource extends SerializableDesktopCapturerSource { thumbnailURL?: string } +type SourceCategory = 'applications' | 'displays' | 'devices' + const sources = ref([]) const isRefetching = ref(false) const activeStreams = ref([]) +const sourceCategory = ref('applications') +const hasFetchedOnce = ref(false) const sourcesOptions = ref({ types: ['screen', 'window'], @@ -29,6 +33,50 @@ const { selectWithSource, } = useElectronScreenCapture(window.electron.ipcRenderer, sourcesOptions) +const categoryOptions = [ + { label: 'Applications', value: 'applications', icon: 'i-solar:window-frame-line-duotone' }, + { label: 'Displays', value: 'displays', icon: 'i-solar:screencast-2-line-duotone' }, + { label: 'Devices', value: 'devices', icon: 'i-solar:smartphone-2-line-duotone' }, +] + +const isDisplaySource = (source: ScreenCaptureSource) => source.id.startsWith('screen:') +const isWindowSource = (source: ScreenCaptureSource) => source.id.startsWith('window:') +const isDeviceSource = (source: ScreenCaptureSource) => source.id.startsWith('device:') + +const filteredSources = computed(() => { + if (sourceCategory.value === 'applications') + return sources.value.filter(isWindowSource) + if (sourceCategory.value === 'displays') + return sources.value.filter(isDisplaySource) + return sources.value.filter(isDeviceSource) +}) + +const sourceCounts = computed(() => ({ + applications: sources.value.filter(isWindowSource).length, + displays: sources.value.filter(isDisplaySource).length, + devices: sources.value.filter(isDeviceSource).length, +})) + +const isInitialLoading = computed(() => !hasFetchedOnce.value && isRefetching.value) + +const refetchLabel = computed(() => { + if (isInitialLoading.value) + return 'Loading...' + return isRefetching.value ? 'Refetching...' : 'Refetch' +}) + +const refetchIcon = computed(() => + isInitialLoading.value ? 'i-svg-spinners:ring-resize' : 'i-solar:refresh-line-duotone', +) + +function getShareLabel(source: ScreenCaptureSource) { + if (isDisplaySource(source)) + return 'Share Screen' + if (isDeviceSource(source)) + return 'Share Device' + return 'Share Window' +} + function toLocalArrayBuffer(bytes: Uint8Array) { if (typeof SharedArrayBuffer !== 'undefined' && bytes.buffer instanceof SharedArrayBuffer) { return bytes.slice().buffer @@ -86,6 +134,11 @@ async function refetchSources() { sources.value = nextSources.map(source => ({ ...source, + // NOTICE(@nekomeowww): In probability of 9/10, the window thumbnail is purely empty or black, sources printed and + // nothing is returned from the desktopCapturer API. + // NOTICE(@sumimakito): Not only thumbnail is empty, the appIcon could be empty as well with nothing returned. + // REVIEW(@sumimakito): This has nothing to do with our side, probably related to a Electron bug, you can + // read more here https://github.com/electron/electron/issues/44504 appIconURL: source.appIcon && source.appIcon.length > 0 ? toObjectUrl(source.appIcon, 'image/png') : undefined, thumbnailURL: source.thumbnail && source.thumbnail.length > 0 ? toObjectUrl(source.thumbnail, 'image/jpeg') : undefined, })) @@ -95,6 +148,7 @@ async function refetchSources() { } finally { isRefetching.value = false + hasFetchedOnce.value = true } } @@ -117,39 +171,40 @@ onBeforeUnmount(() => {