Kasper Seweryn 2024-01-24 19:17:52 +01:00
rodzic de232cb749
commit 5647a1072d
41 zmienionych plików z 5126 dodań i 8415 usunięć

2
.envrc 100644
Wyświetl plik

@ -0,0 +1,2 @@
watch_file flake.nix
use flake

3
.gitignore vendored
Wyświetl plik

@ -106,3 +106,6 @@ tsconfig.tsbuildinfo
# Vscode
.vscode/
# Direnv
.direnv/

130
flake.lock 100644
Wyświetl plik

@ -0,0 +1,130 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1705856552,
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1705889935,
"narHash": "sha256-77KPBK5e0ACNzIgJDMuptTtEqKvHBxTO3ksqXHHVO+4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "e36f66bb10b09f5189dc3b1706948eaeb9a1c555",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

91
flake.nix 100644
Wyświetl plik

@ -0,0 +1,91 @@
{
description = "Build tauri desktop application";
inputs = {
nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
flake-utils = {
url = "github:numtide/flake-utils";
inputs.nixpkgs.follows = "nixpkgs";
};
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = inputs: with inputs; flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [
rust-overlay.overlays.default
];
};
lib = nixpkgs.lib;
commonLibraries = with pkgs;[
# Tauri dependencies
webkitgtk_4_1
gtk3
cairo
gdk-pixbuf
glib
dbus
openssl_3
librsvg
libclang
libappindicator
# GStreamer required for audio playback JS-side
gst_all_1.gstreamer
gst_all_1.gst-vaapi
gst_all_1.gst-plugins-bad
gst_all_1.gst-plugins-ugly
gst_all_1.gst-plugins-good
gst_all_1.gst-plugins-base
];
packages = with pkgs; [
# More tauri dependencies
curl
wget
pkg-config
libsoup_3
clang
rustup
# Frontend dependencies
nodejs
corepack
# API dependencies / Frontend scripts
python3
];
in {
devShell = pkgs.mkShell {
buildInputs = commonLibraries ++ packages;
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath commonLibraries;
XDG_DATA_DIRS = let
base = pkgs.lib.concatMapStringsSep ":" (x: "${x}/share") [
pkgs.gnome.adwaita-icon-theme
pkgs.shared-mime-info
];
gsettings-schema = pkgs.lib.concatMapStringsSep ":" (x: "${x}/share/gsettings-schemas/${x.name}") [
pkgs.glib
pkgs.gsettings-desktop-schemas
pkgs.gtk3
];
in "${base}:${gsettings-schema}:$XDG_DATA_DIRS";
GIO_MODULE_DIR = "${pkgs.glib-networking}/lib/gio/modules/";
# Avoid white screen running with Nix
# https://github.com/tauri-apps/tauri/issues/4315#issuecomment-1207755694
WEBKIT_DISABLE_COMPOSITING_MODE = 1;
};
});
}

Wyświetl plik

@ -21,6 +21,7 @@
"@funkwhale/ui": "0.2.2",
"@sentry/tracing": "7.47.0",
"@sentry/vue": "7.47.0",
"@tauri-apps/api": "1.5.3",
"@vue/runtime-core": "3.3.11",
"@vueuse/core": "10.3.0",
"@vueuse/integrations": "10.3.0",
@ -61,6 +62,7 @@
"@faker-js/faker": "8.4.1",
"@intlify/eslint-plugin-vue-i18n": "2.0.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@tauri-apps/cli": "2.0.0-alpha.21",
"@types/diff": "5.0.9",
"@types/dompurify": "3.0.5",
"@types/jquery": "3.5.29",

Wyświetl plik

