Merge branch 'Subscriptions'
This commit is contained in:
		
						commit
						52475877fe
					
				| 
						 | 
				
			
			@ -11136,6 +11136,14 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "javascript-time-ago": {
 | 
			
		||||
      "version": "2.0.13",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.0.13.tgz",
 | 
			
		||||
      "integrity": "sha512-zH+obXUQ4vlc9UlERFe637rNJQaVYLizwODUfGzYN/cNW/owkk5wzb327gAfEXFpI4yhFcStEaoqoJtMGAmrAg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "relative-time-format": "^0.1.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "jest": {
 | 
			
		||||
      "version": "26.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -13318,6 +13326,11 @@
 | 
			
		|||
      "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "lodash.uniqwith": {
 | 
			
		||||
      "version": "4.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz",
 | 
			
		||||
      "integrity": "sha1-egy/ZfQ7WShiWp1NDcVLGMrcfvM="
 | 
			
		||||
    },
 | 
			
		||||
    "log-symbols": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -16235,6 +16248,11 @@
 | 
			
		|||
      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "relative-time-format": {
 | 
			
		||||
      "version": "0.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-0.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-0O6i4fKjsx8qhz57zorG+LrIDnF9pSvP5s7H9R1Nb5nSqih5dvRyKzNKs6MxhL3bv4iwsz4DuDwAyw+c47QFIA=="
 | 
			
		||||
    },
 | 
			
		||||
    "remove-trailing-separator": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -16517,6 +16535,31 @@
 | 
			
		|||
        "sprintf-js": "^1.1.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "rss-parser": {
 | 
			
		||||
      "version": "3.9.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.9.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wlRSfGrotOXuWo19Dtl2KmQt7o9i5zzCExUrxpechE0O54BAx7JD+xhWyGumPPqiJj771ndflV3sE3bTHen0HQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "entities": "^2.0.3",
 | 
			
		||||
        "xml2js": "^0.4.19"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "entities": {
 | 
			
		||||
          "version": "2.0.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
 | 
			
		||||
          "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "rss-to-json": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rss-to-json/-/rss-to-json-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-d+TwrFI5wAHbZ/fTd3Pvty14tadBjKHAjfMcUam9FWoWrC9g5rHJN9Slw10OZwk6Mey+hqdXwdmymO7d8ebVmw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "axios": "^0.19.2",
 | 
			
		||||
        "xml2json": "^0.12.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "rsvp": {
 | 
			
		||||
      "version": "4.8.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -20244,6 +20287,15 @@
 | 
			
		|||
      "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "xml2js": {
 | 
			
		||||
      "version": "0.4.23",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
 | 
			
		||||
      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "sax": ">=0.6.0",
 | 
			
		||||
        "xmlbuilder": "~11.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "xml2json": {
 | 
			
		||||
      "version": "0.12.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xml2json/-/xml2json-0.12.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -20254,6 +20306,11 @@
 | 
			
		|||
        "node-expat": "^2.3.18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "xmlbuilder": {
 | 
			
		||||
      "version": "11.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
 | 
			
		||||
    },
 | 
			
		||||
    "xmlchars": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,10 +20,12 @@
 | 
			
		|||
    "js-yaml": "^3.14.0",
 | 
			
		||||
    "lodash.debounce": "^4.0.8",
 | 
			
		||||
    "lodash.isequal": "^4.5.0",
 | 
			
		||||
    "lodash.uniqwith": "^4.5.0",
 | 
			
		||||
    "material-design-icons": "^3.0.1",
 | 
			
		||||
    "mediaelement": "^4.2.16",
 | 
			
		||||
    "nedb": "^1.8.0",
 | 
			
		||||
    "opml-to-json": "0.0.3",
 | 
			
		||||
    "rss-parser": "^3.9.0",
 | 
			
		||||
    "video.js": "7.6.6",
 | 
			
		||||
    "videojs-abloop": "^1.1.2",
 | 
			
		||||
    "videojs-contrib-quality-levels": "^2.0.9",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import { ObserveVisibility } from 'vue-observe-visibility'
 | 
			
		|||
import TopNav from './components/top-nav/top-nav.vue'
 | 
			
		||||
import SideNav from './components/side-nav/side-nav.vue'
 | 
			
		||||
import FtToast from './components/ft-toast/ft-toast.vue'
 | 
			
		||||
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
 | 
			
		||||
import $ from 'jquery'
 | 
			
		||||
 | 
			
		||||
let useElectron
 | 
			
		||||
| 
						 | 
				
			
			@ -23,16 +24,21 @@ export default Vue.extend({
 | 
			
		|||
  components: {
 | 
			
		||||
    TopNav,
 | 
			
		||||
    SideNav,
 | 
			
		||||
    FtToast
 | 
			
		||||
    FtToast,
 | 
			
		||||
    FtProgressBar
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    isOpen: function () {
 | 
			
		||||
      return this.$store.getters.getIsSideNavOpen
 | 
			
		||||
    },
 | 
			
		||||
    showProgressBar: function () {
 | 
			
		||||
      return this.$store.getters.getShowProgressBar
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
    this.$store.dispatch('grabUserSettings')
 | 
			
		||||
    this.$store.dispatch('grabHistory')
 | 
			
		||||
    this.$store.dispatch('grabAllProfiles', this.$t('Profile.All Channels'))
 | 
			
		||||
    this.$store.commit('setUsingElectron', useElectron)
 | 
			
		||||
    this.checkThemeSettings()
 | 
			
		||||
    this.checkLocale()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,9 @@
 | 
			
		|||
      <!-- </keep-alive> -->
 | 
			
		||||
    </Transition>
 | 
			
		||||
    <ft-toast />
 | 
			
		||||
    <ft-progress-bar
 | 
			
		||||
      v-if="showProgressBar"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,10 @@ export default Vue.extend({
 | 
			
		|||
      type: Boolean,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
    disabled: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
    dataList: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: () => { return [] }
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +51,11 @@ export default Vue.extend({
 | 
			
		|||
      return `${this.id}_datalist`
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    value: function (val) {
 | 
			
		||||
      this.inputData = val
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
    this.id = this._uid
 | 
			
		||||
    this.inputData = this.value
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@
 | 
			
		|||
      type="text"
 | 
			
		||||
      :placeholder="placeholder"
 | 
			
		||||
      @input="e => handleInput(e.target.value)"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
    >
 | 
			
		||||
    <font-awesome-icon
 | 
			
		||||
      v-if="showArrow"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -255,7 +255,8 @@ export default Vue.extend({
 | 
			
		|||
          liveStreamString: this.$t('Video.Watching'),
 | 
			
		||||
          upcomingString: this.$t('Video.Published.Upcoming'),
 | 
			
		||||
          isLive: this.isLive,
 | 
			
		||||
          isUpcoming: this.data.isUpcoming
 | 
			
		||||
          isUpcoming: this.data.isUpcoming,
 | 
			
		||||
          isRSS: this.data.isRSS
 | 
			
		||||
        }).then((data) => {
 | 
			
		||||
          this.uploadedTime = data
 | 
			
		||||
        }).catch((error) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
.bubblePadding {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 115px;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  -webkit-transition: background 0.2s ease-out;
 | 
			
		||||
  -moz-transition: background 0.2s ease-out;
 | 
			
		||||
  -o-transition: background 0.2s ease-out;
 | 
			
		||||
  transition: background 0.2s ease-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bubblePadding:hover {
 | 
			
		||||
  background-color: var(--side-nav-hover-color);
 | 
			
		||||
  -moz-transition: background 0.2s ease-in;
 | 
			
		||||
  -o-transition: background 0.2s ease-in;
 | 
			
		||||
  transition: background 0.2s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bubble {
 | 
			
		||||
  width: 50px;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
  margin-left: 25px;
 | 
			
		||||
  border-radius: 200px 200px 200px 200px;
 | 
			
		||||
  -webkit-border-radius: 200px 200px 200px 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.initial {
 | 
			
		||||
  font-size: 25px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileName {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  height: 60px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'FtProfileBubble',
 | 
			
		||||
  props: {
 | 
			
		||||
    profileName: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    profileId: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    backgroundColor: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    textColor: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    profileInitial: function () {
 | 
			
		||||
      return this.profileName.slice(0, 1).toUpperCase()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    goToProfile: function () {
 | 
			
		||||
      this.$router.push({
 | 
			
		||||
        path: `/settings/profile/edit/${this.profileId}`
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="bubblePadding"
 | 
			
		||||
    @click="goToProfile"
 | 
			
		||||
  >
 | 
			
		||||
    <div
 | 
			
		||||
      class="bubble"
 | 
			
		||||
      :style="{ background: backgroundColor, color: textColor }"
 | 
			
		||||
    >
 | 
			
		||||
      <p class="initial">
 | 
			
		||||
        {{ profileInitial }}
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="profileName">
 | 
			
		||||
      {{ profileName }}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./ft-profile-bubble.js" />
 | 
			
		||||
<style scoped src="./ft-profile-bubble.css" />
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
.colorOption {
 | 
			
		||||
  width: 40px;
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border-radius: 200px 200px 200px 200px;
 | 
			
		||||
  -webkit-border-radius: 200px 200px 200px 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.initial {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#profileList {
 | 
			
		||||
  display: none;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 60px;
 | 
			
		||||
  right: 10px;
 | 
			
		||||
  min-width: 250px;
 | 
			
		||||
  height: 300px;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  background-color: var(--card-bg-color);
 | 
			
		||||
  box-shadow: 0 1px 2px rgba(0,0,0,.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#profileList:focus {
 | 
			
		||||
  display: inline;
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileWrapper {
 | 
			
		||||
  margin-top: 60px;
 | 
			
		||||
  height: 240px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profile {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  -webkit-transition: background 0.2s ease-out;
 | 
			
		||||
  -moz-transition: background 0.2s ease-out;
 | 
			
		||||
  -o-transition: background 0.2s ease-out;
 | 
			
		||||
  transition: background 0.2s ease-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profile:hover {
 | 
			
		||||
  background-color: var(--side-nav-hover-color);
 | 
			
		||||
  -moz-transition: background 0.2s ease-in;
 | 
			
		||||
  -o-transition: background 0.2s ease-in;
 | 
			
		||||
  transition: background 0.2s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profile .colorOption {
 | 
			
		||||
  float: left;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileName {
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileListTitle {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: -15px;
 | 
			
		||||
  left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileSettings {
 | 
			
		||||
  float: right;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 10px;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,90 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import { mapActions } from 'vuex'
 | 
			
		||||
import $ from 'jquery'
 | 
			
		||||
 | 
			
		||||
import FtCard from '../../components/ft-card/ft-card.vue'
 | 
			
		||||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'FtProfileSelector',
 | 
			
		||||
  components: {
 | 
			
		||||
    'ft-card': FtCard,
 | 
			
		||||
    'ft-icon-button': FtIconButton
 | 
			
		||||
  },
 | 
			
		||||
  data: function () {
 | 
			
		||||
    return {
 | 
			
		||||
      showProfileList: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    },
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
    defaultProfile: function () {
 | 
			
		||||
      return this.$store.getters.getDefaultProfile
 | 
			
		||||
    },
 | 
			
		||||
    activeProfileInitial: function () {
 | 
			
		||||
      return this.activeProfile.name.slice(0, 1).toUpperCase()
 | 
			
		||||
    },
 | 
			
		||||
    profileInitials: function () {
 | 
			
		||||
      return this.profileList.map((profile) => {
 | 
			
		||||
        return profile.name.slice(0, 1).toUpperCase()
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      const profileIndex = this.profileList.findIndex((profile) => {
 | 
			
		||||
        return profile._id === this.defaultProfile
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (profileIndex !== -1) {
 | 
			
		||||
        this.updateActiveProfile(profileIndex)
 | 
			
		||||
      }
 | 
			
		||||
    }, 100)
 | 
			
		||||
 | 
			
		||||
    $('#profileList').focusout(() => {
 | 
			
		||||
      $('#profileList')[0].style.display = 'none'
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    toggleProfileList: function () {
 | 
			
		||||
      $('#profileList')[0].style.display = 'inline'
 | 
			
		||||
      $('#profileList').focus()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    openProfileSettings: function () {
 | 
			
		||||
      this.$router.push({
 | 
			
		||||
        path: '/settings/profile/'
 | 
			
		||||
      })
 | 
			
		||||
      $('#profileList').focusout()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setActiveProfile: function (profile) {
 | 
			
		||||
      if (this.profileList[this.activeProfile]._id === profile._id) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const index = this.profileList.findIndex((x) => {
 | 
			
		||||
        return x._id === profile._id
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (index === -1) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      this.updateActiveProfile(index)
 | 
			
		||||
      const message = this.$t('Profile.$ is now the active profile').replace('$', profile.name)
 | 
			
		||||
      this.showToast({
 | 
			
		||||
        message: message
 | 
			
		||||
      })
 | 
			
		||||
      $('#profileList').focusout()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'showToast',
 | 
			
		||||
      'updateActiveProfile'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="colorOption"
 | 
			
		||||
      :style="{ background: profileList[activeProfile].bgColor, color: profileList[activeProfile].textColor }"
 | 
			
		||||
      @click="toggleProfileList"
 | 
			
		||||
    >
 | 
			
		||||
      <p
 | 
			
		||||
        class="initial"
 | 
			
		||||
      >
 | 
			
		||||
        {{ profileInitials[activeProfile] }}
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ft-card
 | 
			
		||||
      id="profileList"
 | 
			
		||||
      tabindex="-1"
 | 
			
		||||
    >
 | 
			
		||||
      <h3
 | 
			
		||||
        class="profileListTitle"
 | 
			
		||||
      >
 | 
			
		||||
        Profile Select
 | 
			
		||||
      </h3>
 | 
			
		||||
      <ft-icon-button
 | 
			
		||||
        class="profileSettings"
 | 
			
		||||
        icon="sliders-h"
 | 
			
		||||
        @click="openProfileSettings"
 | 
			
		||||
      />
 | 
			
		||||
      <div
 | 
			
		||||
        class="profileWrapper"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          class="profile"
 | 
			
		||||
          v-for="(profile, index) in profileList"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          @click="setActiveProfile(profile)"
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            class="colorOption"
 | 
			
		||||
            :style="{ background: profile.bgColor, color: profile.textColor }"
 | 
			
		||||
          >
 | 
			
		||||
            <p
 | 
			
		||||
              class="initial"
 | 
			
		||||
            >
 | 
			
		||||
              {{ profileInitials[index] }}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <p
 | 
			
		||||
            class="profileName"
 | 
			
		||||
          >
 | 
			
		||||
            {{ profile.name }}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ft-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./ft-profile-selector.js" />
 | 
			
		||||
<style scoped src="./ft-profile-selector.css" />
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
.progressBar {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  height: 3px;
 | 
			
		||||
  bottom: 0px;
 | 
			
		||||
  left: 0px;
 | 
			
		||||
  background-color: var(--primary-color);
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  transition: width 0.5s;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'FtProgressBar',
 | 
			
		||||
  computed: {
 | 
			
		||||
    progressBarPercentage: function () {
 | 
			
		||||
      return this.$store.getters.getProgressBarPercentage
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="progressBar"
 | 
			
		||||
    :style="{ width: progressBarPercentage + '%' }"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./ft-progress-bar.js" />
 | 
			
		||||
<style scoped src="./ft-progress-bar.css" />
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@
 | 
			
		|||
  margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navOption {
 | 
			
		||||
.navOption, .navChannel {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
| 
						 | 
				
			
			@ -40,14 +40,14 @@
 | 
			
		|||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navOption:hover {
 | 
			
		||||
.navOption:hover, .navChannel:hover {
 | 
			
		||||
  background-color: var(--side-nav-hover-color);
 | 
			
		||||
  -moz-transition: background 0.2s ease-in;
 | 
			
		||||
  -o-transition: background 0.2s ease-in;
 | 
			
		||||
  transition: background 0.2s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navOption:active {
 | 
			
		||||
.navOption:active, .navChannel:active {
 | 
			
		||||
  background-color: var(--side-nav-active-color);
 | 
			
		||||
  -moz-transition: background 0.2s ease-in;
 | 
			
		||||
  -o-transition: background 0.2s ease-in;
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +63,26 @@
 | 
			
		|||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navChannel .navLabel {
 | 
			
		||||
  font-size: 11px;
 | 
			
		||||
  width: 150px;
 | 
			
		||||
  margin-left: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.thumbnailContainer {
 | 
			
		||||
  width: 35px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  -ms-transform: translateY(-50%);
 | 
			
		||||
  transform: translateY(-50%);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.channelThumbnail {
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  width: 35px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.closed {
 | 
			
		||||
  width: 80px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +127,24 @@
 | 
			
		|||
  font-size: 11px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.closed .navChannel {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.closed .thumbnailContainer {
 | 
			
		||||
  position: static;
 | 
			
		||||
  display: block;
 | 
			
		||||
  float: none;
 | 
			
		||||
  margin-left: 0px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  top: 0px;
 | 
			
		||||
  -ms-transform: none;
 | 
			
		||||
  transform: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 680px) {
 | 
			
		||||
  .inner {
 | 
			
		||||
    display: contents; /* sunglasses emoji */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,11 +12,35 @@ export default Vue.extend({
 | 
			
		|||
  computed: {
 | 
			
		||||
    isOpen: function () {
 | 
			
		||||
      return this.$store.getters.getIsSideNavOpen
 | 
			
		||||
    },
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    },
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
    activeSubscriptions: function () {
 | 
			
		||||
      const profile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile]))
 | 
			
		||||
      return profile.subscriptions.sort((a, b) => {
 | 
			
		||||
        const nameA = a.name.toLowerCase()
 | 
			
		||||
        const nameB = b.name.toLowerCase()
 | 
			
		||||
        if (nameA < nameB) {
 | 
			
		||||
          return -1
 | 
			
		||||
        }
 | 
			
		||||
        if (nameA > nameB) {
 | 
			
		||||
          return 1
 | 
			
		||||
        }
 | 
			
		||||
        return 0
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    navigate: function (route) {
 | 
			
		||||
      router.push('/' + route)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    goToChannel: function (id) {
 | 
			
		||||
      this.$router.push({ path: `/channel/${id}` })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,10 +16,6 @@
 | 
			
		|||
        <p class="navLabel">
 | 
			
		||||
          {{ $t("Subscriptions.Subscriptions") }}
 | 
			
		||||
        </p>
 | 
			
		||||
        <font-awesome-icon
 | 
			
		||||
          class="refreshIcon"
 | 
			
		||||
          icon="sync"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        class="navOption mobileHidden"
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +94,28 @@
 | 
			
		|||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <div
 | 
			
		||||
        v-for="(channel, index) in activeSubscriptions"
 | 
			
		||||
        :key="index"
 | 
			
		||||
        class="navChannel mobileHidden"
 | 
			
		||||
        :title="channel.name"
 | 
			
		||||
        @click="goToChannel(channel.id)"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          class="thumbnailContainer"
 | 
			
		||||
        >
 | 
			
		||||
          <img
 | 
			
		||||
            class="channelThumbnail"
 | 
			
		||||
            :src="channel.thumbnail"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <p
 | 
			
		||||
          class="navLabel"
 | 
			
		||||
          v-if="isOpen"
 | 
			
		||||
        >
 | 
			
		||||
          {{ channel.name }}
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ft-flex-box>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import { mapActions } from 'vuex'
 | 
			
		||||
import FtCard from '../ft-card/ft-card.vue'
 | 
			
		||||
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
 | 
			
		||||
import FtButton from '../ft-button/ft-button.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -27,9 +28,18 @@ export default Vue.extend({
 | 
			
		|||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    goToChannel: function () {
 | 
			
		||||
      console.log('TODO: Handle goToChannel')
 | 
			
		||||
  computed: {
 | 
			
		||||
    hideWatchedSubs: function () {
 | 
			
		||||
      return this.$store.getters.getHideWatchedSubs
 | 
			
		||||
    },
 | 
			
		||||
    useRssFeeds: function () {
 | 
			
		||||
      return this.$store.getters.getUseRssFeeds
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'updateHideWatchedSubs',
 | 
			
		||||
      'updateUseRssFeeds'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,16 +5,24 @@
 | 
			
		|||
    <h3
 | 
			
		||||
      class="videoTitle"
 | 
			
		||||
    >
 | 
			
		||||
      {{ title }}
 | 
			
		||||
      {{ $t("Settings.Subscription Settings.Subscription Settings") }}
 | 
			
		||||
    </h3>
 | 
			
		||||
    <ft-flex-box class="subscriptionSettingsFlexBox">
 | 
			
		||||
      <ft-toggle-switch
 | 
			
		||||
        label="Hide Videos When Watched"
 | 
			
		||||
        :label="$t('Settings.Subscription Settings.Hide Videos on Watch')"
 | 
			
		||||
        :default-value="hideWatchedSubs"
 | 
			
		||||
        @change="updateHideWatchedSubs"
 | 
			
		||||
      />
 | 
			
		||||
      <ft-toggle-switch
 | 
			
		||||
        :label="$t('Settings.Subscription Settings.Fetch Feeds from RSS')"
 | 
			
		||||
        :default-value="useRssFeeds"
 | 
			
		||||
        @change="updateUseRssFeeds"
 | 
			
		||||
      />
 | 
			
		||||
    </ft-flex-box>
 | 
			
		||||
    <br>
 | 
			
		||||
    <ft-flex-box>
 | 
			
		||||
      <ft-select
 | 
			
		||||
        v-if="false"
 | 
			
		||||
        placeholder="Subscription View Type"
 | 
			
		||||
        :value="viewValues[0]"
 | 
			
		||||
        :select-names="viewNames"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +32,7 @@
 | 
			
		|||
    <br>
 | 
			
		||||
    <ft-flex-box>
 | 
			
		||||
      <ft-button
 | 
			
		||||
        v-if="false"
 | 
			
		||||
        label="Manage My Subscriptions"
 | 
			
		||||
      />
 | 
			
		||||
    </ft-flex-box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import FtInput from '../ft-input/ft-input.vue'
 | 
			
		||||
import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue'
 | 
			
		||||
import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
 | 
			
		||||
import $ from 'jquery'
 | 
			
		||||
import router from '../../router/index.js'
 | 
			
		||||
import debounce from 'lodash.debounce'
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +11,8 @@ export default Vue.extend({
 | 
			
		|||
  name: 'TopNav',
 | 
			
		||||
  components: {
 | 
			
		||||
    FtInput,
 | 
			
		||||
    FtSearchFilters
 | 
			
		||||
    FtSearchFilters,
 | 
			
		||||
    FtProfileSelector
 | 
			
		||||
  },
 | 
			
		||||
  data: () => {
 | 
			
		||||
    return {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,9 @@
 | 
			
		|||
  display: flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
 | 
			
		||||
  &.profiles
 | 
			
		||||
    justify-content: flex-end
 | 
			
		||||
 | 
			
		||||
  .navSearchIcon
 | 
			
		||||
    @media only screen and (min-width: 681px)
 | 
			
		||||
      display: none
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,7 +55,7 @@
 | 
			
		|||
        :class="{ expand: !isSideNavOpen }"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="side" />
 | 
			
		||||
    <ft-profile-selector class="side profiles" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,6 +79,14 @@ export default Vue.extend({
 | 
			
		|||
      return this.$store.getters.getUsingElectron
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    formatTypeNames: function () {
 | 
			
		||||
      return [
 | 
			
		||||
        this.$t('Change Format.Use Dash Formats').toUpperCase(),
 | 
			
		||||
| 
						 | 
				
			
			@ -99,8 +107,24 @@ export default Vue.extend({
 | 
			
		|||
      return this.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ` ${this.$t('Video.Views').toLowerCase()}`
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    isSubscribed: function () {
 | 
			
		||||
      const subIndex = this.profileList[this.activeProfile].subscriptions.findIndex((channel) => {
 | 
			
		||||
        return channel.id === this.channelId
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (subIndex === -1) {
 | 
			
		||||
        return false
 | 
			
		||||
      } else {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    subscribedText: function () {
 | 
			
		||||
      return `${this.$t('Channel.Subscribe').toUpperCase()} ${this.subscriptionCountText}`
 | 
			
		||||
      if (this.isSubscribed) {
 | 
			
		||||
        return `${this.$t('Channel.Unsubscribe').toUpperCase()} ${this.subscriptionCountText}`
 | 
			
		||||
      } else {
 | 
			
		||||
        return `${this.$t('Channel.Subscribe').toUpperCase()} ${this.subscriptionCountText}`
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    dateString() {
 | 
			
		||||
| 
						 | 
				
			
			@ -116,9 +140,74 @@ export default Vue.extend({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    handleSubscription: function () {
 | 
			
		||||
      this.showToast({
 | 
			
		||||
        message: 'Subscriptions have not yet been implemented'
 | 
			
		||||
      })
 | 
			
		||||
      const currentProfile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile]))
 | 
			
		||||
      const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
 | 
			
		||||
 | 
			
		||||
      if (this.isSubscribed) {
 | 
			
		||||
        currentProfile.subscriptions = currentProfile.subscriptions.filter((channel) => {
 | 
			
		||||
          return channel.id !== this.channelId
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        this.updateProfile(currentProfile)
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: 'Channel has been removed from your subscriptions'
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (this.activeProfile === 0) {
 | 
			
		||||
          // Check if a subscription exists in a different profile.
 | 
			
		||||
          // Remove from there as well.
 | 
			
		||||
          let duplicateSubscriptions = 0
 | 
			
		||||
 | 
			
		||||
          this.profileList.forEach((profile) => {
 | 
			
		||||
            if (profile._id === 'allChannels') {
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
            const parsedProfile = JSON.parse(JSON.stringify(profile))
 | 
			
		||||
            const index = parsedProfile.subscriptions.findIndex((channel) => {
 | 
			
		||||
              return channel.id === this.channelId
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
              duplicateSubscriptions++
 | 
			
		||||
 | 
			
		||||
              parsedProfile.subscriptions = parsedProfile.subscriptions.filter((x) => {
 | 
			
		||||
                return x.id !== this.channelId
 | 
			
		||||
              })
 | 
			
		||||
 | 
			
		||||
              this.updateProfile(parsedProfile)
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          if (duplicateSubscriptions > 0) {
 | 
			
		||||
            this.showToast({
 | 
			
		||||
              message: `Removed subscription from ${duplicateSubscriptions} other channel(s)`
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        const subscription = {
 | 
			
		||||
          id: this.channelId,
 | 
			
		||||
          name: this.channelName,
 | 
			
		||||
          thumbnail: this.channelThumbnail
 | 
			
		||||
        }
 | 
			
		||||
        currentProfile.subscriptions.push(subscription)
 | 
			
		||||
 | 
			
		||||
        this.updateProfile(currentProfile)
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: 'Added channel to your subscriptions'
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (this.activeProfile !== 0) {
 | 
			
		||||
          const index = primaryProfile.subscriptions.findIndex((channel) => {
 | 
			
		||||
            return channel.id === this.channelId
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          if (index === -1) {
 | 
			
		||||
            primaryProfile.subscriptions.push(subscription)
 | 
			
		||||
            this.updateProfile(primaryProfile)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    handleFormatChange: function (format) {
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +225,8 @@ export default Vue.extend({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'showToast'
 | 
			
		||||
      'showToast',
 | 
			
		||||
      'updateProfile'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@
 | 
			
		|||
              :label="subscribedText"
 | 
			
		||||
              class="subscribeButton"
 | 
			
		||||
              background-color="var(--primary-color)"
 | 
			
		||||
              text-color="var(--text-with-main-color)"
 | 
			
		||||
              @click="handleSubscription"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import Router from 'vue-router'
 | 
			
		||||
import Subscriptions from '../views/Subscriptions/Subscriptions.vue'
 | 
			
		||||
import ProfileSettings from '../views/ProfileSettings/ProfileSettings.vue'
 | 
			
		||||
import ProfileEdit from '../views/ProfileEdit/ProfileEdit.vue'
 | 
			
		||||
import Trending from '../views/Trending/Trending.vue'
 | 
			
		||||
import Popular from '../views/Popular/Popular.vue'
 | 
			
		||||
import UserPlaylists from '../views/UserPlaylists/UserPlaylists.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +34,32 @@ const router = new Router({
 | 
			
		|||
      },
 | 
			
		||||
      component: Subscriptions
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/settings/profile',
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: 'Profile Settings',
 | 
			
		||||
        icon: 'fa-home'
 | 
			
		||||
      },
 | 
			
		||||
      component: ProfileSettings
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/settings/profile/new',
 | 
			
		||||
      name: 'newProfile',
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: 'New Profile',
 | 
			
		||||
        icon: 'fa-home'
 | 
			
		||||
      },
 | 
			
		||||
      component: ProfileEdit
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/settings/profile/edit/:id',
 | 
			
		||||
      name: 'editProfile',
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: 'Edit Profile',
 | 
			
		||||
        icon: 'fa-home'
 | 
			
		||||
      },
 | 
			
		||||
      component: ProfileEdit
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/trending',
 | 
			
		||||
      meta: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,146 @@
 | 
			
		|||
import Datastore from 'nedb'
 | 
			
		||||
 | 
			
		||||
let dbLocation
 | 
			
		||||
 | 
			
		||||
if (window && window.process && window.process.type === 'renderer') {
 | 
			
		||||
  // Electron is being used
 | 
			
		||||
  /* let dbLocation = localStorage.getItem('dbLocation')
 | 
			
		||||
 | 
			
		||||
  if (dbLocation === null) {
 | 
			
		||||
    const electron = require('electron')
 | 
			
		||||
    dbLocation = electron.remote.app.getPath('userData')
 | 
			
		||||
  } */
 | 
			
		||||
 | 
			
		||||
  const electron = require('electron')
 | 
			
		||||
  dbLocation = electron.remote.app.getPath('userData')
 | 
			
		||||
 | 
			
		||||
  dbLocation = dbLocation + '/profiles.db'
 | 
			
		||||
} else {
 | 
			
		||||
  dbLocation = 'profiles.db'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const profileDb = new Datastore({
 | 
			
		||||
  filename: dbLocation,
 | 
			
		||||
  autoload: true
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const state = {
 | 
			
		||||
  profileList: [{
 | 
			
		||||
    _id: 'allChannels',
 | 
			
		||||
    name: 'All Channels',
 | 
			
		||||
    bgColor: '#000000',
 | 
			
		||||
    textColor: '#FFFFFF',
 | 
			
		||||
    subscriptions: []
 | 
			
		||||
  }],
 | 
			
		||||
  activeProfile: 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getters = {
 | 
			
		||||
  getProfileList: () => {
 | 
			
		||||
    return state.profileList
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getActiveProfile: () => {
 | 
			
		||||
    return state.activeProfile
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  grabAllProfiles ({ dispatch, commit }, defaultName = null) {
 | 
			
		||||
    profileDb.find({}, (err, results) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        if (results.length === 0) {
 | 
			
		||||
          dispatch('createDefaultProfile', defaultName)
 | 
			
		||||
        } else {
 | 
			
		||||
          // We want the primary profile to always be first
 | 
			
		||||
          // So sort with that then sort alphabetically by profile name
 | 
			
		||||
          const profiles = results.sort((a, b) => {
 | 
			
		||||
            if (a._id === 'allChannels') {
 | 
			
		||||
              return -1
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (b._id === 'allChannels') {
 | 
			
		||||
              return 1
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return b.name - a.name
 | 
			
		||||
          })
 | 
			
		||||
          commit('setProfileList', profiles)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  grabProfileInfo (_, profileId) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      console.log(profileId)
 | 
			
		||||
      profileDb.findOne({ _id: profileId }, (err, results) => {
 | 
			
		||||
        if (!err) {
 | 
			
		||||
          resolve(results)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async createDefaultProfile ({ dispatch }, defaultName) {
 | 
			
		||||
    const randomColor = await dispatch('getRandomColor')
 | 
			
		||||
    const textColor = await dispatch('calculateColorLuminance', randomColor)
 | 
			
		||||
    const defaultProfile = {
 | 
			
		||||
      _id: 'allChannels',
 | 
			
		||||
      name: defaultName,
 | 
			
		||||
      bgColor: randomColor,
 | 
			
		||||
      textColor: textColor,
 | 
			
		||||
      subscriptions: []
 | 
			
		||||
    }
 | 
			
		||||
    console.log(defaultProfile)
 | 
			
		||||
    profileDb.update({ _id: 'allChannels' }, defaultProfile, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        dispatch('grabAllProfiles')
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateProfile ({ dispatch }, profile) {
 | 
			
		||||
    profileDb.update({ _id: profile._id }, profile, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        dispatch('grabAllProfiles')
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  insertProfile ({ dispatch }, profile) {
 | 
			
		||||
    profileDb.insert(profile, (err, newDocs) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        dispatch('grabAllProfiles')
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  removeProfile ({ dispatch }, profileId) {
 | 
			
		||||
    profileDb.remove({ _id: profileId }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        dispatch('grabAllProfiles')
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateActiveProfile ({ commit }, index) {
 | 
			
		||||
    commit('setActiveProfile', index)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  setProfileList (state, profileList) {
 | 
			
		||||
    state.profileList = profileList
 | 
			
		||||
  },
 | 
			
		||||
  setActiveProfile (state, activeProfile) {
 | 
			
		||||
    state.activeProfile = activeProfile
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  state,
 | 
			
		||||
  getters,
 | 
			
		||||
  actions,
 | 
			
		||||
  mutations
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ const state = {
 | 
			
		|||
  listType: 'grid',
 | 
			
		||||
  thumbnailPreference: '',
 | 
			
		||||
  invidiousInstance: 'https://invidio.us',
 | 
			
		||||
  defaultProfile: 'allChannels',
 | 
			
		||||
  barColor: false,
 | 
			
		||||
  enableSearchSuggestions: true,
 | 
			
		||||
  rememberHistory: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +57,8 @@ const state = {
 | 
			
		|||
  debugMode: false,
 | 
			
		||||
  disctractionFreeMode: false,
 | 
			
		||||
  hideWatchedSubs: false,
 | 
			
		||||
  usingElectron: true,
 | 
			
		||||
  profileList: [{ name: 'All Channels', color: '#304FFE' }],
 | 
			
		||||
  defaultProfile: 'All Channels'
 | 
			
		||||
  useRssFeeds: false,
 | 
			
		||||
  usingElectron: true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getters = {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +102,10 @@ const getters = {
 | 
			
		|||
    return state.invidiousInstance
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getDefaultProfile: () => {
 | 
			
		||||
    return state.defaultProfile
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getRememberHistory: () => {
 | 
			
		||||
    return state.rememberHistory
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -154,13 +158,21 @@ const getters = {
 | 
			
		|||
    return state.defaultQuality
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getHideWatchedSubs: () => {
 | 
			
		||||
    return state.hideWatchedSubs
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getUseRssFeeds: () => {
 | 
			
		||||
    return state.useRssFeeds
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getUsingElectron: () => {
 | 
			
		||||
    return state.usingElectron
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  grabUserSettings ({ dispatch, commit }) {
 | 
			
		||||
  grabUserSettings ({ dispatch, commit, rootState }) {
 | 
			
		||||
    settingsDb.find({}, (err, results) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        console.log(results)
 | 
			
		||||
| 
						 | 
				
			
			@ -176,6 +188,9 @@ const actions = {
 | 
			
		|||
            case 'backendFallback':
 | 
			
		||||
              commit('setBackendFallback', result.value)
 | 
			
		||||
              break
 | 
			
		||||
            case 'defaultProfile':
 | 
			
		||||
              commit('setDefaultProfile', result.value)
 | 
			
		||||
              break
 | 
			
		||||
            case 'checkForUpdates':
 | 
			
		||||
              commit('setCheckForUpdates', result.value)
 | 
			
		||||
              break
 | 
			
		||||
| 
						 | 
				
			
			@ -200,6 +215,12 @@ const actions = {
 | 
			
		|||
            case 'barColor':
 | 
			
		||||
              commit('setBarColor', result.value)
 | 
			
		||||
              break
 | 
			
		||||
            case 'hideWatchedSubs':
 | 
			
		||||
              commit('setHideWatchedSubs', result.value)
 | 
			
		||||
              break
 | 
			
		||||
            case 'useRssFeeds':
 | 
			
		||||
              commit('setUseRssFeeds', result.value)
 | 
			
		||||
              break
 | 
			
		||||
            case 'rememberHistory':
 | 
			
		||||
              commit('setRememberHistory', result.value)
 | 
			
		||||
              break
 | 
			
		||||
| 
						 | 
				
			
			@ -255,6 +276,14 @@ const actions = {
 | 
			
		|||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateDefaultProfile ({ commit }, defaultProfile) {
 | 
			
		||||
    settingsDb.update({ _id: 'defaultProfile' }, { _id: 'defaultProfile', value: defaultProfile }, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        commit('setDefaultProfile', defaultProfile)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateBackendFallback ({ commit }, backendFallback) {
 | 
			
		||||
    settingsDb.update({ _id: 'backendFallback' }, { _id: 'backendFallback', value: backendFallback }, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
| 
						 | 
				
			
			@ -327,6 +356,22 @@ const actions = {
 | 
			
		|||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateHideWatchedSubs ({ commit }, hideWatchedSubs) {
 | 
			
		||||
    settingsDb.update({ _id: 'hideWatchedSubs' }, { _id: 'hideWatchedSubs', value: hideWatchedSubs }, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        commit('setHideWatchedSubs', hideWatchedSubs)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateUseRssFeeds ({ commit }, useRssFeeds) {
 | 
			
		||||
    settingsDb.update({ _id: 'useRssFeeds' }, { _id: 'useRssFeeds', value: useRssFeeds }, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        commit('setUseRssFeeds', useRssFeeds)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  updateRememberHistory ({ commit }, history) {
 | 
			
		||||
    settingsDb.update({ _id: 'rememberHistory' }, { _id: 'rememberHistory', value: history }, { upsert: true }, (err, numReplaced) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
| 
						 | 
				
			
			@ -448,6 +493,9 @@ const mutations = {
 | 
			
		|||
  setCurrentTheme (state, currentTheme) {
 | 
			
		||||
    state.barColor = currentTheme
 | 
			
		||||
  },
 | 
			
		||||
  setDefaultProfile (state, defaultProfile) {
 | 
			
		||||
    state.defaultProfile = defaultProfile
 | 
			
		||||
  },
 | 
			
		||||
  setBackendFallback (state, backendFallback) {
 | 
			
		||||
    state.backendFallback = backendFallback
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -529,6 +577,9 @@ const mutations = {
 | 
			
		|||
  setHideWatchedSubs (state, hideWatchedSubs) {
 | 
			
		||||
    state.hideWatchedSubs = hideWatchedSubs
 | 
			
		||||
  },
 | 
			
		||||
  setUseRssFeeds (state, useRssFeeds) {
 | 
			
		||||
    state.useRssFeeds = useRssFeeds
 | 
			
		||||
  },
 | 
			
		||||
  setUsingElectron (state, usingElectron) {
 | 
			
		||||
    state.usingElectron = usingElectron
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -537,9 +588,6 @@ const mutations = {
 | 
			
		|||
  },
 | 
			
		||||
  setProfileList (state, profileList) {
 | 
			
		||||
    state.profileList = profileList
 | 
			
		||||
  },
 | 
			
		||||
  setDefaultProfile (state, defaultProfile) {
 | 
			
		||||
    state.defaultProfile = defaultProfile
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,61 +1,38 @@
 | 
			
		|||
import Datastore from 'nedb'
 | 
			
		||||
 | 
			
		||||
let dbLocation
 | 
			
		||||
 | 
			
		||||
if (window && window.process && window.process.type === 'renderer') {
 | 
			
		||||
  // Electron is being used
 | 
			
		||||
  let dbLocation = localStorage.getItem('dbLocation')
 | 
			
		||||
 | 
			
		||||
  if (dbLocation === null) {
 | 
			
		||||
    const electron = require('electron')
 | 
			
		||||
    dbLocation = electron.remote.app.getPath('userData')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dbLocation += '/subscriptions.db'
 | 
			
		||||
} else {
 | 
			
		||||
  dbLocation = 'subscriptions.db'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const subDb = new Datastore({
 | 
			
		||||
  filename: dbLocation,
 | 
			
		||||
  autoload: true
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const state = {
 | 
			
		||||
  subscriptions: []
 | 
			
		||||
  allSubscriptionsList: [],
 | 
			
		||||
  profileSubscriptions: {
 | 
			
		||||
    activeProfile: 0,
 | 
			
		||||
    videoList: []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  addSubscription (state, payload) {
 | 
			
		||||
    state.subscriptions.push(payload)
 | 
			
		||||
const getters = {
 | 
			
		||||
  getAllSubscriptionsList: () => {
 | 
			
		||||
    return state.allSubscriptionsList
 | 
			
		||||
  },
 | 
			
		||||
  setSubscriptions (state, payload) {
 | 
			
		||||
    state.subscriptions = payload
 | 
			
		||||
  getProfileSubscriptions: () => {
 | 
			
		||||
    return state.profileSubscriptions
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  addSubscriptions ({ commit }, payload) {
 | 
			
		||||
    subDb.insert(payload, (err, payload) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        commit('addSubscription', payload)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  updateAllSubscriptionsList ({ commit }, subscriptions) {
 | 
			
		||||
    commit('setAllSubscriptionsList', subscriptions)
 | 
			
		||||
  },
 | 
			
		||||
  getSubscriptions ({ commit }, payload) {
 | 
			
		||||
    subDb.find({}, (err, payload) => {
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        commit('setSubscriptions', payload)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  removeSubscription ({ commit }, channelId) {
 | 
			
		||||
    subDb.remove({ channelId: channelId }, {}, () => {
 | 
			
		||||
      commit('setSubscriptions', this.state.subscriptions.filter(sub => sub.channelId !== channelId))
 | 
			
		||||
    })
 | 
			
		||||
  updateProfileSubscriptions ({ commit }, subscriptions) {
 | 
			
		||||
    commit('setProfileSubscriptions', subscriptions)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const getters = {}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  setAllSubscriptionsList (state, allSubscriptionsList) {
 | 
			
		||||
    state.allSubscriptionsList = allSubscriptionsList
 | 
			
		||||
  },
 | 
			
		||||
  setProfileSubscriptions (state, profileSubscriptions) {
 | 
			
		||||
    state.profileSubscriptions = profileSubscriptions
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  state,
 | 
			
		||||
  getters,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ const state = {
 | 
			
		|||
  sessionSearchHistory: [],
 | 
			
		||||
  popularCache: null,
 | 
			
		||||
  trendingCache: null,
 | 
			
		||||
  showProgressBar: false,
 | 
			
		||||
  progressBarPercentage: 0,
 | 
			
		||||
  searchSettings: {
 | 
			
		||||
    sortBy: 'relevance',
 | 
			
		||||
    time: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +30,24 @@ const state = {
 | 
			
		|||
    'mainAmber',
 | 
			
		||||
    'mainOrange',
 | 
			
		||||
    'mainDeepOrange'
 | 
			
		||||
  ],
 | 
			
		||||
  colorValues: [
 | 
			
		||||
    '#d50000',
 | 
			
		||||
    '#C51162',
 | 
			
		||||
    '#AA00FF',
 | 
			
		||||
    '#6200EA',
 | 
			
		||||
    '#304FFE',
 | 
			
		||||
    '#2962FF',
 | 
			
		||||
    '#0091EA',
 | 
			
		||||
    '#00B8D4',
 | 
			
		||||
    '#00BFA5',
 | 
			
		||||
    '#00C853',
 | 
			
		||||
    '#64DD17',
 | 
			
		||||
    '#AEEA00',
 | 
			
		||||
    '#FFD600',
 | 
			
		||||
    '#FFAB00',
 | 
			
		||||
    '#FF6D00',
 | 
			
		||||
    '#DD2C00'
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,15 +74,78 @@ const getters = {
 | 
			
		|||
 | 
			
		||||
  getSearchSettings () {
 | 
			
		||||
    return state.searchSettings
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getColorValues () {
 | 
			
		||||
    return state.colorValues
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getShowProgressBar () {
 | 
			
		||||
    return state.showProgressBar
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getProgressBarPercentage () {
 | 
			
		||||
    return state.progressBarPercentage
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  updateShowProgressBar ({ commit }, value) {
 | 
			
		||||
    commit('setShowProgressBar', value)
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getRandomColorClass () {
 | 
			
		||||
    const randomInt = Math.floor(Math.random() * state.colorClasses.length)
 | 
			
		||||
    return state.colorClasses[randomInt]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getRandomColor () {
 | 
			
		||||
    const randomInt = Math.floor(Math.random() * state.colorValues.length)
 | 
			
		||||
    return state.colorValues[randomInt]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  calculateColorLuminance (_, colorValue) {
 | 
			
		||||
    const cutHex = colorValue.substring(1, 7)
 | 
			
		||||
    const colorValueR = parseInt(cutHex.substring(0, 2), 16)
 | 
			
		||||
    const colorValueG = parseInt(cutHex.substring(2, 4), 16)
 | 
			
		||||
    const colorValueB = parseInt(cutHex.substring(4, 6), 16)
 | 
			
		||||
 | 
			
		||||
    const luminance = (0.299 * colorValueR + 0.587 * colorValueG + 0.114 * colorValueB) / 255
 | 
			
		||||
 | 
			
		||||
    if (luminance > 0.5) {
 | 
			
		||||
      return '#000000'
 | 
			
		||||
    } else {
 | 
			
		||||
      return '#FFFFFF'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  calculatePublishedDate(_, publishedText) {
 | 
			
		||||
    const date = new Date()
 | 
			
		||||
 | 
			
		||||
    const textSplit = publishedText.split(' ')
 | 
			
		||||
    const timeFrame = textSplit[1]
 | 
			
		||||
    const timeAmount = parseInt(textSplit[0])
 | 
			
		||||
    let timeSpan = null
 | 
			
		||||
 | 
			
		||||
    if (timeFrame.indexOf('second') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 1000
 | 
			
		||||
    } else if (timeFrame.indexOf('minute') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 60000
 | 
			
		||||
    } else if (timeFrame.indexOf('hour') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 3600000
 | 
			
		||||
    } else if (timeFrame.indexOf('day') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 86400000
 | 
			
		||||
    } else if (timeFrame.indexOf('week') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 604800000
 | 
			
		||||
    } else if (timeFrame.indexOf('month') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 2592000000
 | 
			
		||||
    } else if (timeFrame.indexOf('year') > -1) {
 | 
			
		||||
      timeSpan = timeAmount * 31556952000
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return date.getTime() - timeSpan
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getVideoIdFromUrl (_, url) {
 | 
			
		||||
    /** @type {URL} */
 | 
			
		||||
    let urlObject
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +265,8 @@ const actions = {
 | 
			
		|||
    } else if (payload.isUpcoming || payload.publishText === null) {
 | 
			
		||||
      // the check for null is currently just an inferring of knowledge, because there is no other possibility left
 | 
			
		||||
      return payload.upcomingString
 | 
			
		||||
    } else if (payload.isRSS) {
 | 
			
		||||
      return payload.publishText
 | 
			
		||||
    }
 | 
			
		||||
    const strings = payload.publishText.split(' ')
 | 
			
		||||
    const singular = (strings[0] === '1')
 | 
			
		||||
| 
						 | 
				
			
			@ -254,6 +339,14 @@ const mutations = {
 | 
			
		|||
    state.isSideNavOpen = !state.isSideNavOpen
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setShowProgressBar (state, value) {
 | 
			
		||||
    state.showProgressBar = value
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setProgressBarPercentage (state, value) {
 | 
			
		||||
    state.progressBarPercentage = value
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setSessionSearchHistory (state, history) {
 | 
			
		||||
    state.sessionSearchHistory = history
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,34 @@ export default Vue.extend({
 | 
			
		|||
      return this.$store.getters.getSessionSearchHistory
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    isSubscribed: function () {
 | 
			
		||||
      const subIndex = this.profileList[this.activeProfile].subscriptions.findIndex((channel) => {
 | 
			
		||||
        return channel.id === this.id
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (subIndex === -1) {
 | 
			
		||||
        return false
 | 
			
		||||
      } else {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    subscribedText: function () {
 | 
			
		||||
      if (this.isSubscribed) {
 | 
			
		||||
        return this.$t('Channel.Unsubscribe').toUpperCase()
 | 
			
		||||
      } else {
 | 
			
		||||
        return this.$t('Channel.Subscribe').toUpperCase()
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    videoSelectNames: function () {
 | 
			
		||||
      return [
 | 
			
		||||
        this.$t('Channel.Videos.Sort Types.Newest'),
 | 
			
		||||
| 
						 | 
				
			
			@ -408,7 +436,74 @@ export default Vue.extend({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    handleSubscription: function () {
 | 
			
		||||
      console.log('TODO: Channel handleSubscription')
 | 
			
		||||
      const currentProfile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile]))
 | 
			
		||||
      const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
 | 
			
		||||
 | 
			
		||||
      if (this.isSubscribed) {
 | 
			
		||||
        currentProfile.subscriptions = currentProfile.subscriptions.filter((channel) => {
 | 
			
		||||
          return channel.id !== this.id
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        this.updateProfile(currentProfile)
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: 'Channel has been removed from your subscriptions'
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (this.activeProfile === 0) {
 | 
			
		||||
          // Check if a subscription exists in a different profile.
 | 
			
		||||
          // Remove from there as well.
 | 
			
		||||
          let duplicateSubscriptions = 0
 | 
			
		||||
 | 
			
		||||
          this.profileList.forEach((profile) => {
 | 
			
		||||
            if (profile._id === 'allChannels') {
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
            const parsedProfile = JSON.parse(JSON.stringify(profile))
 | 
			
		||||
            const index = parsedProfile.subscriptions.findIndex((channel) => {
 | 
			
		||||
              return channel.id === this.id
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
              duplicateSubscriptions++
 | 
			
		||||
 | 
			
		||||
              parsedProfile.subscriptions = parsedProfile.subscriptions.filter((x) => {
 | 
			
		||||
                return x.id !== this.channelId
 | 
			
		||||
              })
 | 
			
		||||
 | 
			
		||||
              this.updateProfile(parsedProfile)
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          if (duplicateSubscriptions > 0) {
 | 
			
		||||
            this.showToast({
 | 
			
		||||
              message: `Removed subscription from ${duplicateSubscriptions} other channel(s)`
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        const subscription = {
 | 
			
		||||
          id: this.id,
 | 
			
		||||
          name: this.channelName,
 | 
			
		||||
          thumbnail: this.thumbnailUrl
 | 
			
		||||
        }
 | 
			
		||||
        currentProfile.subscriptions.push(subscription)
 | 
			
		||||
 | 
			
		||||
        this.updateProfile(currentProfile)
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: 'Added channel to your subscriptions'
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (this.activeProfile !== 0) {
 | 
			
		||||
          const index = primaryProfile.subscriptions.findIndex((channel) => {
 | 
			
		||||
            return channel.id === this.id
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          if (index === -1) {
 | 
			
		||||
            primaryProfile.subscriptions.push(subscription)
 | 
			
		||||
            this.updateProfile(primaryProfile)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    handleFetchMore: function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -549,7 +644,8 @@ export default Vue.extend({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'showToast'
 | 
			
		||||
      'showToast',
 | 
			
		||||
      'updateProfile'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,7 @@
 | 
			
		|||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <ft-button
 | 
			
		||||
          :label="$t('Channel.Subscribe')"
 | 
			
		||||
          :label="subscribedText"
 | 
			
		||||
          background-color="var(--primary-color)"
 | 
			
		||||
          text-color="var(--text-with-main-color)"
 | 
			
		||||
          class="subscribeButton"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
.card {
 | 
			
		||||
  width: 85%;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  margin-bottom: 60px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.message {
 | 
			
		||||
  color: var(--tertiary-text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileName {
 | 
			
		||||
  width: 400px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bottomMargin {
 | 
			
		||||
  margin-bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.colorOptions {
 | 
			
		||||
  max-width: 1000px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  margin-bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.colorOption {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border-radius: 200px 200px 200px 200px;
 | 
			
		||||
  -webkit-border-radius: 200px 200px 200px 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.initial {
 | 
			
		||||
  font-size: 50px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  bottom: 27px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 680px) {
 | 
			
		||||
  .card {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,183 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import { mapActions } from 'vuex'
 | 
			
		||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
 | 
			
		||||
import FtCard from '../../components/ft-card/ft-card.vue'
 | 
			
		||||
import FtPrompt from '../../components/ft-prompt/ft-prompt.vue'
 | 
			
		||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
 | 
			
		||||
import FtInput from '../../components/ft-input/ft-input.vue'
 | 
			
		||||
import FtButton from '../../components/ft-button/ft-button.vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'ProfileEdit',
 | 
			
		||||
  components: {
 | 
			
		||||
    'ft-loader': FtLoader,
 | 
			
		||||
    'ft-card': FtCard,
 | 
			
		||||
    'ft-prompt': FtPrompt,
 | 
			
		||||
    'ft-flex-box': FtFlexBox,
 | 
			
		||||
    'ft-input': FtInput,
 | 
			
		||||
    'ft-button': FtButton
 | 
			
		||||
  },
 | 
			
		||||
  data: function () {
 | 
			
		||||
    return {
 | 
			
		||||
      isLoading: false,
 | 
			
		||||
      showDeletePrompt: false,
 | 
			
		||||
      deletePromptLabel: '',
 | 
			
		||||
      isNew: false,
 | 
			
		||||
      profileId: '',
 | 
			
		||||
      profileName: '',
 | 
			
		||||
      profileBgColor: '',
 | 
			
		||||
      profileTextColor: '',
 | 
			
		||||
      profileSubscriptions: [],
 | 
			
		||||
      deletePromptValues: [
 | 
			
		||||
        'yes',
 | 
			
		||||
        'no'
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    colorValues: function () {
 | 
			
		||||
      return this.$store.getters.getColorValues
 | 
			
		||||
    },
 | 
			
		||||
    profileInitial: function () {
 | 
			
		||||
      return this.profileName.slice(0, 1).toUpperCase()
 | 
			
		||||
    },
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
    defaultProfile: function () {
 | 
			
		||||
      return this.$store.getters.getDefaultProfile
 | 
			
		||||
    },
 | 
			
		||||
    deletePromptNames: function () {
 | 
			
		||||
      return [
 | 
			
		||||
        this.$t('Yes'),
 | 
			
		||||
        this.$t('No')
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    profileBgColor: async function (val) {
 | 
			
		||||
      this.profileTextColor = await this.calculateColorLuminance(val)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: async function () {
 | 
			
		||||
    this.isLoading = true
 | 
			
		||||
    const profileType = this.$route.name
 | 
			
		||||
 | 
			
		||||
    this.deletePromptLabel = `${this.$t('Profile.Are you sure you want to delete this profile?')} ${this.$t('Profile["All subscriptions will also be deleted."]')}`
 | 
			
		||||
 | 
			
		||||
    if (profileType === 'newProfile') {
 | 
			
		||||
      this.isNew = true
 | 
			
		||||
      this.profileBgColor = await this.getRandomColor()
 | 
			
		||||
      this.isLoading = false
 | 
			
		||||
    } else {
 | 
			
		||||
      this.isNew = false
 | 
			
		||||
      this.profileId = this.$route.params.id
 | 
			
		||||
      console.log(this.profileId)
 | 
			
		||||
      console.log(this.$route.name)
 | 
			
		||||
 | 
			
		||||
      this.grabProfileInfo(this.profileId).then((profile) => {
 | 
			
		||||
        if (profile === null) {
 | 
			
		||||
          this.showToast({
 | 
			
		||||
            message: this.$t('Profile.Profile could not be found')
 | 
			
		||||
          })
 | 
			
		||||
          this.$router.push({
 | 
			
		||||
            path: '/settings/profile/'
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        this.profileName = profile.name
 | 
			
		||||
        this.profileBgColor = profile.bgColor
 | 
			
		||||
        this.profileTextColor = profile.textColor
 | 
			
		||||
        this.profileSubscriptions = profile.subscriptions
 | 
			
		||||
        this.isLoading = false
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    openDeletePrompt: function () {
 | 
			
		||||
      this.showDeletePrompt = true
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    handleDeletePrompt: function (response) {
 | 
			
		||||
      if (response === 'yes') {
 | 
			
		||||
        this.deleteProfile()
 | 
			
		||||
      } else {
 | 
			
		||||
        this.showDeletePrompt = false
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    saveProfile: function () {
 | 
			
		||||
      if (this.profileName === '') {
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: this.$t('Profile.Your profile name cannot be empty')
 | 
			
		||||
        })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const profile = {
 | 
			
		||||
        name: this.profileName,
 | 
			
		||||
        bgColor: this.profileBgColor,
 | 
			
		||||
        textColor: this.profileTextColor,
 | 
			
		||||
        subscriptions: this.profileSubscriptions
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this.isNew) {
 | 
			
		||||
        profile._id = this.profileId
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      console.log(profile)
 | 
			
		||||
 | 
			
		||||
      this.updateProfile(profile)
 | 
			
		||||
 | 
			
		||||
      if (this.isNew) {
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: this.$t('Profile.Profile has been created')
 | 
			
		||||
        })
 | 
			
		||||
        this.$router.push({
 | 
			
		||||
          path: '/settings/profile/'
 | 
			
		||||
        })
 | 
			
		||||
      } else {
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: this.$t('Profile.Profile has been updated')
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setDefaultProfile: function () {
 | 
			
		||||
      this.updateDefaultProfile(this.profileId)
 | 
			
		||||
      const message = this.$t('Profile.Your default profile has been set to $').replace('$', this.profileName)
 | 
			
		||||
      this.showToast({
 | 
			
		||||
        message: message
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    deleteProfile: function () {
 | 
			
		||||
      this.removeProfile(this.profileId)
 | 
			
		||||
      const message = this.$t('Profile.Removed $ from your profiles').replace('$', this.profileName)
 | 
			
		||||
      this.showToast({
 | 
			
		||||
        message: message
 | 
			
		||||
      })
 | 
			
		||||
      if (this.defaultProfile === this.profileId) {
 | 
			
		||||
        this.updateDefaultProfile('allChannels')
 | 
			
		||||
        this.showToast({
 | 
			
		||||
          message: this.$t('Profile.Your default profile has been changed to your primary profile')
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      if (this.activeProfile._id === this.profileId) {
 | 
			
		||||
        this.updateActiveProfile('allChannels')
 | 
			
		||||
      }
 | 
			
		||||
      this.$router.push({
 | 
			
		||||
        path: '/settings/profile/'
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'showToast',
 | 
			
		||||
      'grabProfileInfo',
 | 
			
		||||
      'updateProfile',
 | 
			
		||||
      'removeProfile',
 | 
			
		||||
      'updateDefaultProfile',
 | 
			
		||||
      'updateActiveProfile',
 | 
			
		||||
      'calculateColorLuminance',
 | 
			
		||||
      'getRandomColor'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ft-loader
 | 
			
		||||
      v-if="isLoading"
 | 
			
		||||
      :fullscreen="true"
 | 
			
		||||
    />
 | 
			
		||||
    <div
 | 
			
		||||
      v-else
 | 
			
		||||
    >
 | 
			
		||||
      <ft-card class="card">
 | 
			
		||||
        <h2>{{ $t("Profile.Edit Profile") }}</h2>
 | 
			
		||||
        <ft-flex-box>
 | 
			
		||||
          <ft-input
 | 
			
		||||
            class="profileName"
 | 
			
		||||
            placeholder="Profile Name"
 | 
			
		||||
            :value="profileName"
 | 
			
		||||
            :show-arrow="false"
 | 
			
		||||
            @input="e => profileName = e"
 | 
			
		||||
          />
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
        <h3>{{ $t("Profile.Color Picker") }}</h3>
 | 
			
		||||
        <ft-flex-box
 | 
			
		||||
          class="bottomMargin colorOptions"
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="(color, index) in colorValues"
 | 
			
		||||
            :key="index"
 | 
			
		||||
            class="colorOption"
 | 
			
		||||
            :style="{ background: color }"
 | 
			
		||||
            @click="profileBgColor = color"
 | 
			
		||||
          />
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
        <ft-flex-box
 | 
			
		||||
          class="bottomMargin"
 | 
			
		||||
        >
 | 
			
		||||
          <div>
 | 
			
		||||
            <label for="colorPicker">{{ $t("Profile.Custom Color") }}</label>
 | 
			
		||||
            <input
 | 
			
		||||
              id="colorPicker"
 | 
			
		||||
              type="color"
 | 
			
		||||
              v-model="profileBgColor"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
        <ft-flex-box>
 | 
			
		||||
          <ft-input
 | 
			
		||||
            class="profileName"
 | 
			
		||||
            placeholder=""
 | 
			
		||||
            :value="profileBgColor"
 | 
			
		||||
            :show-arrow="false"
 | 
			
		||||
            :disabled="true"
 | 
			
		||||
          />
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
        <h3>{{ $t("Profile.Profile Preview") }}</h3>
 | 
			
		||||
        <ft-flex-box
 | 
			
		||||
          class="bottomMargin"
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            class="colorOption"
 | 
			
		||||
            :style="{ background: profileBgColor, color: profileTextColor }"
 | 
			
		||||
            style="cursor: default"
 | 
			
		||||
          >
 | 
			
		||||
            <p
 | 
			
		||||
              class="initial"
 | 
			
		||||
            >
 | 
			
		||||
              {{ profileInitial }}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
        <ft-flex-box>
 | 
			
		||||
          <ft-button
 | 
			
		||||
            v-if="isNew"
 | 
			
		||||
            :label="$t('Profile.Create Profile')"
 | 
			
		||||
            @click="saveProfile"
 | 
			
		||||
          />
 | 
			
		||||
          <ft-button
 | 
			
		||||
            v-if="!isNew"
 | 
			
		||||
            :label="$t('Profile.Update Profile')"
 | 
			
		||||
            @click="saveProfile"
 | 
			
		||||
          />
 | 
			
		||||
          <ft-button
 | 
			
		||||
            v-if="!isNew"
 | 
			
		||||
            :label="$t('Profile.Make Default Profile')"
 | 
			
		||||
            @click="setDefaultProfile"
 | 
			
		||||
          />
 | 
			
		||||
          <ft-button
 | 
			
		||||
            v-if="profileId !== 'allChannels' && !isNew"
 | 
			
		||||
            :label="$t('Profile.Delete Profile')"
 | 
			
		||||
            text-color="var(--text-with-main-color)"
 | 
			
		||||
            background-color="var(--primary-color)"
 | 
			
		||||
            @click="openDeletePrompt"
 | 
			
		||||
          />
 | 
			
		||||
        </ft-flex-box>
 | 
			
		||||
      </ft-card>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ft-prompt
 | 
			
		||||
      v-if="showDeletePrompt"
 | 
			
		||||
      :label="deletePromptLabel"
 | 
			
		||||
      :option-names="deletePromptNames"
 | 
			
		||||
      :option-values="deletePromptValues"
 | 
			
		||||
      @click="handleDeletePrompt"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./ProfileEdit.js" />
 | 
			
		||||
<style scoped src="./ProfileEdit.css" />
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
.card {
 | 
			
		||||
  width: 85%;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  margin-bottom: 60px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.message {
 | 
			
		||||
  color: var(--tertiary-text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profileList {
 | 
			
		||||
  max-width: 1000px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 680px) {
 | 
			
		||||
  .card {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import FtCard from '../../components/ft-card/ft-card.vue'
 | 
			
		||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
 | 
			
		||||
import FtProfileBubble from '../../components/ft-profile-bubble/ft-profile-bubble.vue'
 | 
			
		||||
import FtButton from '../../components/ft-button/ft-button.vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'ProfileSettings',
 | 
			
		||||
  components: {
 | 
			
		||||
    'ft-card': FtCard,
 | 
			
		||||
    'ft-flex-box': FtFlexBox,
 | 
			
		||||
    'ft-profile-bubble': FtProfileBubble,
 | 
			
		||||
    'ft-button': FtButton
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
    console.log(this.profileList)
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    newProfile: function () {
 | 
			
		||||
      this.$router.push({
 | 
			
		||||
        path: `/settings/profile/new/`
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ft-card class="card">
 | 
			
		||||
      <h3>{{ $t("Profile.Profile Manager") }}</h3>
 | 
			
		||||
      <ft-flex-box
 | 
			
		||||
        class="profileList"
 | 
			
		||||
      >
 | 
			
		||||
        <ft-profile-bubble
 | 
			
		||||
          v-for="(profile, index) in profileList"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          :profile-id="profile._id"
 | 
			
		||||
          :profile-name="profile.name"
 | 
			
		||||
          :background-color="profile.bgColor"
 | 
			
		||||
          :text-color="profile.textColor"
 | 
			
		||||
        />
 | 
			
		||||
      </ft-flex-box>
 | 
			
		||||
      <ft-flex-box>
 | 
			
		||||
        <ft-button
 | 
			
		||||
          :label="$t('Profile.Create New Profile')"
 | 
			
		||||
          @click="newProfile"
 | 
			
		||||
        />
 | 
			
		||||
      </ft-flex-box>
 | 
			
		||||
    </ft-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./ProfileSettings.js" />
 | 
			
		||||
<style scoped src="./ProfileSettings.css" />
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
    <general-settings />
 | 
			
		||||
    <theme-settings />
 | 
			
		||||
    <player-settings />
 | 
			
		||||
    <subscription-settings />
 | 
			
		||||
    <privacy-settings />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,12 @@
 | 
			
		|||
  color: var(--tertiary-text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.floatingTopButton {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 70px;
 | 
			
		||||
  right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 680px) {
 | 
			
		||||
  .card {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,307 @@
 | 
			
		|||
import Vue from 'vue'
 | 
			
		||||
import { mapActions, mapMutations } from 'vuex'
 | 
			
		||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
 | 
			
		||||
import FtCard from '../../components/ft-card/ft-card.vue'
 | 
			
		||||
import FtButton from '../../components/ft-button/ft-button.vue'
 | 
			
		||||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
 | 
			
		||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
 | 
			
		||||
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
 | 
			
		||||
 | 
			
		||||
import ytch from 'yt-channel-info'
 | 
			
		||||
import Parser from 'rss-parser'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'Subscriptions',
 | 
			
		||||
  components: {
 | 
			
		||||
    'ft-loader': FtLoader,
 | 
			
		||||
    'ft-card': FtCard,
 | 
			
		||||
    'ft-button': FtButton,
 | 
			
		||||
    'ft-icon-button': FtIconButton,
 | 
			
		||||
    'ft-flex-box': FtFlexBox,
 | 
			
		||||
    'ft-element-list': FtElementList
 | 
			
		||||
  },
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
  data: function () {
 | 
			
		||||
    return {
 | 
			
		||||
      isLoading: false,
 | 
			
		||||
      dataLimit: 100,
 | 
			
		||||
      videoList: []
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    usingElectron: function () {
 | 
			
		||||
      return this.$store.getters.getUsingElectron
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    backendPreference: function () {
 | 
			
		||||
      return this.$store.getters.getBackendPreference
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    backendFallback: function () {
 | 
			
		||||
      return this.$store.getters.getBackendFallback
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    invidiousInstance: function () {
 | 
			
		||||
      return this.$store.getters.getInvidiousInstance
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    hideWatchedSubs: function () {
 | 
			
		||||
      return this.$store.getters.getHideWatchedSubs
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    useRssFeeds: function () {
 | 
			
		||||
      return this.$store.getters.getUseRssFeeds
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    profileList: function () {
 | 
			
		||||
      return this.$store.getters.getProfileList
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    activeVideoList: function () {
 | 
			
		||||
      if (this.videoList.length < this.dataLimit) {
 | 
			
		||||
        return this.videoList
 | 
			
		||||
      } else {
 | 
			
		||||
        return this.videoList.slice(0, this.dataLimit)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    activeProfile: function () {
 | 
			
		||||
      return this.$store.getters.getActiveProfile
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    profileSubscriptions: function () {
 | 
			
		||||
      return this.$store.getters.getProfileSubscriptions
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    allSubscriptionsList: function () {
 | 
			
		||||
      return this.$store.getters.getAllSubscriptionsList
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    historyCache: function () {
 | 
			
		||||
      return this.$store.getters.getHistoryCache
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    activeSubscriptionList: function () {
 | 
			
		||||
      return this.profileList[this.activeProfile].subscriptions
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    activeProfile: async function (val) {
 | 
			
		||||
      if (this.allSubscriptionsList.length !== 0) {
 | 
			
		||||
        this.isLoading = true
 | 
			
		||||
        this.videoList = await Promise.all(this.allSubscriptionsList.filter((video) => {
 | 
			
		||||
          const channelIndex = this.activeSubscriptionList.findIndex((x) => {
 | 
			
		||||
            return x.id === video.authorId
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          const historyIndex = this.historyCache.findIndex((x) => {
 | 
			
		||||
            return x.videoId === video.videoId
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          if (this.hideWatchedSubs) {
 | 
			
		||||
            return channelIndex !== -1 && historyIndex === -1
 | 
			
		||||
          } else {
 | 
			
		||||
            return channelIndex !== -1
 | 
			
		||||
          }
 | 
			
		||||
        }))
 | 
			
		||||
        this.isLoading = false
 | 
			
		||||
      } else {
 | 
			
		||||
        this.getSubscriptions()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted: async function () {
 | 
			
		||||
    this.isLoading = true
 | 
			
		||||
    const dataLimit = sessionStorage.getItem('subscriptionLimit')
 | 
			
		||||
    if (dataLimit !== null) {
 | 
			
		||||
      this.dataLimit = dataLimit
 | 
			
		||||
    }
 | 
			
		||||
    setTimeout(async () => {
 | 
			
		||||
      if (this.profileSubscriptions.videoList.length === 0) {
 | 
			
		||||
        this.getSubscriptions()
 | 
			
		||||
      } else {
 | 
			
		||||
        const subscriptionList = JSON.parse(JSON.stringify(this.profileSubscriptions))
 | 
			
		||||
        if (this.hideWatchedSubs) {
 | 
			
		||||
          this.videoList = await Promise.all(subscriptionList.videoList.filter((video) => {
 | 
			
		||||
            const historyIndex = this.historyCache.findIndex((x) => {
 | 
			
		||||
              return x.videoId === video.videoId
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            return historyIndex === -1
 | 
			
		||||
          }))
 | 
			
		||||
        } else {
 | 
			
		||||
          this.videoList = subscriptionList.videoList
 | 
			
		||||
        }
 | 
			
		||||
        this.isLoading = false
 | 
			
		||||
      }
 | 
			
		||||
    }, 200)
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    getSubscriptions: function () {
 | 
			
		||||
      if (this.activeSubscriptionList.length === 0) {
 | 
			
		||||
        this.isLoading = false
 | 
			
		||||
        this.videoList = []
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      this.isLoading = true
 | 
			
		||||
      this.updateShowProgressBar(true)
 | 
			
		||||
      this.setProgressBarPercentage(0)
 | 
			
		||||
 | 
			
		||||
      let videoList = []
 | 
			
		||||
      let channelCount = 0
 | 
			
		||||
 | 
			
		||||
      this.activeSubscriptionList.forEach(async (channel) => {
 | 
			
		||||
        let videos = []
 | 
			
		||||
 | 
			
		||||
        if (!this.usingElectron || this.backendPreference === 'invidious') {
 | 
			
		||||
          if (this.useRssFeeds) {
 | 
			
		||||
            videos = await this.getChannelVideosInvidiousRSS(channel.id)
 | 
			
		||||
          } else {
 | 
			
		||||
            videos = await this.getChannelVideosInvidiousScraper(channel.id)
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (this.useRssFeeds) {
 | 
			
		||||
            videos = await this.getChannelVideosLocalRSS(channel.id)
 | 
			
		||||
          } else {
 | 
			
		||||
            videos = await this.getChannelVideosLocalScraper(channel.id)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        videoList = videoList.concat(videos)
 | 
			
		||||
        channelCount++
 | 
			
		||||
        const percentageComplete = (channelCount / this.activeSubscriptionList.length) * 100
 | 
			
		||||
        this.setProgressBarPercentage(percentageComplete)
 | 
			
		||||
 | 
			
		||||
        if (channelCount === this.activeSubscriptionList.length) {
 | 
			
		||||
          videoList = await Promise.all(videoList.sort((a, b) => {
 | 
			
		||||
            return b.publishedDate - a.publishedDate
 | 
			
		||||
          }))
 | 
			
		||||
 | 
			
		||||
          const profileSubscriptions = {
 | 
			
		||||
            activeProfile: this.activeProfile,
 | 
			
		||||
            videoList: videoList
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.videoList = await Promise.all(videoList.filter((video) => {
 | 
			
		||||
            if (this.hideWatchedSubs) {
 | 
			
		||||
              const historyIndex = this.historyCache.findIndex((x) => {
 | 
			
		||||
                return x.videoId === video.videoId
 | 
			
		||||
              })
 | 
			
		||||
 | 
			
		||||
              return historyIndex === -1
 | 
			
		||||
            } else {
 | 
			
		||||
              return true
 | 
			
		||||
            }
 | 
			
		||||
          }))
 | 
			
		||||
          this.updateProfileSubscriptions(profileSubscriptions)
 | 
			
		||||
          this.isLoading = false
 | 
			
		||||
          this.updateShowProgressBar(false)
 | 
			
		||||
 | 
			
		||||
          if (this.activeProfile === 0) {
 | 
			
		||||
            this.updateAllSubscriptionsList(profileSubscriptions.videoList)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getChannelVideosLocalScraper: function (channelId) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        ytch.getChannelVideos(channelId, 'latest').then(async (response) => {
 | 
			
		||||
          const videos = await Promise.all(response.items.map(async (video) => {
 | 
			
		||||
            video.publishedDate = await this.calculatePublishedDate(video.publishedText)
 | 
			
		||||
            return video
 | 
			
		||||
          }))
 | 
			
		||||
 | 
			
		||||
          resolve(videos)
 | 
			
		||||
        }).catch((err) => {
 | 
			
		||||
          console.log(err)
 | 
			
		||||
          const errorMessage = this.$t('Local API Error (Click to copy)')
 | 
			
		||||
          this.showToast({
 | 
			
		||||
            message: `${errorMessage}: ${err}`,
 | 
			
		||||
            time: 10000,
 | 
			
		||||
            action: () => {
 | 
			
		||||
              navigator.clipboard.writeText(err)
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          resolve([])
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getChannelVideosLocalRSS: function (channelId) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        const parser = new Parser()
 | 
			
		||||
        const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`
 | 
			
		||||
 | 
			
		||||
        parser.parseURL(feedUrl).then(async (feed) => {
 | 
			
		||||
          resolve(await Promise.all(feed.items.map((video) => {
 | 
			
		||||
            video.authorId = channelId
 | 
			
		||||
            video.videoId = video.id.replace('yt:video:', '')
 | 
			
		||||
            video.type = 'video'
 | 
			
		||||
            video.publishedDate = new Date(video.pubDate)
 | 
			
		||||
            video.publishedText = video.publishedDate.toLocaleString()
 | 
			
		||||
            video.lengthSeconds = '0:00'
 | 
			
		||||
            video.isRSS = true
 | 
			
		||||
 | 
			
		||||
            return video
 | 
			
		||||
          })))
 | 
			
		||||
        }).catch((err) => {
 | 
			
		||||
          console.log(err)
 | 
			
		||||
          resolve([])
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getChannelVideosInvidiousScraper: function (channelId) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        const subscriptionsPayload = {
 | 
			
		||||
          resource: 'channels/latest',
 | 
			
		||||
          id: channelId,
 | 
			
		||||
          params: {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.invidiousAPICall(subscriptionsPayload).then((result) => {
 | 
			
		||||
          resolve(result)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getChannelVideosInvidiousRSS: function (channelId) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        const parser = new Parser()
 | 
			
		||||
        const feedUrl = `${this.invidiousInstance}/feed/channel/${channelId}`
 | 
			
		||||
 | 
			
		||||
        parser.parseURL(feedUrl).then(async (feed) => {
 | 
			
		||||
          resolve(await Promise.all(feed.items.map((video) => {
 | 
			
		||||
            video.authorId = channelId
 | 
			
		||||
            video.videoId = video.id.replace('yt:video:', '')
 | 
			
		||||
            video.type = 'video'
 | 
			
		||||
            video.publishedDate = new Date(video.pubDate)
 | 
			
		||||
            video.publishedText = video.publishedDate.toLocaleString()
 | 
			
		||||
            video.lengthSeconds = '0:00'
 | 
			
		||||
            video.isRSS = true
 | 
			
		||||
 | 
			
		||||
            return video
 | 
			
		||||
          })))
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    increaseLimit: function () {
 | 
			
		||||
      this.dataLimit += 100
 | 
			
		||||
      sessionStorage.setItem('subscriptionLimit', this.dataLimit)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    ...mapActions([
 | 
			
		||||
      'showToast',
 | 
			
		||||
      'invidiousAPICall',
 | 
			
		||||
      'updateShowProgressBar',
 | 
			
		||||
      'updateProfileSubscriptions',
 | 
			
		||||
      'updateAllSubscriptionsList',
 | 
			
		||||
      'calculatePublishedDate'
 | 
			
		||||
    ]),
 | 
			
		||||
 | 
			
		||||
    ...mapMutations([
 | 
			
		||||
      'setProgressBarPercentage'
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,45 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ft-card class="card">
 | 
			
		||||
    <ft-loader
 | 
			
		||||
      v-if="isLoading"
 | 
			
		||||
      :fullscreen="true"
 | 
			
		||||
    />
 | 
			
		||||
    <ft-card
 | 
			
		||||
      v-else
 | 
			
		||||
      class="card"
 | 
			
		||||
    >
 | 
			
		||||
      <h3>{{ $t("Subscriptions.Subscriptions") }}</h3>
 | 
			
		||||
      <ft-flex-box>
 | 
			
		||||
      <ft-flex-box
 | 
			
		||||
        v-if="activeVideoList.length === 0"
 | 
			
		||||
      >
 | 
			
		||||
        <p class="message">
 | 
			
		||||
          {{ $t("This part of the app is not ready yet. Come back later when progress has been made.") }}
 | 
			
		||||
          {{ $t("Subscriptions['Your Subscription list is currently empty. Start adding subscriptions to see them here.']") }}
 | 
			
		||||
        </p>
 | 
			
		||||
      </ft-flex-box>
 | 
			
		||||
      <ft-element-list
 | 
			
		||||
        v-else
 | 
			
		||||
        :data="activeVideoList"
 | 
			
		||||
      />
 | 
			
		||||
      <ft-flex-box
 | 
			
		||||
      >
 | 
			
		||||
        <ft-button
 | 
			
		||||
          v-if="videoList.length > dataLimit"
 | 
			
		||||
          label="Load More"
 | 
			
		||||
          background-color="var(--primary-color)"
 | 
			
		||||
          text-color="var(--text-with-main-color)"
 | 
			
		||||
          @click="increaseLimit"
 | 
			
		||||
        />
 | 
			
		||||
      </ft-flex-box>
 | 
			
		||||
    </ft-card>
 | 
			
		||||
    <ft-icon-button
 | 
			
		||||
      v-if="!isLoading"
 | 
			
		||||
      icon="sync"
 | 
			
		||||
      class="floatingTopButton"
 | 
			
		||||
      :title="$t('Subscriptions.Refresh Subscriptions')"
 | 
			
		||||
      :size="12"
 | 
			
		||||
      theme="primary"
 | 
			
		||||
      @click="getSubscriptions"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,8 @@ Subscriptions:
 | 
			
		|||
  Latest Subscriptions: Latest Subscriptions
 | 
			
		||||
  'Your Subscription list is currently empty. Start adding subscriptions to see them here.': Your
 | 
			
		||||
    Subscription list is currently empty. Start adding subscriptions to see them here.
 | 
			
		||||
  'Getting Subscriptions.  Please wait.': Getting Subscriptions. Please wait.
 | 
			
		||||
  'Getting Subscriptions. Please wait.': Getting Subscriptions. Please wait.
 | 
			
		||||
  Refresh Subscriptions: Refresh Subscriptions
 | 
			
		||||
Trending: Trending
 | 
			
		||||
Most Popular: Most Popular
 | 
			
		||||
Playlists: Playlists
 | 
			
		||||
| 
						 | 
				
			
			@ -180,6 +181,7 @@ Settings:
 | 
			
		|||
  Subscription Settings:
 | 
			
		||||
    Subscription Settings: Subscription Settings
 | 
			
		||||
    Hide Videos on Watch: Hide Videos on Watch
 | 
			
		||||
    Fetch Feeds from RSS: Fetch Feeds from RSS
 | 
			
		||||
    Subscriptions Export Format:
 | 
			
		||||
      Subscriptions Export Format: Subscriptions Export Format
 | 
			
		||||
      #& Freetube
 | 
			
		||||
| 
						 | 
				
			
			@ -246,6 +248,28 @@ About:
 | 
			
		|||
 | 
			
		||||
  Latest FreeTube News: Latest FreeTube News
 | 
			
		||||
 | 
			
		||||
Profile:
 | 
			
		||||
  All Channels: All Channels
 | 
			
		||||
  Profile Manager: Profile Manager
 | 
			
		||||
  Create New Profile: Create New Profile
 | 
			
		||||
  Edit Profile: Edit Profile
 | 
			
		||||
  Color Picker: Color Picker
 | 
			
		||||
  Custom Color: Custom Color
 | 
			
		||||
  Profile Preview: Profile Preview
 | 
			
		||||
  Create Profile: Create Profile
 | 
			
		||||
  Update Profile: Update Profile
 | 
			
		||||
  Make Default Profile: Make Default Profile
 | 
			
		||||
  Delete Profile: Delete Profile
 | 
			
		||||
  Are you sure you want to delete this profile?: Are you sure you want to delete this profile?
 | 
			
		||||
  All subscriptions will also be deleted.: All subscriptions will also be deleted.
 | 
			
		||||
  Profile could not be found: Profile could not be found
 | 
			
		||||
  Your profile name cannot be empty: Your profile name cannot be empty
 | 
			
		||||
  Profile has been created: Profile has been created
 | 
			
		||||
  Profile has been updated: Profile has been updated
 | 
			
		||||
  Your default profile has been set to $: Your default profile has been set to $
 | 
			
		||||
  Removed $ from your profiles: Removed $ from your profiles
 | 
			
		||||
  Your default profile has been changed to your primary profile: Your default profile has been changed to your primary profile
 | 
			
		||||
  $ is now the active profile: $ is now the active profile
 | 
			
		||||
#On Channel Page
 | 
			
		||||
Channel:
 | 
			
		||||
  Subscriber: Subscriber
 | 
			
		||||
| 
						 | 
				
			
			@ -321,6 +345,8 @@ Video:
 | 
			
		|||
    Dec: Dec
 | 
			
		||||
    Second: Second
 | 
			
		||||
    Seconds: Seconds
 | 
			
		||||
    Minute: Minute
 | 
			
		||||
    Minutes: Minutes
 | 
			
		||||
    Hour: Hour
 | 
			
		||||
    Hours: Hours
 | 
			
		||||
    Day: Day
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue