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>
 | 
			
		||||
        </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…
	
	Add table
		Add a link
		
	
		Reference in a new issue