mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-08-14 23:57:27 +00:00
feat: support for adding custom instances
This commit is contained in:
parent
d04fb453f6
commit
4d7390e244
4 changed files with 139 additions and 16 deletions
79
src/components/CustomInstanceModal.vue
Normal file
79
src/components/CustomInstanceModal.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<ModalComponent @close="$emit('close')">
|
||||
<h3 v-t="'titles.custom_instances'" class="my-4 font-bold" />
|
||||
<hr />
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<div v-for="(customInstance, index) in customInstances" :key="customInstance.name">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ customInstance.name }} - {{ customInstance.api_url }}</span>
|
||||
<span
|
||||
class="i-fa6-solid:circle-minus cursor-pointer"
|
||||
@click="removeInstance(customInstance, index)"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<form class="flex flex-col items-end gap-2">
|
||||
<input v-model="name" class="input w-full" type="text" :placeholder="$t('preferences.instance_name')" />
|
||||
<input
|
||||
v-model="url"
|
||||
class="input w-full"
|
||||
type="text"
|
||||
:placeholder="$t('preferences.api_url')"
|
||||
@keyup.enter="addInstance"
|
||||
/>
|
||||
<button v-t="'actions.add'" class="btn w-min" @click.prevent="addInstance" />
|
||||
</form>
|
||||
</div>
|
||||
</ModalComponent>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModalComponent from "./ModalComponent.vue";
|
||||
export default {
|
||||
components: { ModalComponent },
|
||||
emits: ["close"],
|
||||
data() {
|
||||
return {
|
||||
customInstances: [],
|
||||
name: "",
|
||||
url: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.customInstances = this.getCustomInstances();
|
||||
},
|
||||
methods: {
|
||||
async addInstance() {
|
||||
const newInstance = {
|
||||
name: this.name,
|
||||
api_url: this.url,
|
||||
};
|
||||
|
||||
if (!newInstance.name || !newInstance.api_url) {
|
||||
return;
|
||||
}
|
||||
if (!this.isValidInstanceUrl(newInstance.api_url)) {
|
||||
alert(this.$t("actions.invalid_url"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.addCustomInstance(newInstance);
|
||||
this.name = "";
|
||||
this.url = "";
|
||||
},
|
||||
removeInstance(instance, index) {
|
||||
this.customInstances.splice(index, 1);
|
||||
|
||||
this.removeCustomInstance(instance);
|
||||
},
|
||||
isValidInstanceUrl(str) {
|
||||
var a = document.createElement("a");
|
||||
a.href = str;
|
||||
return a.host && a.host != window.location.host;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -313,6 +313,10 @@
|
|||
</select>
|
||||
</label>
|
||||
</template>
|
||||
<div class="pref">
|
||||
<span v-t="'titles.custom_instances'" class="w-max" />
|
||||
<button v-t="'actions.customize'" class="btn" @click="showCustomInstancesModal = true" />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<!-- options that are visible only when logged in -->
|
||||
|
@ -359,7 +363,7 @@
|
|||
<th v-t="'preferences.ssl_score'" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="instance in instances" :key="instance.name">
|
||||
<tbody v-for="instance in publicInstances" :key="instance.name">
|
||||
<tr>
|
||||
<td v-text="instance.name" />
|
||||
<td v-text="instance.locations" />
|
||||
|
@ -387,14 +391,23 @@
|
|||
@close="showConfirmResetPrefsDialog = false"
|
||||
@confirm="resetPreferences()"
|
||||
/>
|
||||
<CustomInstanceModal
|
||||
v-if="showCustomInstancesModal"
|
||||
@close="
|
||||
showCustomInstancesModal = false;
|
||||
fetchInstances();
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CountryMap from "@/utils/CountryMaps/en.json";
|
||||
import ConfirmModal from "./ConfirmModal.vue";
|
||||
import CustomInstanceModal from "./CustomInstanceModal.vue";
|
||||
export default {
|
||||
components: {
|
||||
ConfirmModal,
|
||||
CustomInstanceModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -402,7 +415,8 @@ export default {
|
|||
selectedInstance: null,
|
||||
authInstance: false,
|
||||
selectedAuthInstance: null,
|
||||
instances: [],
|
||||
customInstances: [],
|
||||
publicInstances: [],
|
||||
sponsorBlock: true,
|
||||
skipOptions: new Map([
|
||||
["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
|
||||
|
@ -496,25 +510,21 @@ export default {
|
|||
prefetchLimit: 2,
|
||||
password: null,
|
||||
showConfirmResetPrefsDialog: false,
|
||||
showCustomInstancesModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
instances() {
|
||||
return [...this.publicInstances, ...this.customInstances];
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
document.title = this.$t("titles.preferences") + " - Piped";
|
||||
},
|
||||
async mounted() {
|
||||
if (Object.keys(this.$route.query).length > 0) this.$router.replace({ query: {} });
|
||||
|
||||
this.fetchJson(import.meta.env.VITE_PIPED_INSTANCES).then(resp => {
|
||||
this.instances = resp;
|
||||
if (!this.instances.some(instance => instance.api_url == this.apiUrl()))
|
||||
this.instances.push({
|
||||
name: "Custom Instance",
|
||||
api_url: this.apiUrl(),
|
||||
locations: "Unknown",
|
||||
cdn: false,
|
||||
uptime_30d: 100,
|
||||
});
|
||||
});
|
||||
this.fetchInstances();
|
||||
|
||||
if (this.testLocalStorage) {
|
||||
this.selectedInstance = this.getPreferenceString("instance", import.meta.env.VITE_PIPED_API);
|
||||
|
@ -633,6 +643,21 @@ export default {
|
|||
if (shouldReload) window.location.reload();
|
||||
}
|
||||
},
|
||||
async fetchInstances() {
|
||||
this.customInstances = this.getCustomInstances();
|
||||
|
||||
this.fetchJson(import.meta.env.VITE_PIPED_INSTANCES).then(resp => {
|
||||
this.publicInstances = resp;
|
||||
if (!this.publicInstances.some(instance => instance.api_url == this.apiUrl()))
|
||||
this.publicInstances.push({
|
||||
name: "Selected Instance",
|
||||
api_url: this.apiUrl(),
|
||||
locations: "Unknown",
|
||||
cdn: false,
|
||||
uptime_30d: 100,
|
||||
});
|
||||
});
|
||||
},
|
||||
sslScore(url) {
|
||||
return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest";
|
||||
},
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"albums": "Albums",
|
||||
"bookmarks": "Bookmarks",
|
||||
"channel_groups": "Channel groups",
|
||||
"dearrow": "DeArrow"
|
||||
"dearrow": "DeArrow",
|
||||
"custom_instances": "Custom instances"
|
||||
},
|
||||
"player": {
|
||||
"watch_on": "View on {0}",
|
||||
|
@ -151,7 +152,10 @@
|
|||
"generate_qrcode": "Generate QR Code",
|
||||
"download_frame": "Download frame",
|
||||
"add_to_group": "Add to group",
|
||||
"concurrent_prefetch_limit": "Concurrent Stream Prefetch Limit"
|
||||
"concurrent_prefetch_limit": "Concurrent Stream Prefetch Limit",
|
||||
"customize": "Customize",
|
||||
"invalid_url": "Invalid URL!",
|
||||
"add": "Add"
|
||||
},
|
||||
"comment": {
|
||||
"pinned_by": "Pinned by {author}",
|
||||
|
@ -167,7 +171,8 @@
|
|||
"version": "Version",
|
||||
"up_to_date": "Up to date?",
|
||||
"ssl_score": "SSL Score",
|
||||
"uptime_30d": "Uptime (30d)"
|
||||
"uptime_30d": "Uptime (30d)",
|
||||
"api_url": "Api URL"
|
||||
},
|
||||
"login": {
|
||||
"username": "Username",
|
||||
|
|
14
src/main.js
14
src/main.js
|
@ -553,6 +553,20 @@ const mixin = {
|
|||
|
||||
return !resp.error;
|
||||
},
|
||||
getCustomInstances() {
|
||||
return JSON.parse(window.localStorage.getItem("customInstances")) ?? [];
|
||||
},
|
||||
addCustomInstance(instance) {
|
||||
let customInstances = this.getCustomInstances();
|
||||
customInstances.push(instance);
|
||||
window.localStorage.setItem("customInstances", JSON.stringify(customInstances));
|
||||
},
|
||||
removeCustomInstance(instanceToDelete) {
|
||||
let customInstances = this.getCustomInstances().filter(
|
||||
instance => instance.api_url != instanceToDelete.api_url,
|
||||
);
|
||||
window.localStorage.setItem("customInstances", JSON.stringify(customInstances));
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
authenticated(_this) {
|
||||
|
|
Loading…
Reference in a new issue