@ -14,7 +14,6 @@ const ChannelUploadModal = defineAsyncComponent(() => import('~/components/chann
const PlaylistModal = defineAsyncComponent(() => import('~/components/playlists/PlaylistModal.vue'))
const FilterModal = defineAsyncComponent(() => import('~/components/moderation/FilterModal.vue'))
const ReportModal = defineAsyncComponent(() => import('~/components/moderation/ReportModal.vue'))
const SetInstanceModal = defineAsyncComponent(() => import('~/components/SetInstanceModal.vue'))
const ServiceMessages = defineAsyncComponent(() => import('~/components/ServiceMessages.vue'))
const ShortcutsModal = defineAsyncComponent(() => import('~/components/ShortcutsModal.vue'))
const AudioPlayer = defineAsyncComponent(() => import('~/components/audio/Player.vue'))
@ -72,7 +71,6 @@ const [showShortcutsModal, toggleShortcutsModal] = useToggle(false)
onKeyboardShortcut('h', () => toggleShortcutsModal())
const { width } = useWindowSize()
const showSetInstanceModal = ref(false)
// Fetch user data on startup
// NOTE: We're not checking if we're authenticated in the store,
@ -99,10 +97,8 @@ store.dispatch('auth/fetchUser')
<sidebar
:width="width"
@show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal"
@show:shortcuts-modal="toggleShortcutsModal"
/>
<set-instance-modal v-model:show="showSetInstanceModal" />
<service-messages />
<transition name="queue">
<queue v-show="store.state.ui.queueFocused" />

Wyświetl plik

@ -1,160 +0,0 @@
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useVModel } from '@vueuse/core'
import { useStore } from '~/store'
import { uniq } from 'lodash-es'
import axios from 'axios'
import SemanticModal from '~/components/semantic/Modal.vue'
interface Events {
(e: 'update:show', show: boolean): void
}
interface Props {
show: boolean
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const show = useVModel(props, 'show', emit)
const instanceUrl = ref('')
const store = useStore()
const suggestedInstances = computed(() => {
const serverUrl = store.state.instance.frontSettings.defaultServerUrl
return uniq([
store.state.instance.instanceUrl,
...store.state.instance.knownInstances,
serverUrl.endsWith('/') ? serverUrl : serverUrl + '/',
store.getters['instance/defaultInstance']
])
})
watch(() => store.state.instance.instanceUrl, () => store.dispatch('instance/fetchSettings'))
const { t } = useI18n()
const isError = ref(false)
const isLoading = ref(false)
const checkAndSwitch = async (url: string) => {
isError.value = false
isLoading.value = true
try {
const instanceUrl = new URL(url.startsWith('https://') || url.startsWith('http://') ? url : `https://${url}`).origin
await axios.get(instanceUrl + '/api/v1/instance/nodeinfo/2.0/')
show.value = false
store.commit('ui/addMessage', {
content: t('components.SetInstanceModal.message.newUrl', { url: instanceUrl }),
date: new Date()
})
await nextTick()
store.dispatch('instance/setUrl', instanceUrl)
} catch (error) {
isError.value = true
}
isLoading.value = false
}
</script>
<template>
<semantic-modal
v-model:show="show"
@update:show="isError = false"
>
<h3 class="header">
{{ $t('components.SetInstanceModal.header.chooseInstance') }}
</h3>
<div class="scrolling content">
<div
v-if="isError"
role="alert"
class="ui negative message"
>
<h4 class="header">
{{ $t('components.SetInstanceModal.header.failure') }}
</h4>
<ul class="list">
<li>
{{ $t('components.SetInstanceModal.help.serverDown') }}
</li>
<li>
{{ $t('components.SetInstanceModal.help.notFunkwhaleServer') }}
</li>
</ul>
</div>
<form
class="ui form"
@submit.prevent="checkAndSwitch(instanceUrl)"
>
<p
v-if="$store.state.instance.instanceUrl"
class="description"
>
<i18n-t keypath="components.SetInstanceModal.message.currentConnection">
<a
:href="$store.state.instance.instanceUrl"
target="_blank"
>
{{ $store.getters['instance/domain'] }}
<i class="external icon" />
</a>
</i18n-t>
{{ $t('', {url: $store.state.instance.instanceUrl, hostname: $store.getters['instance/domain']}) }}
</p>
<p v-else>
{{ $t('components.SetInstanceModal.help.selectPod') }}
</p>
<div class="field">
<label for="instance-picker">{{ $t('components.SetInstanceModal.label.url') }}</label>
<div class="ui action input">
<input
id="instance-picker"
v-model="instanceUrl"
type="text"
placeholder="https://funkwhale.server"
>
<button
type="submit"
:class="['ui', 'icon', {loading: isLoading}, 'button']"
>
{{ $t('components.SetInstanceModal.button.submit') }}
</button>
</div>
</div>
</form>
<div class="ui hidden divider" />
<form
class="ui form"
@submit.prevent=""
>
<div class="field">
<h4>
{{ $t('components.SetInstanceModal.header.suggestions') }}
</h4>
<button
v-for="(url, key) in suggestedInstances"
:key="key"
class="ui basic button"
@click="checkAndSwitch(url)"
>
{{ url }}
</button>
</div>
</form>
</div>
<div class="actions">
<button class="ui basic cancel button">
{{ $t('components.SetInstanceModal.button.cancel') }}
</button>
</div>
</semantic-modal>
</template>

Wyświetl plik

@ -18,15 +18,10 @@ import Logo from '~/components/Logo.vue'
import useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme'
interface Events {
(e: 'show:set-instance-modal'): void
}
interface Props {
width: number
}
const emit = defineEmits<Events>()
defineProps<Props>()
const store = useStore()
@ -543,12 +538,12 @@ onMounted(() => {
v-if="!isProduction"
class="item"
>
<a
role="button"
href=""
<router-link
to="/instance-chooser"
class="link item"
@click.prevent="emit('show:set-instance-modal')"
>{{ $t('components.Sidebar.link.switchInstance') }}</a>
>
{{ $t('components.Sidebar.link.switchInstance') }}
</router-link>
</div>
</nav>
</section>

Wyświetl plik

@ -9,6 +9,12 @@ const { t } = i18n.global
const logger = useLogger()
export const install: InitModule = ({ store }) => {
// NOTE: Return early if we're not running in a browser
if ('TAURI_PLATFORM' in import.meta.env) {
logger.info('Tauri detected, skipping service worker registration')
// return
}
const updateSW = registerSW({
onRegisterError (error) {
const importStatementsSupported = navigator.userAgent.includes('Chrome')

Wyświetl plik

@ -3,6 +3,7 @@ import type { Permission } from '~/store/auth'
import useLogger from '~/composables/useLogger'
import store from '~/store'
import { TAURI_DEFAULT_INSTANCE_URL } from '~/store/instance'
const logger = useLogger()
@ -17,7 +18,6 @@ export const hasPermissions = (permission: Permission) => (to: RouteLocationNorm
export const requireLoggedIn = (fallbackLocation?: RouteLocationNamedRaw) => (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (store.state.auth.authenticated) return next()
logger.debug('!', to)
return next(fallbackLocation ?? { name: 'login', query: { next: to.fullPath } })
}
@ -25,3 +25,14 @@ export const requireLoggedOut = (fallbackLocation: RouteLocationNamedRaw) => (to
if (!store.state.auth.authenticated) return next()
return next(fallbackLocation)
}
export const forceInstanceChooser = (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (to.path === '/instance-chooser') return next()
// Force instance chooser if unset by tauri
if (store.getters['instance/url'].href === TAURI_DEFAULT_INSTANCE_URL) {
return next(`/instance-chooser?next=${encodeURIComponent(to.fullPath)}`)
}
return next()
}

Wyświetl plik

@ -1,7 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
import { forceInstanceChooser } from './guards'
import routes from './routes'
export default createRouter({
const router = createRouter({
history: createWebHistory(import.meta.env.VUE_APP_ROUTER_BASE_URL as string ?? '/'),
linkActiveClass: 'active',
routes,
@ -22,3 +23,9 @@ export default createRouter({
})
}
})
router.beforeEach((to, from, next) => {
return forceInstanceChooser(to, from, next)
})
export default router

Wyświetl plik

@ -19,6 +19,11 @@ export default [
return next()
}
},
{
path: '/instance-chooser',
name: 'instance-chooser',
component: () => import('~/views/ChooseInstance.vue'),
},
{
path: '/index.html',
redirect: to => {

Wyświetl plik

@ -122,16 +122,27 @@ interface Settings {
const logger = useLogger()
// Use some arbitrary url that will trigger the instance chooser, this needs to be a valid url
export const TAURI_DEFAULT_INSTANCE_URL = 'tauri://force-instance-chooser/'
// We have several way to guess the API server url. By order of precedence:
// 1. use the url provided in settings.json, if any
// 2. use the url specified when building via VUE_APP_INSTANCE_URL
// 3. use the current url
let DEFAULT_INSTANCE_URL = `${location.origin}/`
try {
DEFAULT_INSTANCE_URL = new URL(import.meta.env.VUE_APP_INSTANCE_URL as string).href
} catch (e) {
logger.warn('Invalid VUE_APP_INSTANCE_URL, falling back to current url', e)
}
// 1. force instance chooser, if in tauri app
// 2. use the url provided in settings.json, if any
// 3. use the url specified when building via VUE_APP_INSTANCE_URL
// 4. use the current url
const DEFAULT_INSTANCE_URL = (() => {
if ('TAURI_PLATFORM' in import.meta.env) {
return TAURI_DEFAULT_INSTANCE_URL
}
try {
return new URL(import.meta.env.VUE_APP_INSTANCE_URL as string).href
} catch (e) {
logger.warn('Invalid VUE_APP_INSTANCE_URL, falling back to current url', e)
}
return `${location.origin}/`
})()
const store: Module<State, RootState> = {
namespaced: true,

Wyświetl plik

@ -0,0 +1,189 @@
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import { TAURI_DEFAULT_INSTANCE_URL } from '~/store/instance'
import { uniq } from 'lodash-es'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
const instanceUrl = ref('')
const store = useStore()
const suggestedInstances = computed(() => {
const serverUrl = store.state.instance.frontSettings.defaultServerUrl
return uniq([
store.state.instance.instanceUrl,
...store.state.instance.knownInstances,
serverUrl.endsWith('/') ? serverUrl : serverUrl + '/',
store.getters['instance/defaultInstance']
]).filter(url => url !== TAURI_DEFAULT_INSTANCE_URL)
})
watch(() => store.state.instance.instanceUrl, () => store.dispatch('instance/fetchSettings'))
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const isError = ref(false)
const isLoading = ref(false)
const checkAndSwitch = async (url: string) => {
isError.value = false
isLoading.value = true
try {
const instanceUrl = new URL(url.startsWith('https://') || url.startsWith('http://') ? url : `https://${url}`).origin
await axios.get(instanceUrl + '/api/v1/instance/nodeinfo/2.0/')
store.commit('ui/addMessage', {
content: t('components.SetInstanceModal.message.newUrl', { url: instanceUrl }),
date: new Date()
})
await nextTick()
await store.dispatch('instance/setUrl', instanceUrl)
router.push(route.query.next as string || '/')
} catch (error) {
isError.value = true
}
isLoading.value = false
}
const isTauriInstance = computed(() => store.getters['instance/url'].href === TAURI_DEFAULT_INSTANCE_URL)
</script>
<template>
<div class="instance-chooser">
<img src="../assets/logo/logo-full-500.png" />
<div class="card">
<h3 class="header">
{{ t('components.SetInstanceModal.header.chooseInstance') }}
</h3>
<div class="scrolling content">
<div
v-if="isError"
role="alert"
class="ui negative message"
>
<h4 class="header">
{{ t('components.SetInstanceModal.header.failure') }}
</h4>
<ul class="list">
<li>
{{ t('components.SetInstanceModal.help.serverDown') }}
</li>
<li>
{{ t('components.SetInstanceModal.help.notFunkwhaleServer') }}
</li>
</ul>
</div>
<form
class="ui form"
@submit.prevent="checkAndSwitch(instanceUrl)"
>
<p
v-if="store.state.instance.instanceUrl && !isTauriInstance"
class="description"
>
<i18n-t keypath="components.SetInstanceModal.message.currentConnection">
<a
:href="store.state.instance.instanceUrl"
target="_blank"
>
{{ store.getters['instance/domain'] }}
<i class="external icon" />
</a>
</i18n-t>
{{ t('', {url: store.state.instance.instanceUrl, hostname: store.getters['instance/domain']}) }}
</p>
<p v-else class="description">
{{ t('components.SetInstanceModal.help.selectPod') }}
</p>
<div class="field">
<label for="instance-picker">{{ t('components.SetInstanceModal.label.url') }}</label>
<div class="ui action input">
<input
id="instance-picker"
v-model="instanceUrl"
type="text"
placeholder="https://funkwhale.server"
>
<button
type="submit"
:class="['ui', 'icon', {loading: isLoading}, 'button']"
>
{{ t('components.SetInstanceModal.button.submit') }}
</button>
</div>
</div>
</form>
<div class="ui hidden divider" />
<form
v-if="suggestedInstances.length > 0"
class="ui form"
@submit.prevent=""
>
<div class="field">
<h4>
{{ t('components.SetInstanceModal.header.suggestions') }}
</h4>
<div class="h-scroll">
<button
v-for="(url, key) in suggestedInstances"
:key="key"
class="ui basic button"
@click="checkAndSwitch(url)"
>
{{ url }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.instance-chooser {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 99000;
background: var(--main-background);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
> .card {
margin-top: 2rem;
max-width: 30rem;
width: 100%;
background: #fff;
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
.h-scroll {
max-width: 100%;
overflow-x: auto;
display: flex;
padding: 0 6px 6px;
}
}
}
</style>

3
front/tauri/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

4533
front/tauri/Cargo.lock wygenerowano 100644

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,30 @@
[package]
name = "funkwhale"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "funkwhale"
edition = "2021"
rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "funkwhale_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.0.0-alpha", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-alpha", features = [] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]

Wyświetl plik

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 8.4 KiB

Wyświetl plik

@ -0,0 +1,3 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="32" xmlns="http://www.w3.org/2000/svg" height="32" id="screenshot-7b8093c9-7997-11ed-b471-13134daf88e2" viewBox="-0 0 32 32" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0px" id="shape-7b8093c9-7997-11ed-b471-13134daf88e2" style="fill: rgb(0, 0, 0);" ry="0" rx="0" y="0px" version="1.1"><g id="shape-7b80e1e0-7997-11ed-b471-13134daf88e2"><style type="text/css" rx="0" ry="0" style="fill: rgb(0, 0, 0);">.st0{fill:#FFFFFF;}
.st1{fill:#009FE3;}
.st2{fill:#3C3C3B;}</style></g><g id="shape-7b80e1e1-7997-11ed-b471-13134daf88e2"><g class="fills" id="fills-7b80e1e1-7997-11ed-b471-13134daf88e2"><ellipse class="st0" rx="16" ry="16" cx="15.999999999999886" cy="16" transform="matrix(1.000000, -0.000000, 0.000000, 1.000000, -0.000000, 0.000000)" style="fill: rgb(0, 159, 227); fill-opacity: 1;"/></g><g id="strokes-7b80e1e1-7997-11ed-b471-13134daf88e2" class="strokes"><g class="inner-stroke-shape" transform="matrix(1.000000, -0.000000, 0.000000, 1.000000, -0.000000, 0.000000)"><defs><clipPath id="inner-stroke-rumext-id-3-7b80e1e1-7997-11ed-b471-13134daf88e2-0"><use href="#stroke-shape-rumext-id-3-7b80e1e1-7997-11ed-b471-13134daf88e2-0"/></clipPath><ellipse class="st0" rx="16" ry="16" cx="15.999999999999886" cy="16" id="stroke-shape-rumext-id-3-7b80e1e1-7997-11ed-b471-13134daf88e2-0" style="fill: none; stroke-width: 4; stroke: rgb(0, 0, 0); stroke-opacity: 0;"/></defs><use href="#stroke-shape-rumext-id-3-7b80e1e1-7997-11ed-b471-13134daf88e2-0" clip-path="url('#inner-stroke-rumext-id-3-7b80e1e1-7997-11ed-b471-13134daf88e2-0')"/></g></g></g><g id="shape-7b80e1e2-7997-11ed-b471-13134daf88e2" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-7b80e1e4-7997-11ed-b471-13134daf88e2" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-7b80e1e5-7997-11ed-b471-13134daf88e2" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-7b80e1e7-7997-11ed-b471-13134daf88e2"><g class="fills" id="fills-7b80e1e7-7997-11ed-b471-13134daf88e2"><path class="st1" rx="0" ry="0" d="M16.000,22.000C19.308,22.000,22.000,19.444,22.000,16.302C22.000,16.142,21.850,16.000,21.682,16.000L19.421,16.000C19.252,16.000,19.103,16.142,19.103,16.302C19.103,17.917,17.720,19.249,16.000,19.249C14.299,19.249,12.897,17.935,12.897,16.302C12.897,16.142,12.748,16.000,12.579,16.000L10.318,16.000C10.150,16.000,10.000,16.142,10.000,16.302C10.000,19.462,12.692,22.000,16.000,22.000ZL16.000,22.000Z" style="fill: rgb(253, 253, 255); fill-opacity: 1;"/></g></g><g id="shape-7b80e1e8-7997-11ed-b471-13134daf88e2"><g class="fills" id="fills-7b80e1e8-7997-11ed-b471-13134daf88e2"><path class="st1" rx="0" ry="0" d="M16.000,28.000C22.607,28.000,28.000,22.750,28.000,16.319C28.000,16.150,27.846,16.000,27.673,16.000L25.342,16.000C25.169,16.000,25.014,16.150,25.014,16.319C25.014,21.175,20.970,25.112,15.981,25.112C10.992,25.112,6.947,21.175,6.947,16.319C6.947,16.150,6.793,16.000,6.620,16.000L4.327,16.000C4.154,16.000,4.000,16.150,4.000,16.319C3.961,22.750,9.355,28.000,16.000,28.000ZZ" style="fill: rgb(253, 253, 255); fill-opacity: 1;"/></g></g></g><g id="shape-7b80e1e6-7997-11ed-b471-13134daf88e2"><g class="fills" id="fills-7b80e1e6-7997-11ed-b471-13134daf88e2"><path class="st2" rx="0" ry="0" d="M10.737,10.262C11.528,10.674,12.382,10.751,13.147,11.201C13.644,11.497,13.963,11.819,14.269,12.308C14.753,13.041,14.728,13.967,14.728,13.967L14.792,14.984C14.792,14.984,15.174,16.000,16.028,16.000C16.934,16.000,17.265,14.984,17.265,14.984L17.329,13.967C17.329,13.967,17.303,13.054,17.788,12.308C18.094,11.819,18.400,11.471,18.910,11.201C19.675,10.751,20.529,10.674,21.319,10.262C22.110,9.850,22.875,9.323,23.397,8.590C23.920,7.856,24.162,6.878,23.882,6.017C22.378,5.939,20.644,6.119,19.318,6.840C17.469,7.831,16.347,7.483,16.016,8.963L15.990,8.963C15.659,7.470,14.549,7.831,12.688,6.840C11.362,6.119,9.628,5.939,8.124,6.017C7.830,6.878,8.085,7.843,8.608,8.590C9.169,9.336,9.947,9.863,10.737,10.262ZZ" style="fill: rgb(253, 253, 255); fill-opacity: 1;"/></g></g></g></g></g></svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 4.0 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 12 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 31 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.3 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 9.9 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 14 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 14 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 36 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.1 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 42 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.6 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 6.4 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 8.1 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 4.2 KiB

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 47 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 93 KiB

Wyświetl plik

@ -0,0 +1,6 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

Wyświetl plik

@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
funkwhale_lib::run();
}

Wyświetl plik

@ -0,0 +1,68 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "yarn build",
"beforeDevCommand": "yarn dev",
"devPath": "http://localhost:8080",
"distDir": "../dist"
},
"package": {
"productName": "Funkwhale",
"version": "0.1.0"
},
"plugins": {
"updater": {
"endpoints": []
}
},
"tauri": {
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "audio.funkwhale.desktop",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"updater": {
"active": false
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "Funkwhale",
"width": 800
}
]
}
}

Wyświetl plik

@ -13,7 +13,7 @@ const port = +(process.env.VUE_PORT ?? 8080)
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
envPrefix: ['VUE_', 'FUNKWHALE_SENTRY_'],
envPrefix: ['VUE_', 'TAURI_', 'FUNKWHALE_SENTRY_'],
plugins: [
// https://vue-macros.sxzz.moe/
VueMacros({

Plik diff jest za duży Load Diff