Activity page subscribes to store & more css variables
This commit is contained in:
		| @@ -1,24 +1,28 @@ | ||||
| <template> | ||||
|   <div class="wrapper" v-if="hasPlexUser"> | ||||
|   <div class="wrapper" v-if="plexId"> | ||||
|     <h1>Your watch activity</h1> | ||||
|  | ||||
|     <div class="filter"> | ||||
|       <h2>Filter</h2> | ||||
|  | ||||
|       <div class="filter-item"> | ||||
|         <label class="desktop-only">Days:</label> | ||||
|         <input class="dayinput" | ||||
|     <div style="display: flex; flex-direction: row"> | ||||
|       <label class="filter"> | ||||
|         <span>Days:</span> | ||||
|         <input | ||||
|           class="dayinput" | ||||
|           v-model="days" | ||||
|           placeholder="number of days" | ||||
|           type="number" | ||||
|           pattern="[0-9]*" | ||||
|                :style="{maxWidth: `${3 + (0.5 * days.length)}rem`}"/> | ||||
| <!--         <datalist id="days"> | ||||
|           <option v-for="index in 1500" :value="index" :key="index"></option> | ||||
|         </datalist> --> | ||||
|       </div> | ||||
|           :style="{ maxWidth: `${3 + 0.5 * days.length}rem` }" | ||||
|         /> | ||||
|       </label> | ||||
|  | ||||
|       <toggle-button class="filter-item" :options="chartTypes" :selected.sync="selectedChartDataType" /> | ||||
|       <label class="filter"> | ||||
|         <span>Data sorted by:</span> | ||||
|         <toggle-button | ||||
|           class="filter-item" | ||||
|           :options="chartTypes" | ||||
|           :selected.sync="selectedChartDataType" | ||||
|         /> | ||||
|       </label> | ||||
|     </div> | ||||
|  | ||||
|     <div class="chart-section"> | ||||
| @@ -39,128 +43,137 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import store from '@/store' | ||||
| import ToggleButton from '@/components/ui/ToggleButton'; | ||||
| import { fetchChart } from '@/api' | ||||
| import { mapGetters } from "vuex"; | ||||
| import ToggleButton from "@/components/ui/ToggleButton"; | ||||
| import { fetchChart } from "@/api"; | ||||
|  | ||||
| var Chart = require('chart.js'); | ||||
| Chart.defaults.global.elements.point.radius = 0 | ||||
| Chart.defaults.global.elements.point.hitRadius = 10 | ||||
| Chart.defaults.global.elements.point.pointHoverRadius = 10 | ||||
| Chart.defaults.global.elements.point.hoverBorderWidth = 4 | ||||
| var Chart = require("chart.js"); | ||||
| Chart.defaults.global.elements.point.radius = 0; | ||||
| Chart.defaults.global.elements.point.hitRadius = 10; | ||||
| Chart.defaults.global.elements.point.pointHoverRadius = 10; | ||||
| Chart.defaults.global.elements.point.hoverBorderWidth = 4; | ||||
|  | ||||
| export default { | ||||
|   components: { ToggleButton }, | ||||
|   data() { | ||||
|     return { | ||||
|       days: 30, | ||||
|       selectedChartDataType: 'plays', | ||||
|       charts: [{ | ||||
|         name: 'Watch activity', | ||||
|         ref: 'activityCanvas', | ||||
|       selectedChartDataType: "plays", | ||||
|       charts: [ | ||||
|         { | ||||
|           name: "Watch activity", | ||||
|           ref: "activityCanvas", | ||||
|           data: null, | ||||
|         urlPath: '/plays_by_day', | ||||
|         graphType: 'line' | ||||
|       }, { | ||||
|         name: 'Plays by day of week', | ||||
|         ref: 'playsByDayOfWeekCanvas', | ||||
|           urlPath: "/plays_by_day", | ||||
|           graphType: "line" | ||||
|         }, | ||||
|         { | ||||
|           name: "Plays by day of week", | ||||
|           ref: "playsByDayOfWeekCanvas", | ||||
|           data: null, | ||||
|         urlPath: '/plays_by_dayofweek', | ||||
|         graphType: 'bar' | ||||
|       }], | ||||
|       chartData: [{ | ||||
|         type: 'plays', | ||||
|         tooltipLabel: 'Play count', | ||||
|       },{ | ||||
|         type: 'duration', | ||||
|         tooltipLabel: 'Watched duration', | ||||
|         valueConvertFunction: this.convertSecondsToHumanReadable | ||||
|       }], | ||||
|       gridColor: getComputedStyle(document.documentElement).getPropertyValue('--text-color-5') | ||||
|           urlPath: "/plays_by_dayofweek", | ||||
|           graphType: "bar" | ||||
|         } | ||||
|       ], | ||||
|       chartData: [ | ||||
|         { | ||||
|           type: "plays", | ||||
|           tooltipLabel: "Play count" | ||||
|         }, | ||||
|         { | ||||
|           type: "duration", | ||||
|           tooltipLabel: "Watched duration", | ||||
|           valueConvertFunction: this.convertSecondsToHumanReadable | ||||
|         } | ||||
|       ], | ||||
|       gridColor: getComputedStyle(document.documentElement).getPropertyValue( | ||||
|         "--text-color-5" | ||||
|       ) | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     hasPlexUser() { | ||||
|       return store.getters['userModule/plex_userid'] != null ? true : false | ||||
|     }, | ||||
|     ...mapGetters("user", ["plexId"]), | ||||
|     chartTypes() { | ||||
|       return this.chartData.map(chart => chart.type) | ||||
|       return this.chartData.map(chart => chart.type); | ||||
|     }, | ||||
|     selectedChartType() { | ||||
|       return this.chartData.filter(data => data.type == this.selectedChartDataType)[0] | ||||
|       return this.chartData.filter( | ||||
|         data => data.type == this.selectedChartDataType | ||||
|       )[0]; | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     hasPlexUser(newValue, oldValue) { | ||||
|       if (newValue != oldValue && newValue == true) { | ||||
|         this.fetchChartData(this.charts) | ||||
|       } | ||||
|     }, | ||||
|     days(newValue) { | ||||
|       if (newValue !== '') { | ||||
|         this.fetchChartData(this.charts) | ||||
|       if (newValue !== "") { | ||||
|         this.fetchChartData(this.charts); | ||||
|       } | ||||
|     }, | ||||
|     selectedChartDataType(selectedChartDataType) { | ||||
|       this.fetchChartData(this.charts) | ||||
|       this.fetchChartData(this.charts); | ||||
|     } | ||||
|   }, | ||||
|   beforeMount() { | ||||
|     if (typeof(this.days) == 'number') { | ||||
|       this.days = this.days.toString() | ||||
|     if (typeof this.days == "number") { | ||||
|       this.days = this.days.toString(); | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     fetchChartData(charts) { | ||||
|       if (this.hasPlexUser == false) { | ||||
|         return | ||||
|       if (!this.plexId) { | ||||
|         console.log("NF plexID:", this.plexId); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       for (let chart of charts) { | ||||
|  | ||||
|  | ||||
|  | ||||
|         fetchChart(chart.urlPath, this.days, this.selectedChartType.type) | ||||
|           .then(data => { | ||||
|             this.series = data.data.series.filter(group => group.name === 'TV')[0].data;      // plays pr date in groups (movie/tv/music) | ||||
|         fetchChart(chart.urlPath, this.days, this.selectedChartType.type).then( | ||||
|           data => { | ||||
|             this.series = data.data.series.filter( | ||||
|               group => group.name === "TV" | ||||
|             )[0].data; // plays pr date in groups (movie/tv/music) | ||||
|             this.categories = data.data.categories; // dates | ||||
|  | ||||
|             const x_labels = data.data.categories.map(date => { | ||||
|               if (date.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)) { | ||||
|                 const [year, month, day] = date.split('-') | ||||
|                 return `${day}.${month}` | ||||
|                 const [year, month, day] = date.split("-"); | ||||
|                 return `${day}.${month}`; | ||||
|               } | ||||
|  | ||||
|               return date | ||||
|             }) | ||||
|             let y_activityMovies = data.data.series.filter(group => group.name === 'Movies')[0].data | ||||
|             let y_activityTV = data.data.series.filter(group => group.name === 'TV')[0].data | ||||
|               return date; | ||||
|             }); | ||||
|             let y_activityMovies = data.data.series.filter( | ||||
|               group => group.name === "Movies" | ||||
|             )[0].data; | ||||
|             let y_activityTV = data.data.series.filter( | ||||
|               group => group.name === "TV" | ||||
|             )[0].data; | ||||
|  | ||||
|             const datasets = [{ | ||||
|                 label: `Movies watch last ${ this.days } days`, | ||||
|             const datasets = [ | ||||
|               { | ||||
|                 label: `Movies watch last ${this.days} days`, | ||||
|                 data: y_activityMovies, | ||||
|                 backgroundColor: 'rgba(54, 162, 235, 0.2)', | ||||
|                 borderColor: 'rgba(54, 162, 235, 1)', | ||||
|                 backgroundColor: "rgba(54, 162, 235, 0.2)", | ||||
|                 borderColor: "rgba(54, 162, 235, 1)", | ||||
|                 borderWidth: 1 | ||||
|               }, | ||||
|               { | ||||
|                 label: `Shows watch last ${ this.days } days`, | ||||
|                 label: `Shows watch last ${this.days} days`, | ||||
|                 data: y_activityTV, | ||||
|                 backgroundColor: 'rgba(255, 159, 64, 0.2)', | ||||
|                 borderColor: 'rgba(255, 159, 64, 1)', | ||||
|                 backgroundColor: "rgba(255, 159, 64, 0.2)", | ||||
|                 borderColor: "rgba(255, 159, 64, 1)", | ||||
|                 borderWidth: 1 | ||||
|               } | ||||
|             ] | ||||
|             ]; | ||||
|  | ||||
|             if (chart.data == null) { | ||||
|               this.generateChart(chart, x_labels, datasets) | ||||
|               this.generateChart(chart, x_labels, datasets); | ||||
|             } else { | ||||
|               chart.data.clear(); | ||||
|               chart.data.data.labels = x_labels; | ||||
|               chart.data.data.datasets = datasets; | ||||
|               chart.data.update(); | ||||
|             } | ||||
|           }) | ||||
|           } | ||||
|         ); | ||||
|       } | ||||
|     }, | ||||
|     generateChart(chart, labels, datasets) { | ||||
| @@ -175,89 +188,99 @@ export default { | ||||
|           maintainAspectRatio: false, | ||||
|           tooltips: { | ||||
|             callbacks: { | ||||
|               title: (tooltipItem, data) => `Watch date: ${tooltipItem[0].label}`, | ||||
|               title: (tooltipItem, data) => | ||||
|                 `Watch date: ${tooltipItem[0].label}`, | ||||
|               label: (tooltipItem, data) => { | ||||
|                 let label = data.datasets[tooltipItem.datasetIndex].label | ||||
|                 let label = data.datasets[tooltipItem.datasetIndex].label; | ||||
|                 let value = tooltipItem.value; | ||||
|                 let text = 'Duration watched' | ||||
|                 let text = "Duration watched"; | ||||
|  | ||||
|                 const context = label.split(' ')[0] | ||||
|                 const context = label.split(" ")[0]; | ||||
|                 if (context) { | ||||
|                   text = `${context} ${this.selectedChartType.tooltipLabel.toLowerCase()}` | ||||
|                   text = `${context} ${this.selectedChartType.tooltipLabel.toLowerCase()}`; | ||||
|                 } | ||||
|  | ||||
|                 if (this.selectedChartType.valueConvertFunction) { | ||||
|                   value = this.selectedChartType.valueConvertFunction(tooltipItem.value) | ||||
|                   value = this.selectedChartType.valueConvertFunction( | ||||
|                     tooltipItem.value | ||||
|                   ); | ||||
|                 } | ||||
|  | ||||
|                 return ` ${text}: ${value}` | ||||
|                 return ` ${text}: ${value}`; | ||||
|               } | ||||
|             } | ||||
|           }, | ||||
|           scales: { | ||||
|               yAxes: [{ | ||||
|             yAxes: [ | ||||
|               { | ||||
|                 gridLines: { | ||||
|                   color: this.gridColor | ||||
|                 }, | ||||
|                 stacked: chart.graphType === 'bar', | ||||
|                 stacked: chart.graphType === "bar", | ||||
|                 ticks: { | ||||
|                   // suggestedMax: 10000, | ||||
|                   callback: (value, index, values) => { | ||||
|                     if (this.selectedChartType.valueConvertFunction) { | ||||
|                       return this.selectedChartType.valueConvertFunction(value, values) | ||||
|                       return this.selectedChartType.valueConvertFunction( | ||||
|                         value, | ||||
|                         values | ||||
|                       ); | ||||
|                     } | ||||
|                     return value | ||||
|                     return value; | ||||
|                   }, | ||||
|                   beginAtZero: true | ||||
|                 } | ||||
|               }], | ||||
|               xAxes: [{ | ||||
|                 stacked: chart.graphType === 'bar', | ||||
|                 gridLines: { | ||||
|                   display: false, | ||||
|               } | ||||
|               }] | ||||
|             ], | ||||
|             xAxes: [ | ||||
|               { | ||||
|                 stacked: chart.graphType === "bar", | ||||
|                 gridLines: { | ||||
|                   display: false | ||||
|                 } | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       chart.data = chartInstance; | ||||
|     }, | ||||
|     convertSecondsToHumanReadable(value, values=null) { | ||||
|     convertSecondsToHumanReadable(value, values = null) { | ||||
|       const highestValue = values ? values[0] : value; | ||||
|  | ||||
|       // minutes | ||||
|       if (highestValue < 3600) { | ||||
|         const minutes = Math.floor(value / 60); | ||||
|  | ||||
|         value = `${minutes} m` | ||||
|         value = `${minutes} m`; | ||||
|       } | ||||
|       // hours and minutes | ||||
|       else if (highestValue > 3600 && highestValue < 86400) { | ||||
|         const hours = Math.floor(value / 3600); | ||||
|         const minutes = Math.floor(value % 3600 / 60); | ||||
|         const minutes = Math.floor((value % 3600) / 60); | ||||
|  | ||||
|         value = hours != 0 ? `${hours} h ${minutes} m` : `${minutes} m` | ||||
|         value = hours != 0 ? `${hours} h ${minutes} m` : `${minutes} m`; | ||||
|       } | ||||
|       // days and hours | ||||
|       else if (highestValue > 86400 && highestValue < 31557600) { | ||||
|         const days = Math.floor(value / 86400); | ||||
|         const hours = Math.floor(value % 86400 / 3600); | ||||
|         const hours = Math.floor((value % 86400) / 3600); | ||||
|  | ||||
|         value = days != 0 ? `${days} d ${hours} h` : `${hours} h` | ||||
|         value = days != 0 ? `${days} d ${hours} h` : `${hours} h`; | ||||
|       } | ||||
|       // years and days | ||||
|       else if (highestValue > 31557600) { | ||||
|         const years = Math.floor(value / 31557600); | ||||
|         const days = Math.floor(value % 31557600 / 86400); | ||||
|         const days = Math.floor((value % 31557600) / 86400); | ||||
|  | ||||
|         value = years != 0 ? `${years} y ${days} d` : `${days} d` | ||||
|         value = years != 0 ? `${years} y ${days} d` : `${days} d`; | ||||
|       } | ||||
|  | ||||
|       return value | ||||
|       return value; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| @@ -272,31 +295,56 @@ export default { | ||||
| } | ||||
|  | ||||
| .filter { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   flex-wrap: wrap; | ||||
|   align-items: center; | ||||
|   margin-bottom: 2rem; | ||||
|  | ||||
|   h2 { | ||||
|     margin-bottom: 0.5rem; | ||||
|     width: 100%; | ||||
|     font-weight: 400; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   &-item:not(:first-of-type) { | ||||
|     margin-left: 1rem; | ||||
|   } | ||||
|  | ||||
|   .dayinput { | ||||
|   margin-top: 0.5rem; | ||||
|   display: inline-flex; | ||||
|   flex-direction: column; | ||||
|   font-size: 1.2rem; | ||||
|  | ||||
|   &:not(:first-of-type) { | ||||
|     margin-left: 1.25rem; | ||||
|   } | ||||
|  | ||||
|   input { | ||||
|     width: 100%; | ||||
|     font-size: inherit; | ||||
|     max-width: 3rem; | ||||
|     background-color: $background-ui; | ||||
|     color: $text-color; | ||||
|   } | ||||
|  | ||||
|   span { | ||||
|     font-size: inherit; | ||||
|     line-height: 1; | ||||
|     margin: 0.5rem 0; | ||||
|     font-weight: 300; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // .filter { | ||||
| //   display: flex; | ||||
| //   flex-direction: row; | ||||
| //   flex-wrap: wrap; | ||||
| //   align-items: center; | ||||
| //   margin-bottom: 2rem; | ||||
|  | ||||
| //   h2 { | ||||
| //     margin-bottom: 0.5rem; | ||||
| //     width: 100%; | ||||
| //     font-weight: 400; | ||||
| //   } | ||||
|  | ||||
| //   &-item:not(:first-of-type) { | ||||
| //     margin-left: 1rem; | ||||
| //   } | ||||
|  | ||||
| //   .dayinput { | ||||
| //     font-size: 1.2rem; | ||||
| //     max-width: 3rem; | ||||
| //     background-color: $background-ui; | ||||
| //     color: $text-color; | ||||
| //   } | ||||
| // } | ||||
|  | ||||
| .chart-section { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
| @@ -312,5 +360,4 @@ export default { | ||||
|     font-weight: 300; | ||||
|   } | ||||
| } | ||||
|  | ||||
| </style> | ||||
| @@ -1,4 +1,3 @@ | ||||
|  | ||||
| .noselect { | ||||
|   -webkit-touch-callout: none; /* iOS Safari */ | ||||
|   -webkit-user-select: none; /* Safari */ | ||||
| @@ -8,13 +7,6 @@ | ||||
|   user-select: none; /* Non-prefixed version, currently */ | ||||
| } | ||||
|  | ||||
| .end-section { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   width: 100%; | ||||
|   margin: 1rem 0; | ||||
| } | ||||
|  | ||||
| .button--group { | ||||
|   display: flex; | ||||
|  | ||||
|   | ||||
| @@ -5,8 +5,10 @@ | ||||
| :root { | ||||
|   color-scheme: light; | ||||
|   --text-color: #081c24; | ||||
|   --text-color-90: rgba(8, 28, 36, 0.9); | ||||
|   --text-color-70: rgba(8, 28, 36, 0.7); | ||||
|   --text-color-50: rgba(8, 28, 36, 0.5); | ||||
|   --text-color-10: rgba(8, 28, 36, 0.1); | ||||
|   --text-color-5: rgba(8, 28, 36, 0.05); | ||||
|   --text-color-secondary: orange; | ||||
|   --background-color: #f8f8f8; | ||||
| @@ -40,16 +42,18 @@ | ||||
|   :root { | ||||
|     color-scheme: light dark; | ||||
|     --text-color: #fff; | ||||
|     --text-color-90: rgba(255, 255, 255, 0.9); | ||||
|     --text-color-70: rgba(255, 255, 255, 0.7); | ||||
|     --text-color-50: rgba(255, 255, 255, 0.5); | ||||
|     --text-color-10: rgba(255, 255, 255, 0.1); | ||||
|     --text-color-5: rgba(255, 255, 255, 0.05); | ||||
|     --text-color-secondary: orange; | ||||
|     --background-color: rgba(17, 17, 17, 1); | ||||
|     --background-color-secondary: rgba(6, 7, 8, 1); | ||||
|     --background-ui: #202125; | ||||
|     --background-95: rgba(17, 17, 17, 0.95); | ||||
|     --background-70: rgba(17, 17, 17, 0.8); | ||||
|     --background-40: rgba(17, 17, 17, 0.4); | ||||
|     --background-95: rgba(6, 7, 8, 0.95); | ||||
|     --background-70: rgba(6, 7, 8, 0.7); | ||||
|     --background-40: rgba(6, 7, 8, 0.4); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -72,6 +76,7 @@ $white: #fff; | ||||
| $white-80: rgba(255, 255, 255, 0.8); | ||||
|  | ||||
| $text-color: var(--text-color) !default; | ||||
| $text-color-90: var(--text-color-90) !default; | ||||
| $text-color-70: var(--text-color-70) !default; | ||||
| $text-color-50: var(--text-color-50) !default; | ||||
| $text-color-5: var(--text-color-5) !default; | ||||
| @@ -100,6 +105,7 @@ $color-error-highlight: var(--color-error-highlight) !default; | ||||
|  | ||||
| .dark { | ||||
|   --text-color: #fff; | ||||
|   --text-color-90: rgba(255, 255, 255, 0.9); | ||||
|   --text-color-70: rgba(255, 255, 255, 0.7); | ||||
|   --text-color-50: rgba(255, 255, 255, 0.5); | ||||
|   --text-color-5: rgba(255, 255, 255, 0.05); | ||||
| @@ -114,6 +120,7 @@ $color-error-highlight: var(--color-error-highlight) !default; | ||||
|  | ||||
| .light { | ||||
|   --text-color: #081c24; | ||||
|   --text-color-90: rgba(8, 28, 36, 0.9); | ||||
|   --text-color-70: rgba(8, 28, 36, 0.7); | ||||
|   --text-color-50: rgba(8, 28, 36, 0.5); | ||||
|   --text-color-5: rgba(8, 28, 36, 0.05); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user