mirror of
https://github.com/SrIzan10/helium.git
synced 2026-06-16 07:47:35 +00:00
Compare commits
5 Commits
v0.2.0
...
feat/multi
| Author | SHA1 | Date | |
|---|---|---|---|
| 045e363dc1 | |||
| 7170d87bbd | |||
| f0ac8ac951 | |||
| aad1c72bef | |||
| c61e6e3679 |
2
.github/workflows/electron-release.yml
vendored
2
.github/workflows/electron-release.yml
vendored
@@ -30,8 +30,6 @@ jobs:
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -31,6 +31,21 @@ export interface StreamingClosePromptOptions {
|
||||
cancelLabel: string;
|
||||
}
|
||||
|
||||
export type UpdateStatus =
|
||||
| "checking"
|
||||
| "available"
|
||||
| "not-available"
|
||||
| "download-progress"
|
||||
| "downloaded"
|
||||
| "error";
|
||||
|
||||
export interface UpdateStatusPayload {
|
||||
status: UpdateStatus;
|
||||
version?: string;
|
||||
percent?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface HeliumElectronAPI {
|
||||
isElectron: boolean;
|
||||
getPlatform: () => Promise<PlatformInfo>;
|
||||
@@ -48,6 +63,9 @@ interface HeliumElectronAPI {
|
||||
active: boolean,
|
||||
promptOptions?: StreamingClosePromptOptions,
|
||||
) => Promise<boolean>;
|
||||
checkForUpdates: () => Promise<boolean>;
|
||||
installUpdate: () => Promise<boolean>;
|
||||
onUpdateStatus: (callback: (payload: UpdateStatusPayload) => void) => () => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -263,6 +281,33 @@ export function useElectron() {
|
||||
}
|
||||
};
|
||||
|
||||
const checkForUpdates = async (): Promise<boolean> => {
|
||||
if (!checkElectron()) return false;
|
||||
|
||||
try {
|
||||
return await window.heliumElectron!.checkForUpdates();
|
||||
} catch (error) {
|
||||
console.error("[useElectron] Failed to check for updates:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const installUpdate = async (): Promise<boolean> => {
|
||||
if (!checkElectron()) return false;
|
||||
|
||||
try {
|
||||
return await window.heliumElectron!.installUpdate();
|
||||
} catch (error) {
|
||||
console.error("[useElectron] Failed to install update:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdateStatus = (callback: (payload: UpdateStatusPayload) => void): (() => void) => {
|
||||
if (!checkElectron()) return () => {};
|
||||
return window.heliumElectron!.onUpdateStatus(callback);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkElectron();
|
||||
if (isElectron.value) {
|
||||
@@ -300,6 +345,9 @@ export function useElectron() {
|
||||
getScreenPermissionStatus,
|
||||
openScreenPermissionSettings,
|
||||
setStreamingActive,
|
||||
checkForUpdates,
|
||||
installUpdate,
|
||||
onUpdateStatus,
|
||||
|
||||
startScreenShareWithAudio,
|
||||
stopScreenShare,
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
import SignInDialog from "~/components/app/SignInDialog.vue";
|
||||
import ThemeDropdown from "~/components/ui/ThemeDropdown.vue";
|
||||
import LanguageSwitcher from "~/components/app/LanguageSwitcher.vue";
|
||||
import { useElectron } from "~/composables/useElectron";
|
||||
import { useElectron, type UpdateStatusPayload } from "~/composables/useElectron";
|
||||
import "vue-sonner/style.css";
|
||||
import { toast } from "vue-sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import {
|
||||
Sheet,
|
||||
@@ -17,7 +18,15 @@ import LogoSvg from "~/assets/logo.svg?component";
|
||||
|
||||
const { t } = useI18n();
|
||||
const mobileMenuOpen = ref(false);
|
||||
const { isElectron, platformInfo, getPlatformInfo } = useElectron();
|
||||
const {
|
||||
isElectron,
|
||||
platformInfo,
|
||||
getPlatformInfo,
|
||||
installUpdate,
|
||||
onUpdateStatus,
|
||||
} = useElectron();
|
||||
let removeUpdateStatusListener: (() => void) | undefined;
|
||||
let updateProgressToastId: string | number | undefined;
|
||||
|
||||
const isMacElectron = computed(() => {
|
||||
return isElectron.value && platformInfo.value?.isMac;
|
||||
@@ -40,6 +49,66 @@ const navLinks = [
|
||||
const visibleNavLinks = computed(() => {
|
||||
return navLinks.filter((link) => !isElectron.value || !link.hideInElectron);
|
||||
});
|
||||
|
||||
const showUpdateMessage = (payload: UpdateStatusPayload): void => {
|
||||
if (payload.status === "checking") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.status === "available") {
|
||||
toast.info(t("updateAvailable"), {
|
||||
description: payload.version
|
||||
? t("updateAvailableDescription", { version: payload.version })
|
||||
: t("updateAvailableDescriptionWithoutVersion"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.status === "not-available") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.status === "download-progress") {
|
||||
const percent = payload.percent ?? 0;
|
||||
updateProgressToastId = toast.loading(t("updateDownloading"), {
|
||||
id: updateProgressToastId,
|
||||
description: t("updateDownloadProgress", { percent }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.status === "downloaded") {
|
||||
if (updateProgressToastId) {
|
||||
toast.dismiss(updateProgressToastId);
|
||||
updateProgressToastId = undefined;
|
||||
}
|
||||
|
||||
toast.success(t("updateReady"), {
|
||||
description: payload.version
|
||||
? t("updateReadyDescription", { version: payload.version })
|
||||
: t("updateReadyDescriptionWithoutVersion"),
|
||||
action: {
|
||||
label: t("restartToUpdate"),
|
||||
onClick: () => {
|
||||
void installUpdate();
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error(t("updateFailed"), {
|
||||
description: payload.message || t("updateFailedDescription"),
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
removeUpdateStatusListener = onUpdateStatus(showUpdateMessage);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
removeUpdateStatusListener?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -25,6 +25,13 @@ definePageMeta({
|
||||
layout: "default",
|
||||
});
|
||||
|
||||
interface PlatformDownload {
|
||||
name: string;
|
||||
icon: Component;
|
||||
formats: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface GitHubReleaseAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
@@ -35,13 +42,6 @@ interface GitHubRelease {
|
||||
assets: GitHubReleaseAsset[];
|
||||
}
|
||||
|
||||
interface PlatformDownload {
|
||||
name: string;
|
||||
icon: Component;
|
||||
formats: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const repositoryUrl = "https://github.com/SrIzan10/helium";
|
||||
@@ -76,15 +76,8 @@ function findReleaseAsset(
|
||||
});
|
||||
}
|
||||
|
||||
function getReleaseAssetUrl(
|
||||
patterns: readonly RegExp[],
|
||||
fallbackUrl?: string,
|
||||
): string {
|
||||
return (
|
||||
findReleaseAsset(patterns)?.browser_download_url ??
|
||||
fallbackUrl ??
|
||||
latestReleaseUrl.value
|
||||
);
|
||||
function getReleaseAssetUrl(patterns: readonly RegExp[]): string {
|
||||
return findReleaseAsset(patterns)?.browser_download_url ?? latestReleaseUrl.value;
|
||||
}
|
||||
|
||||
const desktopPlatforms = computed<PlatformDownload[]>(() => {
|
||||
@@ -93,19 +86,19 @@ const desktopPlatforms = computed<PlatformDownload[]>(() => {
|
||||
name: "Windows",
|
||||
icon: Laptop,
|
||||
formats: "NSIS, Portable",
|
||||
href: getReleaseAssetUrl([/-Setup-.*\\.exe$/i, /\\.exe$/i]),
|
||||
href: getReleaseAssetUrl([/-Setup-.*\.exe$/i, /\.exe$/i]),
|
||||
},
|
||||
{
|
||||
name: "macOS",
|
||||
icon: Apple,
|
||||
formats: "DMG, ZIP",
|
||||
href: getReleaseAssetUrl([/\\.dmg$/i, /-mac\\.zip$/i]),
|
||||
href: getReleaseAssetUrl([/\.dmg$/i, /-mac\.zip$/i]),
|
||||
},
|
||||
{
|
||||
name: "Linux",
|
||||
icon: Laptop,
|
||||
formats: "AppImage",
|
||||
href: getReleaseAssetUrl([/\\.AppImage$/i]),
|
||||
href: getReleaseAssetUrl([/\.AppImage$/i]),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -115,7 +108,7 @@ const androidPlatform = computed<PlatformDownload>(() => {
|
||||
name: "Android",
|
||||
icon: Smartphone,
|
||||
formats: "APK",
|
||||
href: getReleaseAssetUrl([/^helium-android-.*\\.apk$/i]),
|
||||
href: getReleaseAssetUrl([/^helium-android-.*\.apk$/i]),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -55,6 +55,94 @@
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1">
|
||||
<Label
|
||||
class="text-xs text-muted-foreground uppercase tracking-wider font-semibold"
|
||||
>
|
||||
{{ $t("streamQuality") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ $t("streamQualityDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
v-for="preset in qualityPresets"
|
||||
:key="preset.id"
|
||||
type="button"
|
||||
class="rounded-lg border px-2.5 py-2 text-left transition hover:border-primary/60 hover:bg-primary/5"
|
||||
:class="[
|
||||
isSelectedQuickPreset(preset)
|
||||
? 'border-primary bg-primary/10 shadow-sm'
|
||||
: 'border-border bg-background',
|
||||
]"
|
||||
@click="applyQuickPreset(preset)"
|
||||
>
|
||||
<span class="flex items-center justify-between gap-2">
|
||||
<span class="text-xs font-semibold">
|
||||
{{ $t(preset.labelKey) }}
|
||||
</span>
|
||||
<Badge
|
||||
v-if="isSelectedQuickPreset(preset)"
|
||||
variant="secondary"
|
||||
class="text-[9px] uppercase tracking-wide"
|
||||
>
|
||||
{{ $t("active") }}
|
||||
</Badge>
|
||||
</span>
|
||||
<span class="mt-0.5 block text-[11px] text-muted-foreground">
|
||||
{{ $t(preset.summaryKey) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs text-muted-foreground">
|
||||
{{ $t("quality") }}
|
||||
</Label>
|
||||
<Select v-model="selectedVideoQuality">
|
||||
<SelectTrigger class="w-full h-9">
|
||||
<SelectValue :placeholder="$t('quality')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="quality in videoQualityOptions"
|
||||
:key="quality.id"
|
||||
:value="quality.id"
|
||||
>
|
||||
{{ $t(quality.labelKey) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs text-muted-foreground">
|
||||
{{ $t("fps") }}
|
||||
</Label>
|
||||
<Select v-model="selectedFrameRate">
|
||||
<SelectTrigger class="w-full h-9">
|
||||
<SelectValue :placeholder="$t('fps')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="option in frameRateOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
class="text-xs text-muted-foreground uppercase tracking-wider font-semibold"
|
||||
@@ -111,6 +199,22 @@
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-3 rounded-lg border px-3 py-2">
|
||||
<div class="space-y-0.5">
|
||||
<Label for="high-quality-audio" class="text-sm font-medium">
|
||||
{{ $t("highQualityAudio") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ $t("highQualityAudioDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="high-quality-audio"
|
||||
v-model="highQualityAudio"
|
||||
:disabled="!includeAudio"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="platformInfo?.isLinux && platformInfo?.supportsVenmic && includeAudio"
|
||||
class="space-y-3 pt-2 border-t border-border"
|
||||
@@ -230,9 +334,137 @@ const isCleaningUp = ref(false);
|
||||
const wsUrl = useWebSocketUrl();
|
||||
|
||||
const includeAudio = ref(true);
|
||||
const highQualityAudio = ref(true);
|
||||
const selectedAudioSource = ref("all");
|
||||
const selectedPresetId = ref("");
|
||||
|
||||
const streamSettingsStorageKey = "helium-stream-settings";
|
||||
|
||||
type VideoQualityId = "dataSaver" | "standard" | "sharp" | "ultra";
|
||||
type FrameRateValue = "24" | "30" | "60";
|
||||
|
||||
interface QualityPreset {
|
||||
id: string;
|
||||
labelKey: string;
|
||||
summaryKey: string;
|
||||
quality: VideoQualityId;
|
||||
frameRate: FrameRateValue;
|
||||
}
|
||||
|
||||
interface VideoQualityOption {
|
||||
id: VideoQualityId;
|
||||
labelKey: string;
|
||||
width: number;
|
||||
height: number;
|
||||
maxBitrate: number;
|
||||
contentHint: "motion" | "detail";
|
||||
}
|
||||
|
||||
interface FrameRateOption {
|
||||
value: FrameRateValue;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface StoredStreamSettings {
|
||||
videoQuality?: VideoQualityId;
|
||||
frameRate?: FrameRateValue;
|
||||
includeAudio?: boolean;
|
||||
highQualityAudio?: boolean;
|
||||
audioSource?: string;
|
||||
}
|
||||
|
||||
const qualityPresets: QualityPreset[] = [
|
||||
{
|
||||
id: "speed",
|
||||
labelKey: "preferSpeed",
|
||||
summaryKey: "preferSpeedSummary",
|
||||
quality: "dataSaver",
|
||||
frameRate: "24",
|
||||
},
|
||||
{
|
||||
id: "balanced",
|
||||
labelKey: "balanced",
|
||||
summaryKey: "balancedSummary",
|
||||
quality: "standard",
|
||||
frameRate: "30",
|
||||
},
|
||||
{
|
||||
id: "quality",
|
||||
labelKey: "preferQuality",
|
||||
summaryKey: "preferQualitySummary",
|
||||
quality: "sharp",
|
||||
frameRate: "30",
|
||||
},
|
||||
{
|
||||
id: "cinematic",
|
||||
labelKey: "highMotion",
|
||||
summaryKey: "highMotionSummary",
|
||||
quality: "standard",
|
||||
frameRate: "60",
|
||||
},
|
||||
];
|
||||
|
||||
const videoQualityOptions: VideoQualityOption[] = [
|
||||
{
|
||||
id: "dataSaver",
|
||||
labelKey: "dataSaver",
|
||||
width: 1280,
|
||||
height: 720,
|
||||
maxBitrate: 1_200_000,
|
||||
contentHint: "detail",
|
||||
},
|
||||
{
|
||||
id: "standard",
|
||||
labelKey: "standard",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
maxBitrate: 2_800_000,
|
||||
contentHint: "detail",
|
||||
},
|
||||
{
|
||||
id: "sharp",
|
||||
labelKey: "sharp",
|
||||
width: 2560,
|
||||
height: 1440,
|
||||
maxBitrate: 5_000_000,
|
||||
contentHint: "detail",
|
||||
},
|
||||
{
|
||||
id: "ultra",
|
||||
labelKey: "ultra",
|
||||
width: 3840,
|
||||
height: 2160,
|
||||
maxBitrate: 8_000_000,
|
||||
contentHint: "detail",
|
||||
},
|
||||
];
|
||||
|
||||
const frameRateOptions: FrameRateOption[] = [
|
||||
{ value: "24", label: "24 FPS" },
|
||||
{ value: "30", label: "30 FPS" },
|
||||
{ value: "60", label: "60 FPS" },
|
||||
];
|
||||
|
||||
const selectedVideoQuality = ref<VideoQualityId>("standard");
|
||||
const selectedFrameRate = ref<FrameRateValue>("30");
|
||||
|
||||
const activeQualityPreset = computed(
|
||||
() =>
|
||||
videoQualityOptions.find(
|
||||
(quality) => quality.id === selectedVideoQuality.value,
|
||||
) ?? videoQualityOptions[1]!,
|
||||
);
|
||||
|
||||
const activeFrameRate = computed(() => Number(selectedFrameRate.value));
|
||||
|
||||
const activeContentHint = computed(() =>
|
||||
selectedFrameRate.value === "60" ? "motion" : activeQualityPreset.value.contentHint,
|
||||
);
|
||||
|
||||
const audioMaxBitrate = computed(() =>
|
||||
highQualityAudio.value ? 128_000 : 64_000,
|
||||
);
|
||||
|
||||
const {
|
||||
isElectron,
|
||||
platformInfo,
|
||||
@@ -249,6 +481,7 @@ const {
|
||||
} = useElectron();
|
||||
|
||||
onMounted(async () => {
|
||||
restoreStreamSettings();
|
||||
await getPlatformInfo();
|
||||
|
||||
if (platformInfo.value?.isLinux && platformInfo.value?.supportsVenmic) {
|
||||
@@ -280,6 +513,95 @@ watch([localStream, locale], async ([stream]) => {
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
[selectedVideoQuality, selectedFrameRate, highQualityAudio],
|
||||
async () => {
|
||||
persistStreamSettings();
|
||||
|
||||
if (!localStream.value) return;
|
||||
|
||||
await applyCurrentStreamSettings();
|
||||
toast.success(t("streamQualityUpdated"));
|
||||
},
|
||||
);
|
||||
|
||||
watch([includeAudio, selectedAudioSource], () => {
|
||||
persistStreamSettings();
|
||||
});
|
||||
|
||||
function isVideoQualityId(value: unknown): value is VideoQualityId {
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
videoQualityOptions.some((quality) => quality.id === value)
|
||||
);
|
||||
}
|
||||
|
||||
function isFrameRateValue(value: unknown): value is FrameRateValue {
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
frameRateOptions.some((option) => option.value === value)
|
||||
);
|
||||
}
|
||||
|
||||
function restoreStreamSettings(): void {
|
||||
const rawSettings = localStorage.getItem(streamSettingsStorageKey);
|
||||
|
||||
if (!rawSettings) return;
|
||||
|
||||
try {
|
||||
const settings = JSON.parse(rawSettings) as StoredStreamSettings;
|
||||
|
||||
if (isVideoQualityId(settings.videoQuality)) {
|
||||
selectedVideoQuality.value = settings.videoQuality;
|
||||
}
|
||||
if (isFrameRateValue(settings.frameRate)) {
|
||||
selectedFrameRate.value = settings.frameRate;
|
||||
}
|
||||
if (typeof settings.includeAudio === "boolean") {
|
||||
includeAudio.value = settings.includeAudio;
|
||||
}
|
||||
if (typeof settings.highQualityAudio === "boolean") {
|
||||
highQualityAudio.value = settings.highQualityAudio;
|
||||
}
|
||||
if (typeof settings.audioSource === "string") {
|
||||
selectedAudioSource.value = settings.audioSource;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to restore stream settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function persistStreamSettings(): void {
|
||||
const settings: StoredStreamSettings = {
|
||||
videoQuality: selectedVideoQuality.value,
|
||||
frameRate: selectedFrameRate.value,
|
||||
includeAudio: includeAudio.value,
|
||||
highQualityAudio: highQualityAudio.value,
|
||||
audioSource: selectedAudioSource.value,
|
||||
};
|
||||
|
||||
localStorage.setItem(streamSettingsStorageKey, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
function applyQuickPreset(preset: QualityPreset): void {
|
||||
selectedVideoQuality.value = preset.quality;
|
||||
selectedFrameRate.value = preset.frameRate;
|
||||
}
|
||||
|
||||
function isSelectedQuickPreset(preset: QualityPreset): boolean {
|
||||
return (
|
||||
selectedVideoQuality.value === preset.quality &&
|
||||
selectedFrameRate.value === preset.frameRate
|
||||
);
|
||||
}
|
||||
|
||||
async function applyCurrentStreamSettings(): Promise<void> {
|
||||
if (!localStream.value) return;
|
||||
|
||||
await applyQualityPresetToStream(localStream.value);
|
||||
await applyQualityPresetToPeerConnections();
|
||||
}
|
||||
|
||||
async function refreshAudioSources() {
|
||||
await getVenmicSources();
|
||||
}
|
||||
@@ -289,6 +611,177 @@ async function copyCode() {
|
||||
toast.success(t("codeCopied"));
|
||||
}
|
||||
|
||||
function getDisplayMediaConstraints(): DisplayMediaStreamOptions {
|
||||
const preset = activeQualityPreset.value;
|
||||
const frameRate = activeFrameRate.value;
|
||||
const shouldRequestAudio =
|
||||
isElectron.value && includeAudio.value && supportsAudioScreenShare.value;
|
||||
|
||||
return {
|
||||
video: {
|
||||
width: { ideal: preset.width },
|
||||
height: { ideal: preset.height },
|
||||
frameRate: { ideal: frameRate, max: frameRate },
|
||||
},
|
||||
audio: shouldRequestAudio
|
||||
? {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false,
|
||||
}
|
||||
: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function applyQualityPresetToSender(
|
||||
sender: RTCRtpSender,
|
||||
): Promise<void> {
|
||||
const preset = activeQualityPreset.value;
|
||||
const frameRate = activeFrameRate.value;
|
||||
const parameters = sender.getParameters();
|
||||
|
||||
if (!parameters.encodings || parameters.encodings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [encoding] = parameters.encodings;
|
||||
encoding.maxBitrate = preset.maxBitrate;
|
||||
encoding.maxFramerate = frameRate;
|
||||
|
||||
await sender.setParameters(parameters);
|
||||
}
|
||||
|
||||
async function applyAudioQualityToSender(sender: RTCRtpSender): Promise<void> {
|
||||
const parameters = sender.getParameters();
|
||||
|
||||
if (!parameters.encodings || parameters.encodings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [encoding] = parameters.encodings;
|
||||
encoding.maxBitrate = audioMaxBitrate.value;
|
||||
|
||||
await sender.setParameters(parameters);
|
||||
}
|
||||
|
||||
async function safelyApplySenderSettings(
|
||||
sender: RTCRtpSender,
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (sender.track?.kind === "video") {
|
||||
await applyQualityPresetToSender(sender);
|
||||
}
|
||||
if (sender.track?.kind === "audio") {
|
||||
await applyAudioQualityToSender(sender);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to apply stream sender settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function withVideoBitrateHints(sdp: string): string {
|
||||
const lines = sdp.split("\r\n");
|
||||
const videoLineIndex = lines.findIndex((line) => line.startsWith("m=video"));
|
||||
|
||||
if (videoLineIndex === -1) return sdp;
|
||||
|
||||
const nextMediaLineIndex = lines.findIndex(
|
||||
(line, index) => index > videoLineIndex && line.startsWith("m="),
|
||||
);
|
||||
const videoSectionEnd =
|
||||
nextMediaLineIndex === -1 ? lines.length : nextMediaLineIndex;
|
||||
const maxBitrateKbps = Math.round(activeQualityPreset.value.maxBitrate / 1000);
|
||||
const startBitrateKbps = Math.min(
|
||||
maxBitrateKbps,
|
||||
Math.max(1_000, Math.round(maxBitrateKbps * 0.75)),
|
||||
);
|
||||
const payloadTypes = lines[videoLineIndex]!.split(" ").slice(3);
|
||||
const tunedPayloadTypes = payloadTypes.filter((payloadType) => {
|
||||
const rtmap = lines
|
||||
.slice(videoLineIndex, videoSectionEnd)
|
||||
.find((line) => line.startsWith(`a=rtmap:${payloadType} `));
|
||||
|
||||
return /\b(VP8|VP9|H264|AV1)\//i.test(rtmap ?? "");
|
||||
});
|
||||
|
||||
const hasBandwidthHint = lines
|
||||
.slice(videoLineIndex, videoSectionEnd)
|
||||
.some((line) => line.startsWith("b=AS:") || line.startsWith("b=TIAS:"));
|
||||
|
||||
if (!hasBandwidthHint) {
|
||||
const connectionLineIndex = lines.findIndex(
|
||||
(line, index) =>
|
||||
index > videoLineIndex &&
|
||||
index < videoSectionEnd &&
|
||||
line.startsWith("c="),
|
||||
);
|
||||
|
||||
if (connectionLineIndex !== -1) {
|
||||
lines.splice(connectionLineIndex + 1, 0, `b=AS:${maxBitrateKbps}`);
|
||||
}
|
||||
}
|
||||
|
||||
tunedPayloadTypes.forEach((payloadType) => {
|
||||
const fmtpIndex = lines.findIndex(
|
||||
(line, index) =>
|
||||
index > videoLineIndex &&
|
||||
index < videoSectionEnd &&
|
||||
line.startsWith(`a=fmtp:${payloadType} `),
|
||||
);
|
||||
const bitrateHint = `x-google-start-bitrate=${startBitrateKbps};x-google-max-bitrate=${maxBitrateKbps}`;
|
||||
|
||||
if (fmtpIndex !== -1) {
|
||||
if (!lines[fmtpIndex]!.includes("x-google-start-bitrate")) {
|
||||
lines[fmtpIndex] = `${lines[fmtpIndex]};${bitrateHint}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const rtmapIndex = lines.findIndex(
|
||||
(line, index) =>
|
||||
index > videoLineIndex &&
|
||||
index < videoSectionEnd &&
|
||||
line.startsWith(`a=rtmap:${payloadType} `),
|
||||
);
|
||||
|
||||
if (rtmapIndex !== -1) {
|
||||
lines.splice(rtmapIndex + 1, 0, `a=fmtp:${payloadType} ${bitrateHint}`);
|
||||
}
|
||||
});
|
||||
|
||||
return lines.join("\r\n");
|
||||
}
|
||||
|
||||
async function applyQualityPresetToPeerConnections(): Promise<void> {
|
||||
const senders = Object.values(streamerStore.peerConnections).flatMap((pc) =>
|
||||
pc.getSenders(),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
senders.map((sender) => safelyApplySenderSettings(sender)),
|
||||
);
|
||||
}
|
||||
|
||||
async function applyQualityPresetToStream(stream: MediaStream): Promise<void> {
|
||||
const preset = activeQualityPreset.value;
|
||||
const frameRate = activeFrameRate.value;
|
||||
const [videoTrack] = stream.getVideoTracks();
|
||||
|
||||
if (!videoTrack) return;
|
||||
|
||||
videoTrack.contentHint = activeContentHint.value;
|
||||
|
||||
try {
|
||||
await videoTrack.applyConstraints({
|
||||
width: { ideal: preset.width },
|
||||
height: { ideal: preset.height },
|
||||
frameRate: { ideal: frameRate, max: frameRate },
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("Failed to apply capture quality constraints:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function notifyRoomClosed() {
|
||||
if (!streamerStore.code) return;
|
||||
|
||||
@@ -351,13 +844,23 @@ const { send, close: closeWebSocket } = useWebSocket(wsUrl, {
|
||||
};
|
||||
|
||||
const offer = await peerConnection.createOffer();
|
||||
await peerConnection.setLocalDescription(offer);
|
||||
const tunedOffer: RTCSessionDescriptionInit = {
|
||||
type: offer.type,
|
||||
sdp: offer.sdp ? withVideoBitrateHints(offer.sdp) : undefined,
|
||||
};
|
||||
await peerConnection.setLocalDescription(tunedOffer);
|
||||
|
||||
void Promise.all(
|
||||
peerConnection
|
||||
.getSenders()
|
||||
.map((sender) => safelyApplySenderSettings(sender)),
|
||||
);
|
||||
|
||||
send(
|
||||
JSON.stringify({
|
||||
event: "offer",
|
||||
targetId: message.viewerId,
|
||||
sdp: offer,
|
||||
sdp: peerConnection.localDescription,
|
||||
iceServers: streamerStore.iceServers,
|
||||
}),
|
||||
);
|
||||
@@ -415,19 +918,11 @@ async function startScreenShare() {
|
||||
}
|
||||
}
|
||||
|
||||
const shouldRequestAudio =
|
||||
isElectron.value && includeAudio.value && supportsAudioScreenShare.value;
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
getDisplayMediaConstraints(),
|
||||
);
|
||||
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: true,
|
||||
audio: shouldRequestAudio
|
||||
? {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false,
|
||||
}
|
||||
: false,
|
||||
});
|
||||
await applyQualityPresetToStream(stream);
|
||||
|
||||
localStream.value = stream;
|
||||
|
||||
@@ -471,45 +966,50 @@ async function changeScreenShareSource() {
|
||||
}
|
||||
}
|
||||
|
||||
const shouldRequestAudio =
|
||||
isElectron.value && includeAudio.value && supportsAudioScreenShare.value;
|
||||
|
||||
const newStream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: true,
|
||||
audio: shouldRequestAudio,
|
||||
});
|
||||
const newStream = await navigator.mediaDevices.getDisplayMedia(
|
||||
getDisplayMediaConstraints(),
|
||||
);
|
||||
|
||||
if (!localStream.value) return;
|
||||
|
||||
const newVideoTrack = newStream.getVideoTracks()[0];
|
||||
const newAudioTrack = newStream.getAudioTracks()[0];
|
||||
|
||||
await applyQualityPresetToStream(newStream);
|
||||
|
||||
newVideoTrack!.onended = () => {
|
||||
console.log("Screen sharing stopped by user");
|
||||
stopStreaming();
|
||||
};
|
||||
|
||||
Object.values(streamerStore.peerConnections).forEach((pc) => {
|
||||
const senders = pc.getSenders();
|
||||
const peerUpdates = Object.values(streamerStore.peerConnections).map(
|
||||
async (pc) => {
|
||||
const senders = pc.getSenders();
|
||||
|
||||
const videoSender = senders.find(
|
||||
(sender) => sender.track?.kind === "video",
|
||||
);
|
||||
if (videoSender) {
|
||||
videoSender.replaceTrack(newVideoTrack!);
|
||||
}
|
||||
|
||||
if (newAudioTrack) {
|
||||
const audioSender = senders.find(
|
||||
(sender) => sender.track?.kind === "audio",
|
||||
const videoSender = senders.find(
|
||||
(sender) => sender.track?.kind === "video",
|
||||
);
|
||||
if (audioSender) {
|
||||
audioSender.replaceTrack(newAudioTrack);
|
||||
} else {
|
||||
pc.addTrack(newAudioTrack, newStream);
|
||||
if (videoSender) {
|
||||
await videoSender.replaceTrack(newVideoTrack!);
|
||||
await safelyApplySenderSettings(videoSender);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (newAudioTrack) {
|
||||
const audioSender = senders.find(
|
||||
(sender) => sender.track?.kind === "audio",
|
||||
);
|
||||
if (audioSender) {
|
||||
await audioSender.replaceTrack(newAudioTrack);
|
||||
await safelyApplySenderSettings(audioSender);
|
||||
} else {
|
||||
const sender = pc.addTrack(newAudioTrack, newStream);
|
||||
await safelyApplySenderSettings(sender);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await Promise.all(peerUpdates);
|
||||
|
||||
localStream.value.getTracks().forEach((track) => {
|
||||
track.stop();
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type IpcMainInvokeEvent,
|
||||
type DesktopCapturerSource,
|
||||
} from 'electron';
|
||||
import electronUpdater, { type AppUpdater, type ProgressInfo, type UpdateInfo } from 'electron-updater';
|
||||
import path from 'path';
|
||||
import { VenmicManager, type VenmicLinkOptions } from './venmic';
|
||||
|
||||
@@ -50,6 +51,7 @@ const NUXT_DEV_URL = process.env.NUXT_DEV_URL || 'http://localhost:3000';
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let venmicManager: VenmicManager | null = null;
|
||||
let isStreamingActive = false;
|
||||
let autoUpdaterConfigured = false;
|
||||
let streamingClosePrompt = {
|
||||
title: 'Active stream',
|
||||
message: 'A stream is still active. Closing Helium will stop it for all viewers.',
|
||||
@@ -61,6 +63,83 @@ console.log('[Helium] Platform:', process.platform);
|
||||
console.log('[Helium] Wayland:', isWayland);
|
||||
console.log('[Helium] XDG_SESSION_TYPE:', process.env.XDG_SESSION_TYPE);
|
||||
|
||||
type UpdateStatus =
|
||||
| 'checking'
|
||||
| 'available'
|
||||
| 'not-available'
|
||||
| 'download-progress'
|
||||
| 'downloaded'
|
||||
| 'error';
|
||||
|
||||
interface UpdateStatusPayload {
|
||||
status: UpdateStatus;
|
||||
version?: string;
|
||||
percent?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
function getAutoUpdater(): AppUpdater {
|
||||
const { autoUpdater } = electronUpdater;
|
||||
return autoUpdater;
|
||||
}
|
||||
|
||||
function sendUpdateStatus(payload: UpdateStatusPayload): void {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (!window.isDestroyed()) {
|
||||
window.webContents.send('helium:update-status', payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toUpdateStatus(status: UpdateStatus, info?: UpdateInfo): UpdateStatusPayload {
|
||||
return {
|
||||
status,
|
||||
version: info?.version,
|
||||
};
|
||||
}
|
||||
|
||||
function setupAutoUpdater(): void {
|
||||
if (isDev || !app.isPackaged || autoUpdaterConfigured) return;
|
||||
|
||||
autoUpdaterConfigured = true;
|
||||
const autoUpdater = getAutoUpdater();
|
||||
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
sendUpdateStatus({ status: 'checking' });
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (info: UpdateInfo) => {
|
||||
sendUpdateStatus(toUpdateStatus('available', info));
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (info: UpdateInfo) => {
|
||||
sendUpdateStatus(toUpdateStatus('not-available', info));
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
|
||||
sendUpdateStatus({
|
||||
status: 'download-progress',
|
||||
percent: Math.round(progress.percent),
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
||||
sendUpdateStatus(toUpdateStatus('downloaded', info));
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (error: Error) => {
|
||||
console.error('[Helium] Auto updater error:', error);
|
||||
sendUpdateStatus({ status: 'error', message: error.message });
|
||||
});
|
||||
|
||||
void autoUpdater.checkForUpdatesAndNotify().catch((error: unknown) => {
|
||||
console.error('[Helium] Failed to check for updates:', error);
|
||||
});
|
||||
}
|
||||
|
||||
if (isLinux) {
|
||||
try {
|
||||
venmicManager = new VenmicManager();
|
||||
@@ -125,6 +204,10 @@ function createWindow(): void {
|
||||
mainWindow.on('page-title-updated', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
setupAutoUpdater();
|
||||
});
|
||||
}
|
||||
|
||||
function setupDisplayMediaHandler(): void {
|
||||
@@ -265,6 +348,26 @@ ipcMain.handle(
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle('helium:check-for-updates', async () => {
|
||||
if (isDev || !app.isPackaged) return false;
|
||||
|
||||
try {
|
||||
await getAutoUpdater().checkForUpdatesAndNotify();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Helium] Manual update check failed:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('helium:install-update', () => {
|
||||
if (isDev || !app.isPackaged) return false;
|
||||
|
||||
isStreamingActive = false;
|
||||
getAutoUpdater().quitAndInstall(false, true);
|
||||
return true;
|
||||
});
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
|
||||
@@ -42,6 +42,21 @@ export interface StreamingClosePromptOptions {
|
||||
cancelLabel: string;
|
||||
}
|
||||
|
||||
export type UpdateStatus =
|
||||
| 'checking'
|
||||
| 'available'
|
||||
| 'not-available'
|
||||
| 'download-progress'
|
||||
| 'downloaded'
|
||||
| 'error';
|
||||
|
||||
export interface UpdateStatusPayload {
|
||||
status: UpdateStatus;
|
||||
version?: string;
|
||||
percent?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const heliumElectronAPI = {
|
||||
isElectron: true as const,
|
||||
getPlatform: (): Promise<PlatformInfo> => ipcRenderer.invoke('helium:get-platform'),
|
||||
@@ -70,6 +85,16 @@ const heliumElectronAPI = {
|
||||
active: boolean,
|
||||
promptOptions?: StreamingClosePromptOptions,
|
||||
): Promise<boolean> => ipcRenderer.invoke('helium:set-streaming-active', active, promptOptions),
|
||||
checkForUpdates: (): Promise<boolean> => ipcRenderer.invoke('helium:check-for-updates'),
|
||||
installUpdate: (): Promise<boolean> => ipcRenderer.invoke('helium:install-update'),
|
||||
onUpdateStatus: (callback: (payload: UpdateStatusPayload) => void): (() => void) => {
|
||||
const listener = (_event: IpcRendererEvent, payload: UpdateStatusPayload): void => {
|
||||
callback(payload);
|
||||
};
|
||||
|
||||
ipcRenderer.on('helium:update-status', listener);
|
||||
return () => ipcRenderer.removeListener('helium:update-status', listener);
|
||||
},
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld('heliumElectron', heliumElectronAPI);
|
||||
|
||||
@@ -11,6 +11,30 @@
|
||||
"live": "Live",
|
||||
"previewWaiting": "Preview waiting",
|
||||
"previewWaitingDescription": "Your selected screen will appear here once sharing starts.",
|
||||
"streamQuality": "Stream quality",
|
||||
"streamQualityDescription": "Switch quality live without restarting the room.",
|
||||
"streamQualityUpdated": "Stream quality updated.",
|
||||
"active": "Active",
|
||||
"preferSpeed": "Prefer speed",
|
||||
"preferSpeedDescription": "720p at 24 FPS with lower bitrate for weaker networks.",
|
||||
"preferSpeedSummary": "720p · 24 FPS",
|
||||
"balanced": "Balanced",
|
||||
"balancedDescription": "1080p at 30 FPS for everyday sharing.",
|
||||
"balancedSummary": "1080p · 30 FPS",
|
||||
"preferQuality": "Prefer quality",
|
||||
"preferQualityDescription": "Up to 1440p with higher detail for text and design work.",
|
||||
"preferQualitySummary": "1440p · 30 FPS",
|
||||
"highMotion": "High motion",
|
||||
"highMotionDescription": "1080p at 60 FPS for smoother demos and video.",
|
||||
"highMotionSummary": "1080p · 60 FPS",
|
||||
"quality": "Quality",
|
||||
"fps": "FPS",
|
||||
"dataSaver": "Data saver",
|
||||
"standard": "Standard",
|
||||
"sharp": "Sharp",
|
||||
"ultra": "Ultra",
|
||||
"highQualityAudio": "High quality audio",
|
||||
"highQualityAudioDescription": "Stereo 48 kHz audio with less processing.",
|
||||
"presets": "Presets",
|
||||
"effortlessScreensharing": "effortless screensharing powered by webrtc",
|
||||
"hostInstead": "stream instead?",
|
||||
@@ -82,5 +106,16 @@
|
||||
"desktopAppNote": "Includes advanced features like system audio capture and native screen recording permissions.",
|
||||
"androidAppNote": "Install the APK directly on your Android device.",
|
||||
"downloadFromGitHub": "Download from GitHub",
|
||||
"viewSourceCode": "View Source Code"
|
||||
"viewSourceCode": "View Source Code",
|
||||
"updateAvailable": "Update available",
|
||||
"updateAvailableDescription": "Helium {version} is downloading in the background.",
|
||||
"updateAvailableDescriptionWithoutVersion": "A new Helium update is downloading in the background.",
|
||||
"updateDownloading": "Downloading update",
|
||||
"updateDownloadProgress": "{percent}% downloaded",
|
||||
"updateReady": "Update ready",
|
||||
"updateReadyDescription": "Helium {version} is ready. Restart to install it now.",
|
||||
"updateReadyDescriptionWithoutVersion": "A Helium update is ready. Restart to install it now.",
|
||||
"restartToUpdate": "Restart",
|
||||
"updateFailed": "Update failed",
|
||||
"updateFailedDescription": "Helium could not check for or download the update."
|
||||
}
|
||||
|
||||
@@ -11,6 +11,30 @@
|
||||
"live": "En vivo",
|
||||
"previewWaiting": "Vista previa en espera",
|
||||
"previewWaitingDescription": "La pantalla seleccionada aparecerá aquí cuando empieces a compartir.",
|
||||
"streamQuality": "Calidad de transmisión",
|
||||
"streamQualityDescription": "Cambia la calidad en vivo sin reiniciar la sala.",
|
||||
"streamQualityUpdated": "Calidad de transmisión actualizada.",
|
||||
"active": "Activo",
|
||||
"preferSpeed": "Priorizar velocidad",
|
||||
"preferSpeedDescription": "720p a 24 FPS con menor bitrate para redes débiles.",
|
||||
"preferSpeedSummary": "720p · 24 FPS",
|
||||
"balanced": "Equilibrado",
|
||||
"balancedDescription": "1080p a 30 FPS para compartir en el día a día.",
|
||||
"balancedSummary": "1080p · 30 FPS",
|
||||
"preferQuality": "Priorizar calidad",
|
||||
"preferQualityDescription": "Hasta 1440p con más detalle para texto y diseño.",
|
||||
"preferQualitySummary": "1440p · 30 FPS",
|
||||
"highMotion": "Mucho movimiento",
|
||||
"highMotionDescription": "1080p a 60 FPS para demos y video más fluidos.",
|
||||
"highMotionSummary": "1080p · 60 FPS",
|
||||
"quality": "Calidad",
|
||||
"fps": "FPS",
|
||||
"dataSaver": "Ahorro de datos",
|
||||
"standard": "Estándar",
|
||||
"sharp": "Nítido",
|
||||
"ultra": "Ultra",
|
||||
"highQualityAudio": "Audio de alta calidad",
|
||||
"highQualityAudioDescription": "Audio estéreo a 48 kHz con menos procesamiento.",
|
||||
"presets": "Ajustes predefinidos",
|
||||
"effortlessScreensharing": "comparte pantalla sin complicaciones",
|
||||
"hostInstead": "¿prefieres transmitir pantalla?",
|
||||
@@ -82,5 +106,16 @@
|
||||
"desktopAppNote": "Incluye funciones avanzadas como captura de audio del sistema y permisos de grabación de pantalla nativos.",
|
||||
"androidAppNote": "Instala el APK directamente en tu dispositivo Android.",
|
||||
"downloadFromGitHub": "Descargar desde GitHub",
|
||||
"viewSourceCode": "Ver Código Fuente"
|
||||
"viewSourceCode": "Ver Código Fuente",
|
||||
"updateAvailable": "Actualización disponible",
|
||||
"updateAvailableDescription": "Helium {version} se está descargando en segundo plano.",
|
||||
"updateAvailableDescriptionWithoutVersion": "Una nueva actualización de Helium se está descargando en segundo plano.",
|
||||
"updateDownloading": "Descargando actualización",
|
||||
"updateDownloadProgress": "{percent}% descargado",
|
||||
"updateReady": "Actualización lista",
|
||||
"updateReadyDescription": "Helium {version} está listo. Reinicia para instalarlo ahora.",
|
||||
"updateReadyDescriptionWithoutVersion": "Una actualización de Helium está lista. Reinicia para instalarla ahora.",
|
||||
"restartToUpdate": "Reiniciar",
|
||||
"updateFailed": "Error al actualizar",
|
||||
"updateFailedDescription": "Helium no pudo buscar o descargar la actualización."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "helium",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.2",
|
||||
"author": {
|
||||
"email": "helium@srizan.dev",
|
||||
"name": "eth0 software",
|
||||
@@ -46,6 +46,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"electron-updater": "^6.8.9",
|
||||
"lucide-vue-next": "^0.548.0",
|
||||
"marked": "^17.0.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
|
||||
108
pnpm-lock.yaml
generated
108
pnpm-lock.yaml
generated
@@ -44,6 +44,9 @@ importers:
|
||||
drizzle-orm:
|
||||
specifier: ^0.45.1
|
||||
version: 0.45.1(@neondatabase/serverless@1.0.2)(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3)
|
||||
electron-updater:
|
||||
specifier: ^6.8.9
|
||||
version: 6.8.9
|
||||
lucide-vue-next:
|
||||
specifier: ^0.548.0
|
||||
version: 0.548.0(vue@3.5.26(typescript@5.9.3))
|
||||
@@ -2028,36 +2031,42 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-minify/binding-linux-arm64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-DyH/t/zSZHuX4Nn239oBteeMC4OP7B13EyXWX18Qg8aJoZ+lZo90WPGOvhP04zII33jJ7di+vrtAUhsX64lp+A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-minify/binding-linux-riscv64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-CMvzrmOg+Gs44E7TRK/IgrHYp+wwVJxVV8niUrDR2b3SsrCO3NQz5LI+7bM1qDbWnuu5Cl1aiitoMfjRY61dSg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-minify/binding-linux-s390x-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-tZWr6j2s0ddm9MTfWTI3myaAArg9GDy4UgvpF00kMQAjLcGUNhEEQbB9Bd9KtCvDQzaan8HQs0GVWUp+DWrymw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-minify/binding-linux-x64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-0YEKmAIun1bS+Iy5Shx6WOTSj3GuilVuctJjc5/vP8/EMTZ/RI8j0eq0Mu3UFPoT/bMULL3MBXuHuEIXmq7Ddg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-minify/binding-linux-x64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-Ew4QDpEsXoV+pG5+bJpheEy3GH436GBe6ASPB0X27Hh9cQ2gb1NVZ7cY7xJj68+fizwS/PtT8GHoG3uxyH17Pg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-minify/binding-openharmony-arm64@0.102.0':
|
||||
resolution: {integrity: sha512-wYPXS8IOu/sXiP3CGHJNPzZo4hfPAwJKevcFH2syvU2zyqUxym7hx6smfcK/mgJBiX7VchwArdGRwrEQKcBSaQ==}
|
||||
@@ -2189,108 +2198,126 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.93.0':
|
||||
resolution: {integrity: sha512-NoB7BJmwVGrcS/J5XXn362lBsIyeTqZF70rCFij3/XwQ2kcELfGMALY9AUulFYauLTY2AG4vcmctJQxn9Lj85g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-0LzebARTU0ROfD6pDK4h1pFn+09meErCZ0MA2TaW08G72+GNneEsksPufOuI+9AxVSRa+jKE3fu0wavvhZgSkg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-/XWcmglH/VJ4yKAGTLRgPKSSikh3xciNxkwGiURt8dS30b+3pwc4ZZmudMu0tQ3mjSu0o7V9APZLMpbHK8Bp5w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.93.0':
|
||||
resolution: {integrity: sha512-s+nraJJR9SuHsgsr42nbOBpAsaSAE6MhK7HGbz01svLJzDsk3Ylh9cbVUPLaS3gOlTq5WC6VjPBkQuInLo0hvQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.95.0':
|
||||
resolution: {integrity: sha512-Pvi1lGe/G+mJZ3hUojMP/aAHAzHA25AEtVr8/iuz7UV72t/15NOgJYr9kELMUMNjPqpr3vKUgXTFmTtAxp11Qw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-2jtIq4nswvy6xdqv1ndWyvVlaRpS0yqomLCvvHdCFx3pFXo5Aoq4RZ39kgvFWrbAtpeYSYeAGFnwgnqjx9ftdw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.93.0':
|
||||
resolution: {integrity: sha512-oNIQb/7HGxVNeVgtkoqNcDS1hjfxArLDuMI72V+Slp67yfBdxgvfmM2JSWE7kGR5gyiZQeTjRbG89VrRwPDtww==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-pUEVHIOVNDfhk4sTlLhn6mrNENhE4/dAwemxIfqpcSyBlYG0xYZND1F3jjR2yWY6DakXZ6VSuDbtiv1LPNlOLw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-Yp6HX/574mvYryiqj0jNvNTJqo4pdAsNP2LPBTxlDQ1cU3lPd7DUA4MQZadaeLI8+AGB2Pn50mPuPyEwFIxeFg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.93.0':
|
||||
resolution: {integrity: sha512-YyzhzAoq5WpRtAGOngpJUu+4jKagSbknORejmpeW48vu8/+XjrVZFc/1Qe4i72EsPzLorDwCxWVkU8VftpM4iA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-5+olaepHTE3J/+w7g0tr3nocvv5BKilAJnzj4L8tWBCLEZbL6olJcGVoldUO+3cgg1SO1xJywP5BuLhT0mDUDw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-R4b0xZpDRhoNB2XZy0kLTSYm0ZmWeKjTii9fcv1Mk3/SIGPrrglwt4U6zEtwK54Dfi4Bve5JnQYduigR/gyDzw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.93.0':
|
||||
resolution: {integrity: sha512-UMXsE6c0MIlvtqDe5t5K8qwC6HqNb3wmy8zKxONo42dIx0WAhVV9ydG2Xlznt1/RhD6nLLtHVaq4yWJXRjUxcg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-8huzHlK/N98wrnYKxIcYsK8ZGBWomQchu/Mzi6m+CtbhjWOv9DmK0jQ2fUWImtluQVpTwS0uZT06d3g7XIkJrA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-xM5A+03Ti3jvWYZoqaBRS3lusvnvIQjA46Fc9aBE/MHgvKgHSkrGEluLWg/33QEwBwxupkH25Pxc1yu97oZCtg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-musl@0.93.0':
|
||||
resolution: {integrity: sha512-0Vd0yFUq129VW+Cpcj/gJOqub4EMN5hUWnVk8UfAvUZ+lxZBFeXbYNI5483SLwzvw5umzlMmkKpYWw5OTwYFaA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-musl@0.95.0':
|
||||
resolution: {integrity: sha512-bWnrLfGDcx/fab0+UQnFbVFbiykof/btImbYf+cI2pU/1Egb2x+OKSmM5Qt0nEUiIpM5fgJmYXxTopybSZOKYA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-openharmony-arm64@0.102.0':
|
||||
resolution: {integrity: sha512-AieLlsliblyaTFq7Iw9Nc618tgwV02JT4fQ6VIUd/3ZzbluHIHfPjIXa6Sds+04krw5TvCS8lsegtDYAyzcyhg==}
|
||||
@@ -2429,72 +2456,84 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-arm64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-NLdrFuEHlmbiC1M1WESFV4luUcB/84GXi+cbnRXhgMjIW/CThRVJ989eTJy59QivkVlLcJSKTiKiKCt0O6TTlQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-arm64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-I08iWABrN7zakn3wuNIBWY3hALQGsDLPQbZT1mXws7tyiQqJNGe49uS0/O50QhX3KXj+mbRGsmjVXLXGJE1CVQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-transform/binding-linux-arm64-musl@0.95.0':
|
||||
resolution: {integrity: sha512-GL0ffCPW8JlFI0/jeSgCY665yDdojHxA0pbYG+k8oEHOWCYZUZK9AXL+r0oerNEWYJ8CRB+L5Yq87ZtU/YUitw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-transform/binding-linux-riscv64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-9+SYW1ARAF6Oj/82ayoqKRe8SI7O1qvzs3Y0kijvhIqAaaZWcFRjI5DToyWRAbnzTtHlMcSllZLXNYdmxBjFxA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-riscv64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-tbH7LaClSmN3YFVo1UjMSe7D6gkb5f+CMIbj9i873UUZomVRmAjC4ygioObfzM+sj/tX0WoTXx5L1YOfQkHL6Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-s390x-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-HV9nTyQw0TTKYPu+gBhaJBioomiM9O4LcGXi+s5IylCGG6imP0/U13q/9xJnP267QFmiWWqnnSFcv0QAWCyh8A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-s390x-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-8jMqiURWa0iTiPMg7BWaln89VdhhWzNlPyKM90NaFVVhBIKCr2UEhrQWdpBw/E9C8uWf/4VabBEhfPMK+0yS4w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-x64-gnu@0.102.0':
|
||||
resolution: {integrity: sha512-4wcZ08mmdFk8OjsnglyeYGu5PW3TDh87AmcMOi7tZJ3cpJjfzwDfY27KTEUx6G880OpjAiF36OFSPwdKTKgp2g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-x64-gnu@0.95.0':
|
||||
resolution: {integrity: sha512-D5ULJ2uWipsTgfvHIvqmnGkCtB3Fyt2ZN7APRjVO+wLr+HtmnaWddKsLdrRWX/m/6nQ2xQdoQekdJrokYK9LtQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-transform/binding-linux-x64-musl@0.102.0':
|
||||
resolution: {integrity: sha512-rUHZSZBw0FUnUgOhL/Rs7xJz9KjH2eFur/0df6Lwq/isgJc/ggtBtFoZ+y4Fb8ON87a3Y2gS2LT7SEctX0XdPQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-transform/binding-linux-x64-musl@0.95.0':
|
||||
resolution: {integrity: sha512-DmCGU+FzRezES5wVAGVimZGzYIjMOapXbWpxuz8M8p3nMrfdBEQ5/tpwBp2vRlIohhABy4vhHJByl4c64ENCGQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-transform/binding-openharmony-arm64@0.102.0':
|
||||
resolution: {integrity: sha512-98y4tccTQ/pA+r2KA/MEJIZ7J8TNTJ4aCT4rX8kWK4pGOko2YsfY3Ru9DVHlLDwmVj7wP8Z4JNxdBrAXRvK+0g==}
|
||||
@@ -2565,36 +2604,42 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-wasm@2.5.1':
|
||||
resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==}
|
||||
@@ -2866,56 +2911,67 @@ packages:
|
||||
resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.52.5':
|
||||
resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.52.5':
|
||||
resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.52.5':
|
||||
resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.52.5':
|
||||
resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.52.5':
|
||||
resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.52.5':
|
||||
resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
|
||||
@@ -3154,24 +3210,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.16':
|
||||
resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.16':
|
||||
resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.16':
|
||||
resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.16':
|
||||
resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==}
|
||||
@@ -4032,6 +4092,10 @@ packages:
|
||||
resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
builder-util-runtime@9.7.0:
|
||||
resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
builder-util@25.1.7:
|
||||
resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==}
|
||||
|
||||
@@ -4802,6 +4866,9 @@ packages:
|
||||
electron-to-chromium@1.5.267:
|
||||
resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
|
||||
|
||||
electron-updater@6.8.9:
|
||||
resolution: {integrity: sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==}
|
||||
|
||||
electron@33.4.11:
|
||||
resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
@@ -5973,24 +6040,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
@@ -6040,12 +6111,19 @@ packages:
|
||||
lodash.difference@4.5.0:
|
||||
resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
|
||||
|
||||
lodash.escaperegexp@4.1.2:
|
||||
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
|
||||
|
||||
lodash.flatten@4.4.0:
|
||||
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
|
||||
|
||||
lodash.isarguments@3.1.0:
|
||||
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||
|
||||
lodash.isequal@4.5.0:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
@@ -7871,6 +7949,9 @@ packages:
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
tiny-typed-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||
|
||||
tinyexec@1.0.1:
|
||||
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
|
||||
|
||||
@@ -8205,6 +8286,7 @@ packages:
|
||||
|
||||
uuid@8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
validate-npm-package-name@5.0.1:
|
||||
@@ -13275,6 +13357,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
builder-util-runtime@9.7.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
sax: 1.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
builder-util@25.1.7:
|
||||
dependencies:
|
||||
7zip-bin: 5.2.0
|
||||
@@ -14057,6 +14146,19 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.267: {}
|
||||
|
||||
electron-updater@6.8.9:
|
||||
dependencies:
|
||||
builder-util-runtime: 9.7.0
|
||||
fs-extra: 10.1.0
|
||||
js-yaml: 4.1.1
|
||||
lazy-val: 1.0.5
|
||||
lodash.escaperegexp: 4.1.2
|
||||
lodash.isequal: 4.5.0
|
||||
semver: 7.7.3
|
||||
tiny-typed-emitter: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron@33.4.11:
|
||||
dependencies:
|
||||
'@electron/get': 2.0.3
|
||||
@@ -15460,10 +15562,14 @@ snapshots:
|
||||
|
||||
lodash.difference@4.5.0: {}
|
||||
|
||||
lodash.escaperegexp@4.1.2: {}
|
||||
|
||||
lodash.flatten@4.4.0: {}
|
||||
|
||||
lodash.isarguments@3.1.0: {}
|
||||
|
||||
lodash.isequal@4.5.0: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.memoize@4.1.2: {}
|
||||
@@ -17770,6 +17876,8 @@ snapshots:
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tiny-typed-emitter@2.1.0: {}
|
||||
|
||||
tinyexec@1.0.1: {}
|
||||
|
||||
tinyexec@1.0.2: {}
|
||||
|
||||
Reference in New Issue
Block a user