Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e0a08da05 | |||
| 0a3b2c3dc8 | |||
| e5f1e6bbb3 | |||
| 1354ca3f7e |
1
client/app/components.d.ts
vendored
@@ -22,6 +22,7 @@ declare module 'vue' {
|
|||||||
PrimeSelect: typeof import('primevue/select')['default']
|
PrimeSelect: typeof import('primevue/select')['default']
|
||||||
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
|
PrimeTag: typeof import('primevue/tag')['default']
|
||||||
PrimeToast: typeof import('primevue/toast')['default']
|
PrimeToast: typeof import('primevue/toast')['default']
|
||||||
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const inputMuted = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const hasBadges = computed(() => {
|
const hasBadges = computed(() => {
|
||||||
return shareConsumers.value.length > 0
|
return streaming.value
|
||||||
|| premuted.value
|
|| premuted.value
|
||||||
|| inputMuted.value
|
|| inputMuted.value
|
||||||
|| props.client.outputMuted
|
|| props.client.outputMuted
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cursor-pointer hover:outline outline-primary rounded overflow-hidden flex items-center justify-center">
|
<div
|
||||||
<video :srcObject="mediaStream" muted autoplay />
|
class="group cursor-pointer hover:outline outline-primary relative rounded overflow-hidden flex items-center justify-center"
|
||||||
|
@click="watch"
|
||||||
|
>
|
||||||
|
<video :srcObject="stream" muted autoplay />
|
||||||
|
|
||||||
|
<PrimeTag
|
||||||
|
severity="secondary"
|
||||||
|
class="absolute bottom-2 text-sm opacity-70 group-hover:opacity-100"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
{{ isMe ? 'You' : client.displayName }}
|
||||||
|
</PrimeTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Consumer } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
consumer: Consumer
|
client: ChadClient
|
||||||
|
stream: MediaStream
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const mediaStream = computed(() => {
|
const { me } = useClients()
|
||||||
return new MediaStream([props.consumer.raw.track])
|
const fullscreenVideo = useFullscreenVideo()
|
||||||
|
|
||||||
|
const isMe = computed(() => {
|
||||||
|
return props.client.socketId === me.value?.socketId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function watch() {
|
||||||
|
fullscreenVideo.show(props.stream)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ export const useApp = createGlobalState(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const somebodyStreamingVideo = computed(() => {
|
const somebodyStreamingVideo = computed(() => {
|
||||||
return mediasoup.videoConsumers.value.length > 0 || mediasoup.shareConsumers.value.length > 0
|
return !!mediasoup.videoProducer.value
|
||||||
|
|| !!mediasoup.shareProducer.value
|
||||||
|
|| mediasoup.videoConsumers.value.length > 0
|
||||||
|
|| mediasoup.shareConsumers.value.length > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
async function muteInput() {
|
async function muteInput() {
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ export function useClient(socketId: MaybeRef<ChadClient['socketId']>) {
|
|||||||
return Object.values(mediasoup.consumers.value).filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return Object.values(mediasoup.consumers.value).filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
const producers = computed(() => {
|
|
||||||
return mediasoup.producers.value.values().filter(producer => producer.appData.socketId === client.value.socketId).toArray()
|
|
||||||
})
|
|
||||||
|
|
||||||
const audioConsumers = computed(() => {
|
const audioConsumers = computed(() => {
|
||||||
return mediasoup.audioConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return mediasoup.audioConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
@@ -30,6 +26,10 @@ export function useClient(socketId: MaybeRef<ChadClient['socketId']>) {
|
|||||||
return mediasoup.shareConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return mediasoup.shareConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const producers = computed(() => {
|
||||||
|
return Object.values(mediasoup.producers.value).filter(producer => producer.appData.socketId === client.value.socketId)
|
||||||
|
})
|
||||||
|
|
||||||
const streaming = computed(() => {
|
const streaming = computed(() => {
|
||||||
return videoConsumers.value.length > 0 || shareConsumers.value.length > 0
|
return videoConsumers.value.length > 0 || shareConsumers.value.length > 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -384,6 +384,9 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: {
|
video: {
|
||||||
deviceId: { exact: preferences.videoDeviceId.value },
|
deviceId: { exact: preferences.videoDeviceId.value },
|
||||||
|
width: { ideal: 1920 },
|
||||||
|
height: { ideal: 1080 },
|
||||||
|
frameRate: { ideal: 60 },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -395,9 +398,9 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
await createProducer({
|
await createProducer({
|
||||||
track,
|
track,
|
||||||
streamId: 'mic-video',
|
streamId: 'mic-video',
|
||||||
codec: device.value.rtpCapabilities.codecs?.find(
|
// codec: device.value.rtpCapabilities.codecs?.find(
|
||||||
c => c.mimeType.toLowerCase() === 'video/AV1',
|
// c => c.mimeType.toLowerCase() === 'video/AV1',
|
||||||
),
|
// ),
|
||||||
// codecOptions: {
|
// codecOptions: {
|
||||||
// videoGoogleStartBitrate: 1000,
|
// videoGoogleStartBitrate: 1000,
|
||||||
// },
|
// },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-[1fr_1.25fr] gap-2 p-2 h-screen grid-rows-[auto_1fr] max-w-full">
|
<div class="grid grid-cols-[360px_1fr] gap-2 p-2 h-screen grid-rows-[auto_1fr] max-w-full">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
||||||
>
|
>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<PrimeSelectButton
|
<PrimeSelectButton
|
||||||
v-model="activeTab"
|
v-model="activeTab"
|
||||||
:options="tabs"
|
:options="tabs"
|
||||||
data-key="id"
|
option-label="id"
|
||||||
:allow-empty="false"
|
:allow-empty="false"
|
||||||
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,38 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-[1fr_1fr] gap-2">
|
<div class="grid grid-cols-[1fr_1fr] gap-2">
|
||||||
<template v-for="(group, id) in groupedConsumers" :key="id">
|
|
||||||
<GalleryCard
|
<GalleryCard
|
||||||
v-for="consumer in group"
|
v-for="item in gallery"
|
||||||
:key="consumer.id"
|
:key="item.client.socketId"
|
||||||
:consumer="consumer"
|
:client="item.client"
|
||||||
@click="watch(consumer)"
|
:stream="item.stream"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Consumer } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
|
|
||||||
|
interface GalleryItem {
|
||||||
|
client: ChadClient
|
||||||
|
stream: MediaStream
|
||||||
|
}
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Gallery',
|
name: 'Gallery',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { videoConsumers, shareConsumers } = useMediasoup()
|
const { videoProducer, shareProducer } = useMediasoup()
|
||||||
const fullscreenVideo = useFullscreenVideo()
|
const { clients, me } = useClients()
|
||||||
|
|
||||||
const groupedConsumers = computed<Partial<Record<string, Consumer[]>>>(() => {
|
const gallery = computed(() => {
|
||||||
if (fullscreenVideo.visible.value)
|
return clients.value.reduce<GalleryItem[]>(
|
||||||
return {}
|
(acc, client) => {
|
||||||
|
const { streaming, videoConsumers, shareConsumers } = useClient(client.socketId)
|
||||||
|
|
||||||
const consumers = [...videoConsumers.value, ...shareConsumers.value]
|
if (!streaming.value)
|
||||||
|
return acc
|
||||||
|
|
||||||
return Object.groupBy(consumers, (consumer) => {
|
for (const consumer of [...videoConsumers.value, ...shareConsumers.value]) {
|
||||||
return consumer.appData.socketId!
|
acc.push({
|
||||||
|
client,
|
||||||
|
stream: new MediaStream([consumer.raw.track]),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[videoProducer.value, shareProducer.value].reduce<GalleryItem[]>((acc, producer) => {
|
||||||
|
if (!me.value || !producer || !producer.raw.track)
|
||||||
|
return acc
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
client: me.value,
|
||||||
|
stream: new MediaStream([producer.raw.track]),
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, []),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function watch(consumer: Consumer) {
|
watch(gallery, (gallery) => {
|
||||||
fullscreenVideo.show(new MediaStream([consumer.raw.track]))
|
if (gallery.length > 0)
|
||||||
}
|
return
|
||||||
|
|
||||||
|
navigateTo({ name: 'Index' })
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -74,7 +74,14 @@
|
|||||||
<p class="text-sm mb-2 text-center">
|
<p class="text-sm mb-2 text-center">
|
||||||
FPS
|
FPS
|
||||||
</p>
|
</p>
|
||||||
<PrimeSelectButton v-model="shareFps" :options="[5, 30, 60]" fluid size="small" />
|
<PrimeSelectButton
|
||||||
|
v-model="shareFps"
|
||||||
|
:options="shareFpsOptions"
|
||||||
|
fluid
|
||||||
|
size="small"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="isTauri">
|
<template v-if="isTauri">
|
||||||
@@ -152,6 +159,13 @@ const {
|
|||||||
shareFps,
|
shareFps,
|
||||||
} = usePreferences()
|
} = usePreferences()
|
||||||
|
|
||||||
|
const shareFpsOptions = [5, 30, 60].map((value) => {
|
||||||
|
return {
|
||||||
|
label: value.toString(),
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey)
|
const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey)
|
||||||
const setupToggleOutputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleOutputHotkey)
|
const setupToggleOutputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleOutputHotkey)
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 352 B |
BIN
client/src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 663 B |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 717 B |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 534 B |
BIN
client/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 525 B |
BIN
client/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 486 B |
|
After Width: | Height: | Size: 1.1 KiB |
BIN
client/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
client/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 956 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 956 B |
BIN
client/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 318 B |
BIN
client/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
client/src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
client/src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 711 B |
BIN
client/src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 425 B |
BIN
client/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
client/src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
client/src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 939 B |
BIN
client/src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
client/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
client/src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
client/src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
client/src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
3
client/src-tauri/icons/original.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M216 32C220.418 32 224 35.5817 224 40V205.846C224 212.802 215.735 216.444 210.602 211.75L191.292 194.096C189.818 192.748 187.892 192 185.895 192H40C35.5817 192 32 188.418 32 184V40C32 35.5817 35.5817 32 40 32H216ZM129.494 64.6367C121.221 64.6367 113.752 66.4847 107.085 70.1816C100.418 73.8786 95.1307 79.288 91.2217 86.4092C87.3127 93.5303 85.3585 102.212 85.3584 112.454C85.3584 122.666 87.2825 131.333 91.1309 138.454C94.9793 145.575 100.222 151 106.858 154.728C113.525 158.424 121.07 160.272 129.494 160.272C135.888 160.272 141.555 159.303 146.494 157.363C151.464 155.424 155.676 152.819 159.131 149.546C162.585 146.243 165.297 142.591 167.267 138.591C168.565 135.993 169.526 133.37 170.147 130.722C170.684 128.434 168.833 126.397 166.483 126.383L151.51 126.293C149.559 126.281 147.933 127.701 147.275 129.538C146.924 130.519 146.497 131.446 145.994 132.318C144.994 134.076 143.707 135.576 142.131 136.818C140.585 138.03 138.782 138.954 136.722 139.591C134.691 140.227 132.434 140.546 129.949 140.546C125.525 140.546 121.692 139.5 118.449 137.409C115.237 135.288 112.737 132.151 110.949 128C109.192 123.818 108.312 118.636 108.312 112.454C108.313 106.515 109.176 101.454 110.903 97.2725C112.661 93.0907 115.161 89.8937 118.403 87.6816C121.676 85.4696 125.57 84.3633 130.085 84.3633C132.63 84.3633 134.949 84.7268 137.04 85.4541C139.161 86.1511 140.995 87.1667 142.54 88.5C144.085 89.8333 145.327 91.4396 146.267 93.3184C146.721 94.2263 147.101 95.1871 147.406 96.2012C147.986 98.1257 149.635 99.6366 151.645 99.6367H166.447C168.81 99.6367 170.677 97.5927 170.23 95.2725C169.492 91.4407 168.292 87.9403 166.631 84.7725C164.358 80.4392 161.403 76.788 157.767 73.8184C154.13 70.8185 149.919 68.5454 145.131 67C140.343 65.4242 135.131 64.6367 129.494 64.6367Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "chad",
|
"productName": "chad",
|
||||||
"version": "0.2.23",
|
"version": "0.2.27",
|
||||||
"identifier": "xyz.koptilnya.chad",
|
"identifier": "xyz.koptilnya.chad",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../.output/public",
|
"frontendDist": "../.output/public",
|
||||||
@@ -12,12 +12,14 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"maximizable": false,
|
"maximizable": true,
|
||||||
"label": "main",
|
"label": "main",
|
||||||
"title": "Chad",
|
"title": "Chad",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"resizable": false,
|
"minWidth": 800,
|
||||||
|
"minHeight": 600,
|
||||||
|
"resizable": true,
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"center": true,
|
"center": true,
|
||||||
"theme": "Dark",
|
"theme": "Dark",
|
||||||
@@ -40,7 +42,12 @@
|
|||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
]
|
],
|
||||||
|
"windows": {
|
||||||
|
"nsis": {
|
||||||
|
"installerIcon": "icons/icon.ico"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
|||||||