mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Subscription groups
This commit is contained in:
		
							parent
							
								
									5e955b2286
								
							
						
					
					
						commit
						c217d5e4e3
					
				
					 6 changed files with 200 additions and 8 deletions
				
			
		| 
						 | 
					@ -55,7 +55,7 @@ export default {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ("indexedDB" in window) {
 | 
					        if ("indexedDB" in window) {
 | 
				
			||||||
            const request = indexedDB.open("piped-db", 3);
 | 
					            const request = indexedDB.open("piped-db", 4);
 | 
				
			||||||
            request.onupgradeneeded = ev => {
 | 
					            request.onupgradeneeded = ev => {
 | 
				
			||||||
                const db = request.result;
 | 
					                const db = request.result;
 | 
				
			||||||
                console.log("Upgrading object store.");
 | 
					                console.log("Upgrading object store.");
 | 
				
			||||||
| 
						 | 
					@ -73,6 +73,10 @@ export default {
 | 
				
			||||||
                    store.createIndex("playlist_id_idx", "playlistId", { unique: true });
 | 
					                    store.createIndex("playlist_id_idx", "playlistId", { unique: true });
 | 
				
			||||||
                    store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
 | 
					                    store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                if (!db.objectStoreNames.contains("channel_groups")) {
 | 
				
			||||||
 | 
					                    const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
 | 
				
			||||||
 | 
					                    store.createIndex("groupName", "groupName", { unique: true });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            request.onsuccess = e => {
 | 
					            request.onsuccess = e => {
 | 
				
			||||||
                window.db = e.target.result;
 | 
					                window.db = e.target.result;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/components/DefaultValueCheckbox.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/DefaultValueCheckbox.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					// Wrapper around v-model to allow default values without requiring to use a v-model inside the calling component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        defaultValue: Boolean,
 | 
				
			||||||
 | 
					        callback: Function,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    data() {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            value: false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					        this.value = this.defaultValue;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <input type="checkbox" class="checkbox" v-model="value" @change="callback()" />
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,19 @@
 | 
				
			||||||
        <option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`video.${filter}`" />
 | 
					        <option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`video.${filter}`" />
 | 
				
			||||||
    </select>
 | 
					    </select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <label for="group-selector" class="ml-10 mr-2">
 | 
				
			||||||
 | 
					        <strong v-text="`${$t('titles.channel_groups')}:`" />
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <select id="group-selector" v-model="selectedGroupName" default="" class="select w-auto">
 | 
				
			||||||
 | 
					        <option value="" v-t="`video.all`" />
 | 
				
			||||||
 | 
					        <option
 | 
				
			||||||
 | 
					            v-for="group in channelGroups"
 | 
				
			||||||
 | 
					            :key="group.groupName"
 | 
				
			||||||
 | 
					            :value="group.groupName"
 | 
				
			||||||
 | 
					            v-text="group.groupName"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <span class="md:float-right">
 | 
					    <span class="md:float-right">
 | 
				
			||||||
        <SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
 | 
					        <SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
| 
						 | 
					@ -25,7 +38,7 @@
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <LoadingIndicatorPage :show-content="videosStore != null" class="video-grid">
 | 
					    <LoadingIndicatorPage :show-content="videosStore != null" class="video-grid">
 | 
				
			||||||
        <template v-for="video in videos" :key="video.url">
 | 
					        <template v-for="video in filteredVideos" :key="video.url">
 | 
				
			||||||
            <VideoItem v-if="shouldShowVideo(video)" :is-feed="true" :item="video" />
 | 
					            <VideoItem v-if="shouldShowVideo(video)" :is-feed="true" :item="video" />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
    </LoadingIndicatorPage>
 | 
					    </LoadingIndicatorPage>
 | 
				
			||||||
| 
						 | 
					@ -50,6 +63,8 @@ export default {
 | 
				
			||||||
            videos: [],
 | 
					            videos: [],
 | 
				
			||||||
            availableFilters: ["all", "shorts", "videos"],
 | 
					            availableFilters: ["all", "shorts", "videos"],
 | 
				
			||||||
            selectedFilter: "all",
 | 
					            selectedFilter: "all",
 | 
				
			||||||
 | 
					            selectedGroupName: "",
 | 
				
			||||||
 | 
					            channelGroups: [],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
| 
						 | 
					@ -57,6 +72,12 @@ export default {
 | 
				
			||||||
            if (_this.authenticated) return _this.authApiUrl() + "/feed/rss?authToken=" + _this.getAuthToken();
 | 
					            if (_this.authenticated) return _this.authApiUrl() + "/feed/rss?authToken=" + _this.getAuthToken();
 | 
				
			||||||
            else return _this.authApiUrl() + "/feed/unauthenticated/rss?channels=" + _this.getUnauthenticatedChannels();
 | 
					            else return _this.authApiUrl() + "/feed/unauthenticated/rss?channels=" + _this.getUnauthenticatedChannels();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        filteredVideos(_this) {
 | 
				
			||||||
 | 
					            const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName);
 | 
				
			||||||
 | 
					            return _this.selectedGroupName == ""
 | 
				
			||||||
 | 
					                ? _this.videos
 | 
				
			||||||
 | 
					                : _this.videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-11)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        this.fetchFeed().then(videos => {
 | 
					        this.fetchFeed().then(videos => {
 | 
				
			||||||
| 
						 | 
					@ -66,6 +87,20 @@ export default {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.selectedFilter = this.getPreferenceString("feedFilter") ?? "all";
 | 
					        this.selectedFilter = this.getPreferenceString("feedFilter") ?? "all";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!window.db) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const cursor = this.getChannelGroupsCursor();
 | 
				
			||||||
 | 
					        cursor.onsuccess = e => {
 | 
				
			||||||
 | 
					            const cursor = e.target.result;
 | 
				
			||||||
 | 
					            if (cursor) {
 | 
				
			||||||
 | 
					                const group = cursor.value;
 | 
				
			||||||
 | 
					                this.channelGroups = this.channelGroups.concat({
 | 
				
			||||||
 | 
					                    groupName: group.groupName,
 | 
				
			||||||
 | 
					                    channels: JSON.parse(group.channels),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = this.$t("titles.feed") + " - Piped";
 | 
					        document.title = this.$t("titles.feed") + " - Piped";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,12 +15,32 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <br />
 | 
					    <br />
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
 | 
					    <div class="w-full flex flex-wrap">
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					            v-for="group in channelGroups"
 | 
				
			||||||
 | 
					            class="btn mx-1 w-max"
 | 
				
			||||||
 | 
					            :class="{ selected: selectedGroup === group }"
 | 
				
			||||||
 | 
					            :key="group.groupName"
 | 
				
			||||||
 | 
					            @click="selectedGroup = group"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
 | 
				
			||||||
 | 
					            <div v-if="group.groupName != '' && selectedGroup == group">
 | 
				
			||||||
 | 
					                <font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" />
 | 
				
			||||||
 | 
					                <font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button class="btn mx-1">
 | 
				
			||||||
 | 
					            <font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <br />
 | 
				
			||||||
 | 
					    <hr />
 | 
				
			||||||
    <!-- Subscriptions card list -->
 | 
					    <!-- Subscriptions card list -->
 | 
				
			||||||
    <div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
 | 
					    <div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
 | 
				
			||||||
        <!-- channel info card -->
 | 
					        <!-- channel info card -->
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
            class="col m-2 p-1 border rounded-lg border-gray-500"
 | 
					            class="col m-2 p-1 border rounded-lg border-gray-500"
 | 
				
			||||||
            v-for="subscription in subscriptions"
 | 
					            v-for="subscription in filteredSubscriptions"
 | 
				
			||||||
            :key="subscription.url"
 | 
					            :key="subscription.url"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <router-link :to="subscription.url" class="flex p-2 font-bold text-4x4">
 | 
					            <router-link :to="subscription.url" class="flex p-2 font-bold text-4x4">
 | 
				
			||||||
| 
						 | 
					@ -36,13 +56,48 @@
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <br />
 | 
					    <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
 | 
				
			||||||
 | 
					        <h2 v-t="'actions.create_group'" />
 | 
				
			||||||
 | 
					        <div class="flex flex-col">
 | 
				
			||||||
 | 
					            <input class="input my-4" type="text" v-model="newGroupName" :placeholder="$t('actions.group_name')" />
 | 
				
			||||||
 | 
					            <button class="ml-auto btn w-max" v-t="'actions.create_group'" @click="createGroup()" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </ModalComponent>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
 | 
				
			||||||
 | 
					        <h2>{{ selectedGroup.groupName }}</h2>
 | 
				
			||||||
 | 
					        <div class="flex flex-col mt-3 mb-2">
 | 
				
			||||||
 | 
					            <div v-for="subscription in subscriptions" :key="subscription.name">
 | 
				
			||||||
 | 
					                <div class="flex justify-between">
 | 
				
			||||||
 | 
					                    <span>{{ subscription.name }}</span>
 | 
				
			||||||
 | 
					                    <DefaultValueCheckbox
 | 
				
			||||||
 | 
					                        :default-value="selectedGroup.channels.includes(subscription.url.substr(-11))"
 | 
				
			||||||
 | 
					                        :callback="() => checkedChange(subscription)"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <hr />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </ModalComponent>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 | 
					import DefaultValueCheckbox from "./DefaultValueCheckbox.vue";
 | 
				
			||||||
 | 
					import ModalComponent from "./ModalComponent.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            subscriptions: [],
 | 
					            subscriptions: [],
 | 
				
			||||||
 | 
					            selectedGroup: {
 | 
				
			||||||
 | 
					                groupName: "",
 | 
				
			||||||
 | 
					                channels: [],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            channelGroups: [],
 | 
				
			||||||
 | 
					            showCreateGroupModal: false,
 | 
				
			||||||
 | 
					            showEditGroupModal: false,
 | 
				
			||||||
 | 
					            newGroupName: "",
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
| 
						 | 
					@ -50,6 +105,21 @@ export default {
 | 
				
			||||||
            this.subscriptions = json;
 | 
					            this.subscriptions = json;
 | 
				
			||||||
            this.subscriptions.forEach(subscription => (subscription.subscribed = true));
 | 
					            this.subscriptions.forEach(subscription => (subscription.subscribed = true));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.channelGroups = this.channelGroups.concat(this.selectedGroup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!window.db) return;
 | 
				
			||||||
 | 
					        const cursor = this.getChannelGroupsCursor();
 | 
				
			||||||
 | 
					        cursor.onsuccess = e => {
 | 
				
			||||||
 | 
					            const cursor = e.target.result;
 | 
				
			||||||
 | 
					            if (cursor) {
 | 
				
			||||||
 | 
					                const group = cursor.value;
 | 
				
			||||||
 | 
					                this.channelGroups = this.channelGroups.concat({
 | 
				
			||||||
 | 
					                    groupName: group.groupName,
 | 
				
			||||||
 | 
					                    channels: JSON.parse(group.channels),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = "Subscriptions - Piped";
 | 
					        document.title = "Subscriptions - Piped";
 | 
				
			||||||
| 
						 | 
					@ -88,7 +158,6 @@ export default {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        exportHandler() {
 | 
					        exportHandler() {
 | 
				
			||||||
            const subscriptions = [];
 | 
					            const subscriptions = [];
 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.subscriptions.forEach(subscription => {
 | 
					            this.subscriptions.forEach(subscription => {
 | 
				
			||||||
                subscriptions.push({
 | 
					                subscriptions.push({
 | 
				
			||||||
                    url: "https://www.youtube.com" + subscription.url,
 | 
					                    url: "https://www.youtube.com" + subscription.url,
 | 
				
			||||||
| 
						 | 
					@ -96,15 +165,53 @@ export default {
 | 
				
			||||||
                    service_id: 0,
 | 
					                    service_id: 0,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					 | 
				
			||||||
            const json = JSON.stringify({
 | 
					            const json = JSON.stringify({
 | 
				
			||||||
                app_version: "",
 | 
					                app_version: "",
 | 
				
			||||||
                app_version_int: 0,
 | 
					                app_version_int: 0,
 | 
				
			||||||
                subscriptions: subscriptions,
 | 
					                subscriptions: subscriptions,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.download(json, "subscriptions.json", "application/json");
 | 
					            this.download(json, "subscriptions.json", "application/json");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        createGroup() {
 | 
				
			||||||
 | 
					            if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const newGroup = {
 | 
				
			||||||
 | 
					                groupName: this.newGroupName,
 | 
				
			||||||
 | 
					                channels: [],
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            this.channelGroups = this.channelGroups.concat(newGroup);
 | 
				
			||||||
 | 
					            this.createOrUpdateChannelGroup(newGroup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.newGroupName = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.showCreateGroupModal = false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        deleteGroup(group) {
 | 
				
			||||||
 | 
					            this.deleteChannelGroup(group.groupName);
 | 
				
			||||||
 | 
					            this.channelGroups = this.channelGroups.filter(g => g != group);
 | 
				
			||||||
 | 
					            this.selectedGroup = this.channelGroups[0];
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        checkedChange(subscription) {
 | 
				
			||||||
 | 
					            const channelId = subscription.url.substr(-11);
 | 
				
			||||||
 | 
					            this.selectedGroup.channels = this.selectedGroup.channels.includes(channelId)
 | 
				
			||||||
 | 
					                ? this.selectedGroup.channels.filter(channel => channel != channelId)
 | 
				
			||||||
 | 
					                : this.selectedGroup.channels.concat(channelId);
 | 
				
			||||||
 | 
					            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    computed: {
 | 
				
			||||||
 | 
					        filteredSubscriptions(_this) {
 | 
				
			||||||
 | 
					            return _this.selectedGroup.groupName == ""
 | 
				
			||||||
 | 
					                ? _this.subscriptions
 | 
				
			||||||
 | 
					                : _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    components: { ModalComponent, DefaultValueCheckbox },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					.selected {
 | 
				
			||||||
 | 
					    border: 0.1rem outset red;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,8 @@
 | 
				
			||||||
        "player": "Player",
 | 
					        "player": "Player",
 | 
				
			||||||
        "livestreams": "Livestreams",
 | 
					        "livestreams": "Livestreams",
 | 
				
			||||||
        "channels": "Channels",
 | 
					        "channels": "Channels",
 | 
				
			||||||
        "bookmarks": "Bookmarks"
 | 
					        "bookmarks": "Bookmarks",
 | 
				
			||||||
 | 
					        "channel_groups": "Channel groups"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Watch on {0}"
 | 
					        "watch_on": "Watch on {0}"
 | 
				
			||||||
| 
						 | 
					@ -131,7 +132,9 @@
 | 
				
			||||||
        "playlist_bookmarked": "Bookmarked",
 | 
					        "playlist_bookmarked": "Bookmarked",
 | 
				
			||||||
        "dismiss": "Dismiss",
 | 
					        "dismiss": "Dismiss",
 | 
				
			||||||
        "show_more": "Show more",
 | 
					        "show_more": "Show more",
 | 
				
			||||||
        "show_less": "Show less"
 | 
					        "show_less": "Show less",
 | 
				
			||||||
 | 
					        "create_group": "Create group",
 | 
				
			||||||
 | 
					        "group_name": "Group name"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Pinned by {author}",
 | 
					        "pinned_by": "Pinned by {author}",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/main.js
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								src/main.js
									
										
									
									
									
								
							| 
						 | 
					@ -21,6 +21,7 @@ import {
 | 
				
			||||||
    faServer,
 | 
					    faServer,
 | 
				
			||||||
    faDonate,
 | 
					    faDonate,
 | 
				
			||||||
    faBookmark,
 | 
					    faBookmark,
 | 
				
			||||||
 | 
					    faEdit,
 | 
				
			||||||
} from "@fortawesome/free-solid-svg-icons";
 | 
					} from "@fortawesome/free-solid-svg-icons";
 | 
				
			||||||
import { faGithub, faBitcoin, faYoutube } from "@fortawesome/free-brands-svg-icons";
 | 
					import { faGithub, faBitcoin, faYoutube } from "@fortawesome/free-brands-svg-icons";
 | 
				
			||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 | 
					import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 | 
				
			||||||
| 
						 | 
					@ -48,6 +49,7 @@ library.add(
 | 
				
			||||||
    faServer,
 | 
					    faServer,
 | 
				
			||||||
    faDonate,
 | 
					    faDonate,
 | 
				
			||||||
    faBookmark,
 | 
					    faBookmark,
 | 
				
			||||||
 | 
					    faEdit,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import router from "@/router/router.js";
 | 
					import router from "@/router/router.js";
 | 
				
			||||||
| 
						 | 
					@ -271,6 +273,25 @@ const mixin = {
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .replaceAll("\n", "<br>");
 | 
					                .replaceAll("\n", "<br>");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        getChannelGroupsCursor() {
 | 
				
			||||||
 | 
					            if (!window.db) return;
 | 
				
			||||||
 | 
					            var tx = window.db.transaction("channel_groups", "readonly");
 | 
				
			||||||
 | 
					            var store = tx.objectStore("channel_groups");
 | 
				
			||||||
 | 
					            return store.index("groupName").openCursor();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        createOrUpdateChannelGroup(group) {
 | 
				
			||||||
 | 
					            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
				
			||||||
 | 
					            var store = tx.objectStore("channel_groups");
 | 
				
			||||||
 | 
					            store.put({
 | 
				
			||||||
 | 
					                groupName: group.groupName,
 | 
				
			||||||
 | 
					                channels: JSON.stringify(group.channels),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        deleteChannelGroup(groupName) {
 | 
				
			||||||
 | 
					            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
				
			||||||
 | 
					            var store = tx.objectStore("channel_groups");
 | 
				
			||||||
 | 
					            store.delete(groupName);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
        authenticated(_this) {
 | 
					        authenticated(_this) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue