Merge branch 'master' into ft-toast

This commit is contained in:
Kyle Watson 2020-08-02 12:32:01 +01:00 committed by GitHub
commit 7312d6e0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 3493 additions and 3057 deletions

View File

@ -25,30 +25,75 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npm run build --if-present
- name: Upload .deb Artifact
- name: Build ARM64 with Node.js ${{ matrix.node-version}}
if: startsWith(matrix.os, 'ubuntu')
run: npm run build:arm --if-present
- name: Upload Linux .zip x64 Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_portable_x64.zip
path: build/freetube-vue-0.8.0.zip
- name: Upload Linux .zip ARM Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_portable_arm64.zip
path: build/freetube-vue-0.8.0-arm64.zip
- name: Upload .deb x64 Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_amd64.deb
path: build/freetube-vue_0.8.0_amd64.deb
- name: Upload AppImage Artifact
- name: Upload .deb ARM Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_arm64.deb
path: build/freetube-vue_0.8.0_arm64.deb
- name: Upload AppImage x64 Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_amd64.AppImage
path: build/FreeTube-Vue-0.8.0.AppImage
- name: Upload .rpm Artifact
- name: Upload AppImage ARM Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_arm64.AppImage
path: build/FreeTube-Vue-0.8.0-arm64.AppImage
- name: Upload .rpm x64 Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_amd64.rpm
path: build/freetube-vue-0.8.0.x86_64.rpm
- name: Upload Linux .zip Artifact
- name: Upload .rpm ARM Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_amd64.zip
path: build/freetube-vue-0.8.0.zip
name: freetube-vue_0.8.0_arm64.rpm
path: build/freetube-vue-0.8.0.arm64.rpm
- name: Upload Alpine .apk x64 Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_alpine_amd64.apk
path: build/freetube-vue-0.8.0.apk
- name: Upload Alpine .apk ARM Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_alpine_arm64.apk
path: build/freetube-vue-0.8.0-arm64.apk
- name: Upload Web Build
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu')
with:
name: freetube-vue_0.8.0_static_web
path: dist/web
- name: Upload Windows .exe Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'windows')
@ -61,6 +106,12 @@ jobs:
with:
name: freetube-vue-0.8.0-setup-x64.exe
path: build/FreeTube-Vue Setup 0.8.0.exe
- name: Upload Windows Portable Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'windows')
with:
name: freetube-vue-0.8.0-portable-x64.exe
path: build/FreeTube-Vue 0.8.0.exe
- name: Upload Mac .dmg Artifact
uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'macos')

7
.vscode/tasks.json vendored
View File

@ -7,6 +7,13 @@
"type": "npm",
"script": "dev",
"problemMatcher": []
},
{
"type": "npm",
"script": "dev-runner",
"problemMatcher": [],
"label": "npm: dev-runner",
"detail": "node _scripts/dev-runner.js"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2,7 +2,9 @@ const os = require('os')
const builder = require('electron-builder')
const Platform = builder.Platform
const Arch = builder.Arch
const { name, productName } = require('../package.json')
const args = process.argv
let targets
var platform = os.platform()
@ -12,7 +14,13 @@ if (platform == 'darwin') {
} else if (platform == 'win32') {
targets = Platform.WINDOWS.createTarget()
} else if (platform == 'linux') {
targets = Platform.LINUX.createTarget()
let arch = Arch.x64
if (args[2] === 'arm') {
arch = Arch.arm64
}
targets = Platform.LINUX.createTarget(['deb', 'zip', 'apk', 'rpm', 'AppImage'], arch)
}
const config = {
@ -47,7 +55,7 @@ const config = {
linux: {
category: 'Network',
icon: '_icons/icon.png',
target: ['deb', 'rpm', 'zip', 'AppImage'],
target: ['deb', 'zip', 'apk', 'rpm', 'AppImage'],
},
mac: {
category: 'public.app-category.utilities',

View File

@ -85,6 +85,13 @@ if (isDevMode) {
ignore: ['.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/images'),
globOptions: {
ignore: ['.*'],
},
},
]
}
),

View File

@ -132,6 +132,7 @@ const config = {
'@': path.join(__dirname, '../src/'),
src: path.join(__dirname, '../src/'),
icons: path.join(__dirname, '../_icons/'),
images: path.join(__dirname, '../src/renderer/assets/img/'),
},
extensions: ['.ts', '.js', '.vue', '.json'],
},
@ -171,6 +172,13 @@ if (isDevMode) {
ignore: ['.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/web/images'),
globOptions: {
ignore: ['.*'],
},
},
]
}
),

View File

@ -135,6 +135,7 @@ const config = {
vue$: 'vue/dist/vue.esm.js',
src: path.join(__dirname, '../src/'),
icons: path.join(__dirname, '../_icons/'),
images: path.join(__dirname, '../src/renderer/assets/img/'),
},
extensions: ['.js', '.vue', '.json', '.css'],
},
@ -174,6 +175,13 @@ if (isDevMode) {
ignore: ['.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/web/images'),
globOptions: {
ignore: ['.*'],
},
},
]
}
),

