mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Merge pull request #3468 from Bnyro/custom-instances
feat: support for adding custom instances
This commit is contained in:
		
						commit
						89d9f30e41
					
				
					 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>
 | 
					            </select>
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
    </template>
 | 
					    </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 />
 | 
					    <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- options that are visible only when logged in -->
 | 
					    <!-- options that are visible only when logged in -->
 | 
				
			||||||
| 
						 | 
					@ -359,7 +363,7 @@
 | 
				
			||||||
                <th v-t="'preferences.ssl_score'" />
 | 
					                <th v-t="'preferences.ssl_score'" />
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody v-for="instance in instances" :key="instance.name">
 | 
					        <tbody v-for="instance in publicInstances" :key="instance.name">
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td v-text="instance.name" />
 | 
					                <td v-text="instance.name" />
 | 
				
			||||||
                <td v-text="instance.locations" />
 | 
					                <td v-text="instance.locations" />
 | 
				
			||||||
| 
						 | 
					@ -387,14 +391,23 @@
 | 
				
			||||||
        @close="showConfirmResetPrefsDialog = false"
 | 
					        @close="showConfirmResetPrefsDialog = false"
 | 
				
			||||||
        @confirm="resetPreferences()"
 | 
					        @confirm="resetPreferences()"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					    <CustomInstanceModal
 | 
				
			||||||
 | 
					        v-if="showCustomInstancesModal"
 | 
				
			||||||
 | 
					        @close="
 | 
				
			||||||
 | 
					            showCustomInstancesModal = false;
 | 
				
			||||||
 | 
					            fetchInstances();
 | 
				
			||||||
 | 
					        "
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import CountryMap from "@/utils/CountryMaps/en.json";
 | 
					import CountryMap from "@/utils/CountryMaps/en.json";
 | 
				
			||||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
					import ConfirmModal from "./ConfirmModal.vue";
 | 
				
			||||||
 | 
					import CustomInstanceModal from "./CustomInstanceModal.vue";
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    components: {
 | 
					    components: {
 | 
				
			||||||
        ConfirmModal,
 | 
					        ConfirmModal,
 | 
				
			||||||
 | 
					        CustomInstanceModal,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
| 
						 | 
					@ -402,7 +415,8 @@ export default {
 | 
				
			||||||
            selectedInstance: null,
 | 
					            selectedInstance: null,
 | 
				
			||||||
            authInstance: false,
 | 
					            authInstance: false,
 | 
				
			||||||
            selectedAuthInstance: null,
 | 
					            selectedAuthInstance: null,
 | 
				
			||||||
            instances: [],
 | 
					            customInstances: [],
 | 
				
			||||||
 | 
					            publicInstances: [],
 | 
				
			||||||
            sponsorBlock: true,
 | 
					            sponsorBlock: true,
 | 
				
			||||||
            skipOptions: new Map([
 | 
					            skipOptions: new Map([
 | 
				
			||||||
                ["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
 | 
					                ["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
 | 
				
			||||||
| 
						 | 
					@ -496,25 +510,21 @@ export default {
 | 
				
			||||||
            prefetchLimit: 2,
 | 
					            prefetchLimit: 2,
 | 
				
			||||||
            password: null,
 | 
					            password: null,
 | 
				
			||||||
            showConfirmResetPrefsDialog: false,
 | 
					            showConfirmResetPrefsDialog: false,
 | 
				
			||||||
 | 
					            showCustomInstancesModal: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    computed: {
 | 
				
			||||||
 | 
					        instances() {
 | 
				
			||||||
 | 
					            return [...this.publicInstances, ...this.customInstances];
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = this.$t("titles.preferences") + " - Piped";
 | 
					        document.title = this.$t("titles.preferences") + " - Piped";
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    async mounted() {
 | 
					    async mounted() {
 | 
				
			||||||
        if (Object.keys(this.$route.query).length > 0) this.$router.replace({ query: {} });
 | 
					        if (Object.keys(this.$route.query).length > 0) this.$router.replace({ query: {} });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.fetchJson(import.meta.env.VITE_PIPED_INSTANCES).then(resp => {
 | 
					        this.fetchInstances();
 | 
				
			||||||
            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,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.testLocalStorage) {
 | 
					        if (this.testLocalStorage) {
 | 
				
			||||||
            this.selectedInstance = this.getPreferenceString("instance", import.meta.env.VITE_PIPED_API);
 | 
					            this.selectedInstance = this.getPreferenceString("instance", import.meta.env.VITE_PIPED_API);
 | 
				
			||||||
| 
						 | 
					@ -633,6 +643,21 @@ export default {
 | 
				
			||||||
                if (shouldReload) window.location.reload();
 | 
					                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) {
 | 
					        sslScore(url) {
 | 
				
			||||||
            return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest";
 | 
					            return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest";
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,8 @@
 | 
				
			||||||
        "albums": "Albums",
 | 
					        "albums": "Albums",
 | 
				
			||||||
        "bookmarks": "Bookmarks",
 | 
					        "bookmarks": "Bookmarks",
 | 
				
			||||||
        "channel_groups": "Channel groups",
 | 
					        "channel_groups": "Channel groups",
 | 
				
			||||||
        "dearrow": "DeArrow"
 | 
					        "dearrow": "DeArrow",
 | 
				
			||||||
 | 
					        "custom_instances": "Custom instances"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "View on {0}",
 | 
					        "watch_on": "View on {0}",
 | 
				
			||||||
| 
						 | 
					@ -151,7 +152,10 @@
 | 
				
			||||||
        "generate_qrcode": "Generate QR Code",
 | 
					        "generate_qrcode": "Generate QR Code",
 | 
				
			||||||
        "download_frame": "Download frame",
 | 
					        "download_frame": "Download frame",
 | 
				
			||||||
        "add_to_group": "Add to group",
 | 
					        "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": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Pinned by {author}",
 | 
					        "pinned_by": "Pinned by {author}",
 | 
				
			||||||
| 
						 | 
					@ -167,7 +171,8 @@
 | 
				
			||||||
        "version": "Version",
 | 
					        "version": "Version",
 | 
				
			||||||
        "up_to_date": "Up to date?",
 | 
					        "up_to_date": "Up to date?",
 | 
				
			||||||
        "ssl_score": "SSL Score",
 | 
					        "ssl_score": "SSL Score",
 | 
				
			||||||
        "uptime_30d": "Uptime (30d)"
 | 
					        "uptime_30d": "Uptime (30d)",
 | 
				
			||||||
 | 
					        "api_url": "Api URL"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Username",
 | 
					        "username": "Username",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/main.js
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								src/main.js
									
										
									
									
									
								
							| 
						 | 
					@ -553,6 +553,20 @@ const mixin = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return !resp.error;
 | 
					            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: {
 | 
					    computed: {
 | 
				
			||||||
        authenticated(_this) {
 | 
					        authenticated(_this) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue