feat: add tauri
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
|
@ -106,3 +106,6 @@ tsconfig.tsbuildinfo
|
|||
|
||||
# Vscode
|
||||
.vscode/
|
||||
|
||||
# Direnv
|
||||
.direnv/
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,11 @@ export default [
|
|||
return next()
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/instance-chooser',
|
||||
name: 'instance-chooser',
|
||||
component: () => import('~/views/ChooseInstance.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index.html',
|
||||
redirect: to => {
|
||||
|
|
|
@ -122,17 +122,28 @@ 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}/`
|
||||
// 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 {
|
||||
DEFAULT_INSTANCE_URL = new URL(import.meta.env.VUE_APP_INSTANCE_URL as string).href
|
||||
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,
|
||||
state: {
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
|
@ -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" ]
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
After Width: | Height: | Size: 8.4 KiB |
|
@ -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>
|
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 93 KiB |
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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({
|
||||
|
|