4941
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@
"url": "https://github.com/FreeTubeApp/FreeTube/issues"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.29",
"@fortawesome/free-solid-svg-icons": "^5.13.1",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/vue-fontawesome": "^0.1.10",
"@silvermine/videojs-quality-selector": "^1.2.4",
"autolinker": "^3.14.1",
"bulma-pro": "^0.2.0",
"dateformat": "^3.0.3",
"electron-context-menu": "^2.0.1",
"electron-context-menu": "^2.2.0",
"jquery": "^3.5.1",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
@ -32,42 +32,42 @@
"vue": "^2.6.11",
"vue-electron": "^1.0.6",
"vue-router": "^3.3.4",
"vuex": "^3.4.0",
"vuex": "^3.5.1",
"xml2json": "^0.12.0",
"youtube-chat": "^1.1.0",
"youtube-comments-fetch": "^1.0.1",
"youtube-comments-task": "^1.3.15",
"youtube-suggest": "^1.1.0",
"yt-channel-info": "git+https://github.com/FreeTubeApp/yt-channel-info.git",
"yt-channel-info": "^1.0.1",
"yt-xml2vtt": "^1.1.1",
"ytdl-core": "^3.1.1",
"ytpl": "^0.1.21",
"ytsr": "^0.1.15"
"ytdl-core": "^3.2.0",
"ytpl": "^0.2.3",
"ytsr": "^0.1.20"
},
"description": "A private YouTube client",
"devDependencies": {
"@babel/core": "^7.10.3",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-object-rest-spread": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"@babel/preset-typescript": "^7.10.1",
"@typescript-eslint/eslint-plugin": "^3.3.0",
"@typescript-eslint/parser": "^3.3.0",
"@babel/core": "^7.10.5",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-object-rest-spread": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
"@typescript-eslint/eslint-plugin": "^3.7.1",
"@typescript-eslint/parser": "^3.7.1",
"acorn": "^7.3.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"copy-webpack-plugin": "^6.0.2",
"css-loader": "^3.6.0",
"copy-webpack-plugin": "^6.0.3",
"css-loader": "^4.1.0",
"devtron": "^1.4.0",
"electron": "^9.0.4",
"electron-builder": "^22.7.0",
"electron-builder-squirrel-windows": "^22.7.0",
"electron": "^9.1.2",
"electron-builder": "^22.8.0",
"electron-builder-squirrel-windows": "^22.8.1",
"electron-debug": "^3.1.0",
"electron-rebuild": "^1.11.0",
"eslint": "^7.3.0",
"eslint": "^7.5.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
@ -76,28 +76,28 @@
"fast-glob": "^3.2.4",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"jest": "^26.0.1",
"jest": "^26.1.0",
"mini-css-extract-plugin": "^0.9.0",
"node-abi": "^2.18.0",
"node-loader": "^0.6.0",
"node-loader": "^1.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"sass": "^1.26.8",
"sass-loader": "^8.0.2",
"sass": "^1.26.10",
"sass-loader": "^9.0.2",
"style-loader": "^1.2.1",
"tree-kill": "1.2.2",
"typescript": "^3.9.5",
"typescript": "^3.9.7",
"url-loader": "^4.1.0",
"vue-devtools": "^5.1.3",
"vue-devtools": "^5.1.4",
"vue-eslint-parser": "^7.1.0",
"vue-loader": "^15.9.2",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack": "^4.44.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"license": "GPL-3.0-or-later",
"license": "AGPL-3.0-or-later",
"main": "./dist/main.js",
"name": "freetube-vue",
"private": true,
@ -108,7 +108,9 @@
},
"scripts": {
"build": "run-s rebuild:electron pack build-release",
"build:arm": "run-s rebuild:electron pack build-release:arm",
"build-release": "node _scripts/build.js",
"build-release:arm": "node _scripts/build.js arm",
"debug": "run-s rebuild:electron debug-runner",
"debug-runner": "node _scripts/dev-runner.js --remote-debug",
"dev": "run-s rebuild:electron dev-runner",

View File

@ -64,6 +64,7 @@ function createWindow () {
backgroundColor: '#fff',
width: 960,
height: 540,
autoHideMenuBar: true,
// useContentSize: true,
webPreferences: {
nodeIntegration: true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,11 @@
import Vue from 'vue'
export default Vue.extend({
name: 'FtAutoGrid',
props: {
grid: {
type: Boolean,
required: true
}
}
})

View File

@ -0,0 +1,10 @@
.ft-auto-grid
&.grid
display: grid
grid-template-columns: repeat(auto-fill, 240px)
justify-content: space-evenly
grid-gap: 5px
&.list
display: grid
grid-gap: 16px

View File

@ -0,0 +1,14 @@
<template>
<div
class="ft-auto-grid"
:class="{
grid: grid,
list: !grid
}"
>
<slot />
</div>
</template>
<script src="./ft-auto-grid.js" />
<style scoped lang="sass" src="./ft-auto-grid.sass" />

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtGrid from '../ft-grid/ft-grid.vue'
import FtAutoGrid from '../ft-auto-grid/ft-auto-grid.vue'
import FtListVideo from '../ft-list-video/ft-list-video.vue'
import FtListChannel from '../ft-list-channel/ft-list-channel.vue'
import FtListPlaylist from '../ft-list-playlist/ft-list-playlist.vue'
@ -9,7 +9,7 @@ export default Vue.extend({
name: 'FtElementList',
components: {
'ft-flex-box': FtFlexBox,
'ft-grid': FtGrid,
'ft-auto-grid': FtAutoGrid,
'ft-list-video': FtListVideo,
'ft-list-channel': FtListChannel,
'ft-list-playlist': FtListPlaylist

View File

@ -1,49 +1,30 @@
<template>
<span>
<ft-flex-box
v-if="listType === 'list'"
<ft-auto-grid
:grid="listType !== 'list'"
>
<template
v-for="(result, index) in data"
>
<span
v-for="(result, index) in data"
<ft-list-channel
v-if="result.type === 'channel'"
:key="index"
class="maxWidth"
>
<ft-list-channel
v-if="result.type === 'channel'"
:data="result"
/>
<ft-list-video
v-if="result.type === 'video' || result.type === 'shortVideo'"
:data="result"
/>
<ft-list-playlist
v-if="result.type === 'playlist'"
:data="result"
/>
</span>
</ft-flex-box>
<ft-grid
v-else
>
<span
v-for="(result, index) in data"
appearance="result"
:data="result"
/>
<ft-list-video
v-if="result.type === 'video' || result.type === 'shortVideo'"
:key="index"
>
<ft-list-channel
v-if="result.type === 'channel'"
:data="result"
/>
<ft-list-video
v-if="result.type === 'video' || result.type === 'shortVideo'"
:data="result"
/>
<ft-list-playlist
v-if="result.type === 'playlist'"
:data="result"
/>
</span>
</ft-grid>
</span>
appearance="result"
:data="result"
/>
<ft-list-playlist
v-if="result.type === 'playlist'"
:key="index"
appearance="result"
:data="result"
/>
</template>
</ft-auto-grid>
</template>
<script src="./ft-element-list.js" />

View File

@ -19,6 +19,14 @@ export default Vue.extend({
type: Boolean,
default: true
},
padding: {
type: Number,
default: 10
},
size: {
type: Number,
default: 20
},
forceDropdown: {
type: Boolean,
default: false

View File

@ -3,12 +3,11 @@
flex-flow: row wrap
justify-content: space-evenly
position: relative
user-select: none
.iconButton
width: 1em
height: 1em
padding: 10px
font-size: 20px
border-radius: 50%
cursor: pointer
transition: background 0.15s ease-out
@ -59,10 +58,10 @@
user-select: none
&.left
right: calc(50% - 20px)
right: calc(50% - 10px)
&.right
left: calc(50% - 20px)
left: calc(50% - 10px)
.list
margin: 0

View File

@ -10,6 +10,10 @@
secondary: theme === 'secondary',
shadow: useShadow
}"
:style="{
padding: padding + 'px',
fontSize: size + 'px'
}"
@click="handleIconClick"
/>
<div

View File

@ -1,93 +0,0 @@
.channelThumbnail {
position: relative;
cursor: pointer;
}
.channelThumbnail img {
width: 140px;
height: 140px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
.channelName {
font-weight: bold;
color: var(--title-color);
cursor: pointer;
margin-bottom: 0px;
}
.subscriberCount {
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
}
.videoCount {
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
}
.grid {
width: 240px;
height: 250px;
padding: 2px;
overflow: hidden;
}
.grid .channelThumbnail {
width: 100%;
height: 140px;
margin-bottom: 5px;
}
.grid .channelThumbnail img {
position: relative;
left: 50px;
}
.grid .channelName {
width: 240px;
}
.list {
height: 140px;
width: 100%;
margin-left: 5px;
margin-top: 15px;
border-bottom: 1px solid var(--secondary-text-color);
}
.list .channelThumbnail {
float: left;
width: 240px;
}
.list .channelThumbnail img {
position: relative;
width: 120px;
height: 120px;
left: 60px;
}
.list .channelName {
margin-left: 250px;
width: 275px;
}
.list .subscriberCount {
margin-left: 10px;
}
.list .videoCount {
margin-left: 1px;
}
.list .description {
margin-left: 250px;
font-size: 13px;
color: var(--secondary-text-color);
height: 35px;
overflow: hidden;
}

View File

@ -6,6 +6,10 @@ export default Vue.extend({
data: {
type: Object,
required: true
},
appearance: {
type: String,
required: true
}
},
data: function () {
@ -32,10 +36,6 @@ export default Vue.extend({
}
},
methods: {
goToChannel: function () {
this.$router.push({ path: `/channel/${this.id}` })
},
parseLocalData: function () {
this.thumbnail = this.data.avatar
this.channelName = this.data.name

View File

@ -0,0 +1 @@
@use "../../sass-partials/_ft-list-item"

View File

@ -1,40 +1,50 @@
<template>
<div
class="ft-list-channel"
:class="{ list: listType === 'list', grid: listType === 'grid' }"
class="ft-list-channel ft-list-item"
:class="{
list: listType === 'list',
grid: listType === 'grid',
[appearance]: true
}"
>
<div class="channelThumbnail">
<img
:src="thumbnail"
@click="goToChannel(id)"
<router-link
:to="`/channel/${id}`"
>
<img
:src="thumbnail"
class="channelImage"
>
</router-link>
</div>
<div class="info">
<router-link
class="title"
:to="`/channel/${id}`"
>
{{ channelName }}
</router-link>
<div class="infoLine">
<span
class="subscriberCount"
>
{{ subscriberCount }} subscribers
</span>
<span
class="videoCount"
>
- {{ videoCount }} videos
</span>
</div>
<p
v-if="listType !== 'grid'"
class="description"
>
{{ description }}
</p>
</div>
<p
class="channelName"
@click="goToChannel(id)"
>
{{ channelName }}
</p>
<span
class="subscriberCount"
@click="goToChannel(id)"
>
{{ subscriberCount }} subscribers
</span>
<span
class="videoCount"
@click="goToChannel(id)"
>
- {{ videoCount }} videos
</span>
<p
v-if="listType !== 'grid'"
class="description"
>
{{ description }}
</p>
</div>
</template>
<script src="./ft-list-channel.js" />
<style scoped src="./ft-list-channel.css" />
<style scoped lang="sass" src="./ft-list-channel.sass" />

View File

@ -1,106 +0,0 @@
.videoThumbnail {
position: relative;
cursor: pointer;
}
.videoCountContainer {
position: absolute;
top: 0px;
right: 0px;
width: 120px;
background-color: rgba(0,0,0,0.6);
color: #FFFFFF;
text-align: center;
font-size: 20px;
}
.videoCountContainer span {
position: absolute;
top: 40px;
left: 45px;
}
.playlistTitle {
font-weight: bold;
color: var(--title-color);
cursor: pointer;
}
.channelName {
color: var(--secondary-text-color);
cursor: pointer;
font-size: 14px;
}
.grid {
width: 240px;
height: 250px;
padding: 2px;
overflow: hidden;
}
.grid .videoThumbnail {
width: 100%;
height: 130px;
margin-bottom: -5px;
}
.grid .videoThumbnail img {
width: 100%;
height: 130px;
}
.grid .videoCountContainer {
height: 130px;
}
.grid .playlistTitle {
max-height: 75px;
overflow-y: hidden;
}
.grid .channelName {
width: 275px;
}
.list {
height: 140px;
width: 100%;
margin-left: 5px;
margin-top: 15px;
border-bottom: 1px solid var(--secondary-text-color);
}
.list .videoThumbnail {
float: left;
width: 240px;
height: 130px;
}
.list .videoThumbnail img {
width: 100%;
height: 130px;
}
.list .videoCountContainer {
height: 130px;
}
.list .playlistTitle {
margin-left: 250px;
margin-top: 5px;
margin-bottom: -10px;
}
.list .channelName {
margin-left: 250px;
width: 275px;
}
.list .description {
margin-left: 285px;
font-size: 13px;
color: var(--secondary-text-color);
height: 35px;
overflow: hidden;
}

View File

@ -6,6 +6,10 @@ export default Vue.extend({
data: {
type: Object,
required: true
},
appearance: {
type: String,
required: true
}
},
data: function () {
@ -58,14 +62,6 @@ export default Vue.extend({
this.channelLink = this.data.author.ref
this.playlistLink = this.data.link
this.videoCount = parseInt(this.data.length.split(' ')[0])
},
goToPlaylist: function (id) {
this.$router.push({ path: `/playlist/${id}` })
},
goToChannel: function (id) {
console.log(id)
}
}
})

View File

@ -0,0 +1 @@
@use "../../sass-partials/_ft-list-item"

View File

@ -1,38 +1,45 @@
<template>
<div
class="ft-list-video"
class="ft-list-video ft-list-item"
:appearance="appearance"
:class="{ list: listType === 'list', grid: listType === 'grid' }"
>
<div class="videoThumbnail">
<router-link
class="videoThumbnail"
:to="`/playlist/${playlistId}`"
>
<img
:src="thumbnail"
@click="goToPlaylist(playlistId)"
class="thumbnailImage"
>
<div
class="videoCountContainer"
@click="goToPlaylist(playlistId)"
>
<span>
{{ videoCount }}
<br>
<font-awesome-icon icon="list" />
</span>
<div class="background" />
<div class="inner">
<div>{{ videoCount }}</div>
<div><font-awesome-icon icon="list" /></div>
</div>
</div>
</router-link>
<div class="info">
<router-link
class="title"
:to="`/playlist/${playlistId}`"
>
{{ title }}
</router-link>
<div class="infoLine">
<router-link
class="channelName"
:to="`/channel/${channelId}`"
>
{{ channelName }}
</router-link>
</div>
</div>
<p
class="playlistTitle"
@click="goToPlaylist(playlistId)"
>
{{ title }}
</p>
<p
class="channelName"
@click="goToChannel(channelId)"
>
{{ channelName }}
</p>
</div>
</template>
<script src="./ft-list-playlist.js" />
<style scoped src="./ft-list-playlist.css" />
<style scoped lang="sass" src="./ft-list-playlist.sass" />

View File

@ -1,247 +0,0 @@
.videoThumbnail {
position: relative;
cursor: pointer;
}
.videoThumbnail:hover .videoWatched {
visibility: hidden;
}
.favoritesIcon {
position: absolute;
font-size: 15px;
top: 0px;
right: 0px;
color: #FFFFFF;
padding: 5px;
background-color: #000000;
opacity: 0.7;
cursor: pointer;
}
.favorited {
color: yellow;
}
.videoDuration {
position: absolute;
font-size: 13px;
bottom: -7px;
right: 0px;
color: #FFFFFF;
padding: 2px;
background-color: #000000;
opacity: 0.7;
cursor: pointer;
}
.videoWatched {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
background-color: rgba(0,0,0,0.4);
color: #FFFFFF;
pointer-events: none;
}
.watchedProgressBar {
background-color: var(--red-500);
opacity: 0.8;
height: 3px;
position: absolute;
bottom: 0px;
left: 0px;
cursor: pointer;
}
.videoTitle {
color: var(--title-color);
font-weight: bold;
cursor: pointer;
}
.channelName {
color: var(--secondary-text-color);
cursor: pointer;
font-size: 14px;
}
.viewCount {
cursor: pointer;
font-size: 13px;
}
.uploadedTime {
cursor: pointer;
font-size: 13px;
}
.liveText {
color: var(--red-500);
font-size: 13px;
cursor: pointer;
}
/deep/ .iconButton {
font-size: 15px;
padding: 8px;
}
/deep/ .iconDropdown {
margin-top: 30px;
}
.grid {
width: 240px;
height: 250px;
padding: 2px;
overflow: hidden;
}
.grid .videoThumbnail {
width: 100%;
height: 130px;
margin-bottom: -5px;
}
.grid .videoThumbnail img {
width: 100%;
height: 130px;
}
.grid .videoWatched {
height: 110px;
}
.grid .optionsButton {
margin-top: 10px;
margin-left: 210px;
position: absolute;
}
.grid .videoTitle {
max-height: 55px;
width: 210px;
overflow-y: hidden;
margin-bottom: -15px;
}
.grid .channelName {
width: 220px;
height: 17px;
margin-bottom: 10px;
overflow-y: hidden;
}
.grid .liveText {
float: right;
}
.list {
height: 140px;
width: 100%;
margin-left: 5px;
margin-top: 15px;
}
.list .videoThumbnail {
float: left;
width: 240px;
height: 130px;
}
.list .videoThumbnail img {
width: 100%;
height: 130px;
}
.list .videoWatched {
height: 130px;
}
.list .optionsButton {
float: right;
}
.list .videoTitle {
margin-left: 250px;
margin-top: 5px;
margin-bottom: -10px;
}
.list .channelName {
margin-left: 250px;
max-width: 275px;
}
.list .viewCount {
margin-left: 10px;
}
.list .liveText {
margin-left: 10px;
}
.list .description {
margin-left: 250px;
font-size: 13px;
color: var(--secondary-text-color);
height: 35px;
overflow: hidden;
}
.videoRecommendation.list {
height: 110px;
}
.videoRecommendation.list .videoThumbnail {
width: 180px;
height: 100px;
}
.videoRecommendation.list .videoThumbnail img {
height: 100px;
}
.videoRecommendation.list .videoTitle {
font-size: 12px;
margin-left: 185px;
}
.videoRecommendation.list .channelName {
margin-left: 185px;
}
.videoRecommendation.list .viewCount {
margin-left: 5px;
}
.playlistItem .list {
height: 60px;
width: calc(100% - 30px);
}
.playlistItem .list .videoThumbnail {
width: 100px;
height: 60px;
}
.playlistItem .list .videoThumbnail img {
height: 60px;
}
.playlistItem .list .videoTitle {
font-size: 12px;
margin-left: 105px;
margin-right: 30px;
}
.playlistItem .list .channelName {
margin-left: 105px;
margin-right: 30px;
}
.playlistItem .list .viewCount {
margin-left: 5px;
}

View File

@ -18,6 +18,10 @@ export default Vue.extend({
forceListType: {
type: String,
default: null
},
appearance: {
type: String,
required: true
}
},
data: function () {
@ -121,31 +125,6 @@ export default Vue.extend({
}
},
methods: {
play: function () {
const playlistInfo = {
playlistId: this.playlistId
}
console.log('playlist info')
console.log(playlistInfo)
if (this.playlistId !== null) {
console.log('Sending playlist info')
this.$router.push(
{
path: `/watch/${this.id}`,
query: playlistInfo
}
)
} else {
console.log('no playlist found')
this.$router.push({ path: `/watch/${this.id}` })
}
},
goToChannel: function () {
this.$router.push({ path: `/channel/${this.channelId}` })
},
toggleSave: function () {
console.log('TODO: ft-list-video method toggleSave')
},

View File

@ -0,0 +1 @@
@use "../../sass-partials/_ft-list-item"

View File

@ -1,27 +1,39 @@
<template>
<div
class="ft-list-video"
class="ft-list-video ft-list-item"
:class="{
list: (listType === 'list' || forceListType === 'list') && forceListType !== 'grid',
grid: (listType === 'grid' || forceListType === 'list') && forceListType !== 'list'
grid: (listType === 'grid' || forceListType === 'list') && forceListType !== 'list',
[appearance]: true
}"
>
<div class="videoThumbnail">
<img
:src="thumbnail"
@click="play(id)"
<div
class="videoThumbnail"
>
<router-link
class="thumbnailLink"
:to="{
path: `/watch/${id}`,
query: playlistId ? {playlistId} : {}
}"
>
<p
v-if="!isLive"
<img
:src="thumbnail"
class="thumbnailImage"
>
</router-link>
<div
class="videoDuration"
@click="play(id)"
:class="{ live: isLive }"
>
{{ duration }}
</p>
<font-awesome-icon
{{ isLive ? "Live" : duration }}
</div>
<ft-icon-button
v-if="!isLive"
icon="star"
class="favoritesIcon"
:padding="appearance === `watchPlaylistItem` ? 5 : 6"
:size="appearance === `watchPlaylistItem` ? 14 : 18"
:class="{ favorited: isFavorited }"
@click="toggleSave(id)"
/>
@ -29,7 +41,7 @@
v-if="watched"
class="videoWatched"
>
WATCHED
Watched
</div>
<div
v-if="watched"
@ -37,65 +49,56 @@
:style="{width: progressPercentage + '%'}"
/>
</div>
<ft-icon-button
class="optionsButton"
title="More Options"
theme="base"
:use-shadow="false"
dropdown-position-x="left"
:dropdown-names="optionsNames"
:dropdown-values="optionsValues"
@click="handleOptionsClick"
/>
<p
class="videoTitle"
@click="play(id)"
>
{{ title }}
</p>
<p
class="channelName"
@click="goToChannel"
>
{{ channelName }}
</p>
<span
v-if="!isLive && !hideViews"
class="viewCount"
@click="play(id)"
>
{{ viewCount }} views
</span>
<span
v-if="uploadedTime !== '' && !isLive"
class="uploadedTime"
@click="play(id)"
>
- {{ uploadedTime }}
</span>
<span
v-if="isLive"
class="viewCount"
@click="play(id)"
>
{{ viewCount }} watching
</span>
<p
v-if="listType !== 'grid'"
class="description"
@click="play(id)"
>
{{ description }}
</p>
<span
v-if="isLive"
class="liveText"
@click="play(id)"
>
LIVE NOW
</span>
<div class="info">
<ft-icon-button
class="optionsButton"
title="More Options"
theme="base"
:size="16"
:use-shadow="false"
dropdown-position-x="left"
:dropdown-names="optionsNames"
:dropdown-values="optionsValues"
@click="handleOptionsClick"
/>
<router-link
class="title"
:to="{
path: `/watch/${id}`,
query: playlistId ? {playlistId} : {}
}"
>
{{ title }}
</router-link>
<div class="infoLine">
<router-link
class="channelName"
:to="`/channel/${channelId}`"
>
<span>{{ channelName }}</span>
</router-link>
<span
v-if="!isLive && !hideViews"
class="viewCount"
> {{ viewCount }} views</span>
<span
v-if="uploadedTime !== '' && !isLive"
class="uploadedTime"
> {{ uploadedTime }}</span>
<span
v-if="isLive"
class="viewCount"
> {{ viewCount }} watching</span>
</div>
<p
v-if="listType !== 'grid' && appearance === 'result'"
class="description"
>
{{ description }}
</p>
</div>
</div>
</template>
<script src="./ft-list-video.js" />
<style scoped src="./ft-list-video.css" />
<style scoped src="./ft-list-video.sass" lang="sass" />

View File

@ -1,3 +0,0 @@
.maxWidth {
width: 100%;
}

View File

@ -1,33 +0,0 @@
import Vue from 'vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtGrid from '../ft-grid/ft-grid.vue'
import FtListVideo from '../ft-list-video/ft-list-video.vue'
import FtListChannel from '../ft-list-channel/ft-list-channel.vue'
import FtListPlaylist from '../ft-list-playlist/ft-list-playlist.vue'
export default Vue.extend({
name: 'WatchPlaylist',
components: {
'ft-flex-box': FtFlexBox,
'ft-grid': FtGrid,
'ft-list-video': FtListVideo,
'ft-list-channel': FtListChannel,
'ft-list-playlist': FtListPlaylist
},
props: {
data: {
type: Array,
required: true
}
},
data: function () {
return {
test: 'hello'
}
},
computed: {
listType: function () {
return this.$store.getters.getListType
}
}
})

View File

@ -1,50 +0,0 @@
<template>
<span>
<ft-flex-box
v-if="listType === 'list'"
>
<span
v-for="(result, index) in data"
:key="index"
class="maxWidth"
>
<ft-list-channel
v-if="result.type === 'channel'"
:data="result"
/>
<ft-list-video
v-if="result.type === 'video' || result.type === 'shortVideo'"
:data="result"
/>
<ft-list-playlist
v-if="result.type === 'playlist'"
:data="result"
/>
</span>
</ft-flex-box>
<ft-grid
v-else
>
<span
v-for="(result, index) in data"
:key="index"
>
<ft-list-channel
v-if="result.type === 'channel'"
:data="result"
/>
<ft-list-video
v-if="result.type === 'video' || result.type === 'shortVideo'"
:data="result"
/>
<ft-list-playlist
v-if="result.type === 'playlist'"
:data="result"
/>
</span>
</ft-grid>
</span>
</template>
<script src="./watch-playlist.js" />
<style scoped src="./watch-playlist.css" />

View File

@ -37,36 +37,34 @@
.playlistItems {
overflow-y: auto;
height: 395px;
margin-top: -15px;
margin-left: -16px;
margin-right: -16px;
}
.playlistItem {
height: 75px;
width: 100%;
display: grid;
grid-template-columns: 30px 1fr;
align-items: center;
transition: background 0.2s ease-out;
}
.playlistItem:not(:last-child) {
margin-bottom: 8px;
}
.playlistItem:hover {
background-color: var(--side-nav-hover-color);
transition: background 0.2s ease-in;
}
.videoIndexContainer {
text-align: center;
}
.videoIndex {
float: left;
position: relative;
top: 15px;
left: 10px;
color: var(--tertiary-text-color);
}
.videoIndexIcon {
float: left;
position: relative;
font-size: 14px;
top: 32px;
left: 10px;
color: var(--tertiary-text-color);
}

View File

@ -47,7 +47,7 @@
@click="playNextVideo"
/>
</p>
<ft-flex-box
<div
v-if="!isLoading"
class="playlistItems"
>
@ -56,25 +56,27 @@
:key="index"
class="playlistItem"
>
<font-awesome-icon
v-if="item.videoId === videoId"
class="videoIndexIcon"
icon="play"
/>
<p
v-else
class="videoIndex"
>
{{ index + 1 }}
</p>
<div class="videoIndexContainer">
<font-awesome-icon
v-if="item.videoId === videoId"
class="videoIndexIcon"
icon="play"
/>
<p
v-else
class="videoIndex"
>
{{ index + 1 }}
</p>
</div>
<ft-list-video
:data="item"
:playlist-id="playlistId"
appearance="watchPlaylistItem"
force-list-type="list"
class="videoInfo"
/>
</div>
</ft-flex-box>
</div>
</div>
</ft-card>
</template>

View File

@ -1,7 +1,4 @@
.relative {
position: relative;
}
.videoRecommendation {
margin-bottom: -15px;
.watchVideoRecommendations {
display: grid;
grid-gap: 8px;
}

View File

@ -1,5 +1,5 @@
<template>
<ft-card class="relative">
<ft-card class="relative watchVideoRecommendations">
<h3>
Up Next
</h3>
@ -7,8 +7,8 @@
v-for="(video, index) in data"
:key="index"
:data="video"
appearance="recommendation"
force-list-type="list"
class="videoRecommendation"
/>
</ft-card>
</template>

View File

@ -0,0 +1,166 @@
$thumbnail-overlay-opacity: 0.85
@mixin is-result
@at-root
.result#{&}
@content
@mixin is-watch-playlist-item
@at-root
.watchPlaylistItem#{&}
@content
@mixin is-recommendation
@at-root
.recommendation#{&}
@content
@mixin is-sidebar-item
@at-root
.watchPlaylistItem#{&}, .recommendation#{&}
@content
.ft-list-item
.videoThumbnail
display: flex
position: relative
.thumbnailLink
display: flex
.thumbnailImage
height: 130px
@include is-sidebar-item
height: 75px
@include is-recommendation
width: 163px
height: auto
.videoDuration
position: absolute
bottom: 4px
right: 4px
padding: 3px 4px
line-height: 1.2
font-size: 15px
border-radius: 5px
margin: 0
opacity: $thumbnail-overlay-opacity
color: var(--primary-text-color)
background-color: var(--card-bg-color)
pointer-events: none
&.live
background-color: #f22
color: #fff
@include is-watch-playlist-item
font-size: 12px
.favoritesIcon
position: absolute
top: 3px
right: 3px
font-size: 17px
opacity: $thumbnail-overlay-opacity
.videoCountContainer
position: absolute
right: 0
top: 0
bottom: 0
width: 60px
font-size: 20px
.background, .inner
position: absolute
top: 0
bottom: 0
left: 0
right: 0
.background
background-color: var(--bg-color)
opacity: 0.9
.inner
display: flex
flex-direction: column
justify-content: center
align-items: center
color: var(--primary-text-color)
.channelThumbnail
display: flex
justify-content: center
.channelImage
height: 130px
border-radius: 50%
.info
flex: 1
position: relative
.optionsButton
float: right // ohhhh man, float was finally the right choice for something
.title
font-size: 20px
color: var(--primary-text-color)
text-decoration: none
@include is-sidebar-item
font-size: 15px
.infoLine
margin-top: 5px
font-size: 14px
@include is-sidebar-item
font-size: 12px
&, .channelName
color: var(--secondary-text-color)
.description
font-size: 14px
color: var(--secondary-text-color)
&.list
display: flex
align-items: flex-start
.videoThumbnail, .channelThumbnail
margin-right: 20px
.channelThumbnail
width: 231px
@include is-sidebar-item
.videoThumbnail
margin-right: 10px
&.grid
display: flex
flex-direction: column
min-height: 230px
padding-bottom: 20px
.videoThumbnail, .channelThumbnail
margin-bottom: 12px
.thumbnailImage
width: 100%
.title
font-size: 18px
.infoLine
margin-top: 8px
font-size: 13px
.videoWatched, .live
text-transform: uppercase

View File

@ -10,6 +10,8 @@
--link-visited-color: var(--accent-color-visited);
--card-bg-color: #FFFFFF;
--secondary-card-bg-color: #eeeeee;
--scrollbar-color: #CCCCCC;
--scrollbar-color-hover: #BDBDBD;
--side-nav-color: #FFFFFF;
--side-nav-hover-color: #e0e0e0;
--side-nav-active-color: #757575;
@ -32,6 +34,8 @@
--link-visited-color: var(--accent-color-visited);
--card-bg-color: #303030;
--secondary-card-bg-color: rgba(0, 0, 0, 0.75);
--scrollbar-color: #414141;
--scrollbar-color-hover: #757575;
--side-nav-color: #262626;
--side-nav-hover-color: #212121;
--side-nav-active-color: #303030;
@ -53,12 +57,14 @@
--link-visited-color: var(--accent-color-visited);
--card-bg-color: #000000;
--secondary-card-bg-color: rgba(0, 0, 0, 0.75);
--scrollbar-color: #515151;
--scrollbar-color-hover: #424242;
--side-nav-color: #000000;
--side-nav-hover-color: #212121;
--side-nav-active-color: #303030;
--search-bar-color: #262626;
--instance-menu-color: var(--search-bar-color);
--logo-icon: url("~../../_icons/iconColorSmall.png");
--logo-icon: url("~../../_icons/iconColorSmall.png");
--logo-text: url("~../../_icons/textColorSmall.png");
}
@ -442,9 +448,24 @@ body {
color: var(--primary-text-color);
background-color: var(--bg-color);
}
a:link {
color: var(--link-color);
}
a:visited {
color: var(--link-visited-color);
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-color);
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-color-hover);
}

View File

@ -13,6 +13,16 @@
max-height: 300px;
}
.defaultChannelBanner {
width: 100%;
position: absolute;
top: 0px;
left: 0px;
height: 200px;
background-color: black;
background-image: url("~images/defaultBanner.png");
}
.channelInfoContainer {
width: 100%;
height: 200px;
@ -116,6 +126,10 @@
margin-top: 200px;
}
.message {
color: var(--tertiary-text-color);
}
.getNextPage {
background-color: var(--search-bar-color);
width: 100%;
@ -123,4 +137,5 @@
line-height: 45px;
text-align: center;
cursor: pointer;
margin-top: 16px;
}

View File

@ -88,6 +88,28 @@ export default Vue.extend({
formattedSubCount: function () {
return this.subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
showFetchMoreButton: function () {
switch (this.currentTab) {
case 'videos':
if (this.videoContinuationString !== '' && this.videoContinuationString !== null) {
return true
}
break
case 'playlists':
if (this.playlistContinuationString !== '' && this.playlistContinuationString !== null) {
return true
}
break
case 'search':
if (this.searchContinuationString !== '' && this.searchContinuationString !== null) {
return true
}
break
}
return false
}
},
watch: {
@ -132,13 +154,11 @@ export default Vue.extend({
} else {
switch (this.backendPreference) {
case 'local':
this.apiUsed = 'local'
this.getChannelInfoLocal()
this.getChannelVideosLocal()
this.getPlaylistsLocal()
break
case 'invidious':
this.apiUsed = 'invidious'
this.getChannelInfoInvidious()
this.getPlaylistsInvidious()
break
@ -147,17 +167,37 @@ export default Vue.extend({
},
methods: {
getChannelInfoLocal: function () {
this.apiUsed = 'local'
ytch.getChannelInfo(this.id).then((response) => {
this.id = response.authorId
this.channelName = response.author
this.subCount = response.subscriberCount
this.thumbnailUrl = response.authorThumbnails[2].url
this.bannerUrl = `https://${response.authorBanners[response.authorBanners.length - 1].url}`
this.channelDescription = response.description
this.relatedChannels = response.relatedChannels
if (response.authorBanners !== null) {
const bannerUrl = response.authorBanners[response.authorBanners.length - 1].url
if (!bannerUrl.includes('https')) {
this.bannerUrl = `https://${bannerUrl}`
} else {
this.bannerUrl = bannerUrl
}
} else {
this.bannerUrl = null
}
this.isLoading = false
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'local' && this.backendFallback) {
console.log('Falling back to Invidious API')
this.getChannelInfoInvidious()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
},
@ -167,20 +207,30 @@ export default Vue.extend({
this.latestVideos = response.items
this.videoContinuationString = response.continuation
this.isElementListLoading = false
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'local' && this.backendFallback) {
console.log('Falling back to Invidious API')
this.getChannelInfoInvidious()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
},
channelLocalNextPage: function () {
console.log(this.videoContinuationString)
ytch.getChannelVideosMore(this.id, this.videoContinuationString).then((response) => {
ytch.getChannelVideosMore(this.videoContinuationString).then((response) => {
this.latestVideos = this.latestVideos.concat(response.items)
this.videoContinuationString = response.continuation
console.log(this.videoContinuationString)
}).catch((err) => {
console.log(err)
})
},
getChannelInfoInvidious: function () {
this.isLoading = true
this.apiUsed = 'invidious'
this.$store.dispatch('invidiousGetChannelInfo', this.id).then((response) => {
console.log(response)
@ -188,10 +238,14 @@ export default Vue.extend({
this.id = response.authorId
this.subCount = response.subCount
this.thumbnailUrl = response.authorThumbnails[3].url
this.bannerUrl = response.authorBanners[0].url
this.channelDescription = response.description
this.relatedChannels = response.relatedChannels
this.latestVideos = response.latestVideos
if (typeof (response.authorBanners) !== 'undefined') {
this.bannerUrl = response.authorBanners[0].url
}
this.isLoading = false
}).catch((error) => {
console.log(error)
@ -222,14 +276,25 @@ export default Vue.extend({
this.latestPlaylists = response.items
this.playlistContinuationString = response.continuation
this.isElementListLoading = false
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'local' && this.backendFallback) {
console.log('Falling back to Invidious API')
this.getPlaylistsInvidious()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
},
getPlaylistsLocalMore: function () {
ytch.getChannelPlaylistsMore(this.id, this.playlistContinuationString).then((response) => {
ytch.getChannelPlaylistsMore(this.playlistContinuationString).then((response) => {
console.log(response)
this.latestPlaylists = this.latestPlaylists.concat(response.items)
this.playlistContinuationString = response.continuation
}).catch((err) => {
console.log(err)
})
},
@ -252,8 +317,15 @@ export default Vue.extend({
this.playlistContinuationString = response.continuation
this.latestPlaylists = this.latestPlaylists.concat(response.playlists)
this.isElementListLoading = false
}).catch((error) => {
console.log(error)
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'invidious' && this.backendFallback) {
console.log('Falling back to Local API')
this.getPlaylistsLocal()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
},
@ -324,13 +396,24 @@ export default Vue.extend({
this.searchResults = response.items
this.isElementListLoading = false
this.searchContinuationString = response.continuation
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'local' && this.backendFallback) {
console.log('Falling back to Invidious API')
this.searchChannelInvidious()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
} else {
ytch.searchChannelMore(this.id, this.searchContinuationString).then((response) => {
ytch.searchChannelMore(this.searchContinuationString).then((response) => {
console.log(response)
this.searchResults = this.searchResults.concat(response.items)
this.isElementListLoading = false
this.searchContinuationString = response.continuation
}).catch((err) => {
console.log(err)
})
}
},
@ -349,6 +432,15 @@ export default Vue.extend({
this.searchResults = this.searchResults.concat(response)
this.isElementListLoading = false
this.searchPage++
}).catch((err) => {
console.log(err)
if (this.backendPreference === 'invidious' && this.backendFallback) {
console.log('Falling back to Local API')
this.searchChannelLocal()
} else {
this.isLoading = false
// TODO: Show toast with error message
}
})
}
}

View File

@ -11,9 +11,14 @@
class="card"
>
<img
v-if="bannerUrl !== null"
class="channelBanner"
:src="bannerUrl"
>
<img
v-else
class="defaultChannelBanner"
>
<div class="channelInfoContainer">
<div class="channelInfo">
<img
@ -102,8 +107,14 @@
v-html="channelDescription"
/>
<br>
<h2>Featured Channels</h2>
<ft-flex-box>
<h2
v-if="relatedChannels.length > 0"
>
Featured Channels
</h2>
<ft-flex-box
v-if="relatedChannels.length > 0"
>
<ft-channel-bubble
v-for="(channel, index) in relatedChannels"
:key="index"
@ -124,15 +135,37 @@
v-show="currentTab === 'videos'"
:data="latestVideos"
/>
<ft-flex-box
v-if="currentTab === 'videos' && latestVideos.length === 0"
>
<p class="message">
This channel does not currently have any videos
</p>
</ft-flex-box>
<ft-element-list
v-show="currentTab === 'playlists'"
:data="latestPlaylists"
/>
<ft-flex-box
v-if="currentTab === 'playlists' && latestPlaylists.length === 0"
>
<p class="message">
This channel does not currently have any playlists
</p>
</ft-flex-box>
<ft-element-list
v-show="currentTab === 'search'"
:data="searchResults"
/>
<ft-flex-box
v-if="currentTab === 'search' && searchResults.length === 0"
>
<p class="message">
Your search results have returned 0 results
</p>
</ft-flex-box>
<div
v-if="showFetchMoreButton"
class="getNextPage"
@click="handleFetchMore"
>

View File

@ -12,4 +12,7 @@
.playlistItems {
float: right;
width: 60%;
padding: 10px;
display: grid;
grid-gap: 10px;
}

View File

@ -13,16 +13,14 @@
v-if="!isLoading"
class="playlistItems"
>
<ft-flex-box>
<ft-list-video
v-for="(item, index) in playlistItems"
:key="index"
:data="item"
:playlist-id="playlistId"
force-list-type="list"
class="playlistItem"
/>
</ft-flex-box>
<ft-list-video
v-for="(item, index) in playlistItems"
:key="index"
:data="item"
:playlist-id="playlistId"
appearance="result"
force-list-type="list"
/>
</ft-card>
</div>
</template>

View File

@ -5,6 +5,7 @@
line-height: 45px;
text-align: center;
cursor: pointer;
margin-top: 16px;
}
.card {

View File

@ -214,14 +214,20 @@ export default Vue.extend({
this.videoDislikeCount = result.videoDetails.dislikes
this.isLive = result.player_response.videoDetails.isLive
if (this.videoDislikeCount === null) {
this.videoDislikeCount = 0
}
const subCount = result.videoDetails.author.subscriber_count
if (subCount >= 1000000) {
this.channelSubscriptionCountText = `${subCount / 1000000}M`
} else if (subCount >= 10000) {
this.channelSubscriptionCountText = `${subCount / 1000}K`
} else {
this.channelSubscriptionCountText = subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
if (typeof (subCount) !== 'undefined') {
if (subCount >= 1000000) {
this.channelSubscriptionCountText = `${subCount / 1000000}M`
} else if (subCount >= 10000) {
this.channelSubscriptionCountText = `${subCount / 1000}K`
} else {
this.channelSubscriptionCountText = subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
}
if (this.isLive) {

View File

@ -50,8 +50,10 @@
min-width: 380px
.watchVideoPlaylist, .watchVideoSidebar, .theatrePlaylist
height: 500px
margin: 0 8px 16px
.watchVideoSidebar, .theatrePlaylist
height: 500px
.watchVideoRecommendations, .theatreRecommendations
margin: 0 8px 16px