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 ci
- run: npm run lint - run: npm run lint
- run: npm run build --if-present - 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 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
with: with:
name: freetube-vue_0.8.0_amd64.deb name: freetube-vue_0.8.0_amd64.deb
path: build/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 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
with: with:
name: freetube-vue_0.8.0_amd64.AppImage name: freetube-vue_0.8.0_amd64.AppImage
path: build/FreeTube-Vue-0.8.0.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 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
with: with:
name: freetube-vue_0.8.0_amd64.rpm name: freetube-vue_0.8.0_amd64.rpm
path: build/freetube-vue-0.8.0.x86_64.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 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
with: with:
name: freetube-vue_0.8.0_amd64.zip name: freetube-vue_0.8.0_arm64.rpm
path: build/freetube-vue-0.8.0.zip 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 - name: Upload Windows .exe Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'windows') if: startsWith(matrix.os, 'windows')
@ -61,6 +106,12 @@ jobs:
with: with:
name: freetube-vue-0.8.0-setup-x64.exe name: freetube-vue-0.8.0-setup-x64.exe
path: build/FreeTube-Vue Setup 0.8.0.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 - name: Upload Mac .dmg Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
if: startsWith(matrix.os, 'macos') if: startsWith(matrix.os, 'macos')

7
.vscode/tasks.json vendored
View File

@ -7,6 +7,13 @@
"type": "npm", "type": "npm",
"script": "dev", "script": "dev",
"problemMatcher": [] "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 builder = require('electron-builder')
const Platform = builder.Platform const Platform = builder.Platform
const Arch = builder.Arch
const { name, productName } = require('../package.json') const { name, productName } = require('../package.json')
const args = process.argv
let targets let targets
var platform = os.platform() var platform = os.platform()
@ -12,7 +14,13 @@ if (platform == 'darwin') {
} else if (platform == 'win32') { } else if (platform == 'win32') {
targets = Platform.WINDOWS.createTarget() targets = Platform.WINDOWS.createTarget()
} else if (platform == 'linux') { } 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 = { const config = {
@ -47,7 +55,7 @@ const config = {
linux: { linux: {
category: 'Network', category: 'Network',
icon: '_icons/icon.png', icon: '_icons/icon.png',
target: ['deb', 'rpm', 'zip', 'AppImage'], target: ['deb', 'zip', 'apk', 'rpm', 'AppImage'],
}, },
mac: { mac: {
category: 'public.app-category.utilities', category: 'public.app-category.utilities',

View File

@ -85,6 +85,13 @@ if (isDevMode) {
ignore: ['.*'], 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/'), '@': path.join(__dirname, '../src/'),
src: path.join(__dirname, '../src/'), src: path.join(__dirname, '../src/'),
icons: path.join(__dirname, '../_icons/'), icons: path.join(__dirname, '../_icons/'),
images: path.join(__dirname, '../src/renderer/assets/img/'),
}, },
extensions: ['.ts', '.js', '.vue', '.json'], extensions: ['.ts', '.js', '.vue', '.json'],
}, },
@ -171,6 +172,13 @@ if (isDevMode) {
ignore: ['.*'], 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', vue$: 'vue/dist/vue.esm.js',
src: path.join(__dirname, '../src/'), src: path.join(__dirname, '../src/'),
icons: path.join(__dirname, '../_icons/'), icons: path.join(__dirname, '../_icons/'),
images: path.join(__dirname, '../src/renderer/assets/img/'),
}, },
extensions: ['.js', '.vue', '.json', '.css'], extensions: ['.js', '.vue', '.json', '.css'],
}, },
@ -174,6 +175,13 @@ if (isDevMode) {
ignore: ['.*'], ignore: ['.*'],
}, },
}, },
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/web/images'),
globOptions: {
ignore: ['.*'],
},
},
] ]
} }
), ),

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

View File

@ -64,6 +64,7 @@ function createWindow () {
backgroundColor: '#fff', backgroundColor: '#fff',
width: 960, width: 960,
height: 540, height: 540,
autoHideMenuBar: true,
// useContentSize: true, // useContentSize: true,
webPreferences: { webPreferences: {
nodeIntegration: true, 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 Vue from 'vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.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 FtListVideo from '../ft-list-video/ft-list-video.vue'
import FtListChannel from '../ft-list-channel/ft-list-channel.vue' import FtListChannel from '../ft-list-channel/ft-list-channel.vue'
import FtListPlaylist from '../ft-list-playlist/ft-list-playlist.vue' import FtListPlaylist from '../ft-list-playlist/ft-list-playlist.vue'
@ -9,7 +9,7 @@ export default Vue.extend({
name: 'FtElementList', name: 'FtElementList',
components: { components: {
'ft-flex-box': FtFlexBox, 'ft-flex-box': FtFlexBox,
'ft-grid': FtGrid, 'ft-auto-grid': FtAutoGrid,
'ft-list-video': FtListVideo, 'ft-list-video': FtListVideo,
'ft-list-channel': FtListChannel, 'ft-list-channel': FtListChannel,
'ft-list-playlist': FtListPlaylist 'ft-list-playlist': FtListPlaylist

View File

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

View File

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

View File

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

View File

@ -10,6 +10,10 @@
secondary: theme === 'secondary', secondary: theme === 'secondary',
shadow: useShadow shadow: useShadow
}" }"
:style="{
padding: padding + 'px',
fontSize: size + 'px'
}"
@click="handleIconClick" @click="handleIconClick"
/> />
<div <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: { data: {
type: Object, type: Object,
required: true required: true
},
appearance: {
type: String,
required: true
} }
}, },
data: function () { data: function () {
@ -32,10 +36,6 @@ export default Vue.extend({
} }
}, },
methods: { methods: {
goToChannel: function () {
this.$router.push({ path: `/channel/${this.id}` })
},
parseLocalData: function () { parseLocalData: function () {
this.thumbnail = this.data.avatar this.thumbnail = this.data.avatar
this.channelName = this.data.name this.channelName = this.data.name

View File

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

View File

@ -1,32 +1,41 @@
<template> <template>
<div <div
class="ft-list-channel" class="ft-list-channel ft-list-item"
:class="{ list: listType === 'list', grid: listType === 'grid' }" :class="{
list: listType === 'list',
grid: listType === 'grid',
[appearance]: true
}"
> >
<div class="channelThumbnail"> <div class="channelThumbnail">
<router-link
:to="`/channel/${id}`"
>
<img <img
:src="thumbnail" :src="thumbnail"
@click="goToChannel(id)" class="channelImage"
> >
</router-link>
</div> </div>
<p <div class="info">
class="channelName" <router-link
@click="goToChannel(id)" class="title"
:to="`/channel/${id}`"
> >
{{ channelName }} {{ channelName }}
</p> </router-link>
<div class="infoLine">
<span <span
class="subscriberCount" class="subscriberCount"
@click="goToChannel(id)"
> >
{{ subscriberCount }} subscribers {{ subscriberCount }} subscribers
</span> </span>
<span <span
class="videoCount" class="videoCount"
@click="goToChannel(id)"
> >
- {{ videoCount }} videos - {{ videoCount }} videos
</span> </span>
</div>
<p <p
v-if="listType !== 'grid'" v-if="listType !== 'grid'"
class="description" class="description"
@ -34,7 +43,8 @@
{{ description }} {{ description }}
</p> </p>
</div> </div>
</div>
</template> </template>
<script src="./ft-list-channel.js" /> <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: { data: {
type: Object, type: Object,
required: true required: true
},
appearance: {
type: String,
required: true
} }
}, },
data: function () { data: function () {
@ -58,14 +62,6 @@ export default Vue.extend({
this.channelLink = this.data.author.ref this.channelLink = this.data.author.ref
this.playlistLink = this.data.link this.playlistLink = this.data.link
this.videoCount = parseInt(this.data.length.split(' ')[0]) 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> <template>
<div <div
class="ft-list-video" class="ft-list-video ft-list-item"
:appearance="appearance"
:class="{ list: listType === 'list', grid: listType === 'grid' }" :class="{ list: listType === 'list', grid: listType === 'grid' }"
> >
<div class="videoThumbnail"> <router-link
class="videoThumbnail"
:to="`/playlist/${playlistId}`"
>
<img <img
:src="thumbnail" :src="thumbnail"
@click="goToPlaylist(playlistId)" class="thumbnailImage"
> >
<div <div
class="videoCountContainer" class="videoCountContainer"
@click="goToPlaylist(playlistId)"
> >
<span> <div class="background" />
{{ videoCount }} <div class="inner">
<br> <div>{{ videoCount }}</div>
<font-awesome-icon icon="list" /> <div><font-awesome-icon icon="list" /></div>
</span>
</div> </div>
</div> </div>
<p </router-link>
class="playlistTitle" <div class="info">
@click="goToPlaylist(playlistId)" <router-link
class="title"
:to="`/playlist/${playlistId}`"
> >
{{ title }} {{ title }}
</p> </router-link>
<p <div class="infoLine">
<router-link
class="channelName" class="channelName"
@click="goToChannel(channelId)" :to="`/channel/${channelId}`"
> >
{{ channelName }} {{ channelName }}
</p> </router-link>
</div>
</div>
</div> </div>
</template> </template>
<script src="./ft-list-playlist.js" /> <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: { forceListType: {
type: String, type: String,
default: null default: null
},
appearance: {
type: String,
required: true
} }
}, },
data: function () { data: function () {
@ -121,31 +125,6 @@ export default Vue.extend({
} }
}, },
methods: { 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 () { toggleSave: function () {
console.log('TODO: ft-list-video method toggleSave') 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> <template>
<div <div
class="ft-list-video" class="ft-list-video ft-list-item"
:class="{ :class="{
list: (listType === 'list' || forceListType === 'list') && forceListType !== 'grid', 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"
>
<router-link
class="thumbnailLink"
:to="{
path: `/watch/${id}`,
query: playlistId ? {playlistId} : {}
}" }"
> >
<div class="videoThumbnail">
<img <img
:src="thumbnail" :src="thumbnail"
@click="play(id)" class="thumbnailImage"
> >
<p </router-link>
v-if="!isLive" <div
class="videoDuration" class="videoDuration"
@click="play(id)" :class="{ live: isLive }"
> >
{{ duration }} {{ isLive ? "Live" : duration }}
</p> </div>
<font-awesome-icon <ft-icon-button
v-if="!isLive" v-if="!isLive"
icon="star" icon="star"
class="favoritesIcon" class="favoritesIcon"
:padding="appearance === `watchPlaylistItem` ? 5 : 6"
:size="appearance === `watchPlaylistItem` ? 14 : 18"
:class="{ favorited: isFavorited }" :class="{ favorited: isFavorited }"
@click="toggleSave(id)" @click="toggleSave(id)"
/> />
@ -29,7 +41,7 @@
v-if="watched" v-if="watched"
class="videoWatched" class="videoWatched"
> >
WATCHED Watched
</div> </div>
<div <div
v-if="watched" v-if="watched"
@ -37,65 +49,56 @@
:style="{width: progressPercentage + '%'}" :style="{width: progressPercentage + '%'}"
/> />
</div> </div>
<div class="info">
<ft-icon-button <ft-icon-button
class="optionsButton" class="optionsButton"
title="More Options" title="More Options"
theme="base" theme="base"
:size="16"
:use-shadow="false" :use-shadow="false"
dropdown-position-x="left" dropdown-position-x="left"
:dropdown-names="optionsNames" :dropdown-names="optionsNames"
:dropdown-values="optionsValues" :dropdown-values="optionsValues"
@click="handleOptionsClick" @click="handleOptionsClick"
/> />
<p <router-link
class="videoTitle" class="title"
@click="play(id)" :to="{
path: `/watch/${id}`,
query: playlistId ? {playlistId} : {}
}"
> >
{{ title }} {{ title }}
</p> </router-link>
<p <div class="infoLine">
<router-link
class="channelName" class="channelName"
@click="goToChannel" :to="`/channel/${channelId}`"
> >
{{ channelName }} <span>{{ channelName }}</span>
</p> </router-link>
<span <span
v-if="!isLive && !hideViews" v-if="!isLive && !hideViews"
class="viewCount" class="viewCount"
@click="play(id)" > {{ viewCount }} views</span>
>
{{ viewCount }} views
</span>
<span <span
v-if="uploadedTime !== '' && !isLive" v-if="uploadedTime !== '' && !isLive"
class="uploadedTime" class="uploadedTime"
@click="play(id)" > {{ uploadedTime }}</span>
>
- {{ uploadedTime }}
</span>
<span <span
v-if="isLive" v-if="isLive"
class="viewCount" class="viewCount"
@click="play(id)" > {{ viewCount }} watching</span>
> </div>
{{ viewCount }} watching
</span>
<p <p
v-if="listType !== 'grid'" v-if="listType !== 'grid' && appearance === 'result'"
class="description" class="description"
@click="play(id)"
> >
{{ description }} {{ description }}
</p> </p>
<span </div>
v-if="isLive"
class="liveText"
@click="play(id)"
>
LIVE NOW
</span>
</div> </div>
</template> </template>
<script src="./ft-list-video.js" /> <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 { .playlistItems {
overflow-y: auto; overflow-y: auto;
height: 395px; height: 395px;
margin-top: -15px;
margin-left: -16px;
margin-right: -16px;
} }
.playlistItem { .playlistItem {
height: 75px; display: grid;
width: 100%; grid-template-columns: 30px 1fr;
align-items: center;
transition: background 0.2s ease-out; transition: background 0.2s ease-out;
} }
.playlistItem:not(:last-child) {
margin-bottom: 8px;
}
.playlistItem:hover { .playlistItem:hover {
background-color: var(--side-nav-hover-color); background-color: var(--side-nav-hover-color);
transition: background 0.2s ease-in; transition: background 0.2s ease-in;
} }
.videoIndexContainer {
text-align: center;
}
.videoIndex { .videoIndex {
float: left;
position: relative;
top: 15px;
left: 10px;
color: var(--tertiary-text-color); color: var(--tertiary-text-color);
} }
.videoIndexIcon { .videoIndexIcon {
float: left;
position: relative;
font-size: 14px; font-size: 14px;
top: 32px;
left: 10px;
color: var(--tertiary-text-color); color: var(--tertiary-text-color);
} }

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template> <template>
<ft-card class="relative"> <ft-card class="relative watchVideoRecommendations">
<h3> <h3>
Up Next Up Next
</h3> </h3>
@ -7,8 +7,8 @@
v-for="(video, index) in data" v-for="(video, index) in data"
:key="index" :key="index"
:data="video" :data="video"
appearance="recommendation"
force-list-type="list" force-list-type="list"
class="videoRecommendation"
/> />
</ft-card> </ft-card>
</template> </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); --link-visited-color: var(--accent-color-visited);
--card-bg-color: #FFFFFF; --card-bg-color: #FFFFFF;
--secondary-card-bg-color: #eeeeee; --secondary-card-bg-color: #eeeeee;
--scrollbar-color: #CCCCCC;
--scrollbar-color-hover: #BDBDBD;
--side-nav-color: #FFFFFF; --side-nav-color: #FFFFFF;
--side-nav-hover-color: #e0e0e0; --side-nav-hover-color: #e0e0e0;
--side-nav-active-color: #757575; --side-nav-active-color: #757575;
@ -32,6 +34,8 @@
--link-visited-color: var(--accent-color-visited); --link-visited-color: var(--accent-color-visited);
--card-bg-color: #303030; --card-bg-color: #303030;
--secondary-card-bg-color: rgba(0, 0, 0, 0.75); --secondary-card-bg-color: rgba(0, 0, 0, 0.75);
--scrollbar-color: #414141;
--scrollbar-color-hover: #757575;
--side-nav-color: #262626; --side-nav-color: #262626;
--side-nav-hover-color: #212121; --side-nav-hover-color: #212121;
--side-nav-active-color: #303030; --side-nav-active-color: #303030;
@ -53,6 +57,8 @@
--link-visited-color: var(--accent-color-visited); --link-visited-color: var(--accent-color-visited);
--card-bg-color: #000000; --card-bg-color: #000000;
--secondary-card-bg-color: rgba(0, 0, 0, 0.75); --secondary-card-bg-color: rgba(0, 0, 0, 0.75);
--scrollbar-color: #515151;
--scrollbar-color-hover: #424242;
--side-nav-color: #000000; --side-nav-color: #000000;
--side-nav-hover-color: #212121; --side-nav-hover-color: #212121;
--side-nav-active-color: #303030; --side-nav-active-color: #303030;
@ -442,9 +448,24 @@ body {
color: var(--primary-text-color); color: var(--primary-text-color);
background-color: var(--bg-color); background-color: var(--bg-color);
} }
a:link { a:link {
color: var(--link-color); color: var(--link-color);
} }
a:visited { a:visited {
color: var(--link-visited-color); 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; max-height: 300px;
} }
.defaultChannelBanner {
width: 100%;
position: absolute;
top: 0px;
left: 0px;
height: 200px;
background-color: black;
background-image: url("~images/defaultBanner.png");
}
.channelInfoContainer { .channelInfoContainer {
width: 100%; width: 100%;
height: 200px; height: 200px;
@ -116,6 +126,10 @@
margin-top: 200px; margin-top: 200px;
} }
.message {
color: var(--tertiary-text-color);
}
.getNextPage { .getNextPage {
background-color: var(--search-bar-color); background-color: var(--search-bar-color);
width: 100%; width: 100%;
@ -123,4 +137,5 @@
line-height: 45px; line-height: 45px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
margin-top: 16px;
} }

View File

@ -88,6 +88,28 @@ export default Vue.extend({
formattedSubCount: function () { formattedSubCount: function () {
return this.subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') 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: { watch: {
@ -132,13 +154,11 @@ export default Vue.extend({
} else { } else {
switch (this.backendPreference) { switch (this.backendPreference) {
case 'local': case 'local':
this.apiUsed = 'local'
this.getChannelInfoLocal() this.getChannelInfoLocal()
this.getChannelVideosLocal() this.getChannelVideosLocal()
this.getPlaylistsLocal() this.getPlaylistsLocal()
break break
case 'invidious': case 'invidious':
this.apiUsed = 'invidious'
this.getChannelInfoInvidious() this.getChannelInfoInvidious()
this.getPlaylistsInvidious() this.getPlaylistsInvidious()
break break
@ -147,17 +167,37 @@ export default Vue.extend({
}, },
methods: { methods: {
getChannelInfoLocal: function () { getChannelInfoLocal: function () {
this.apiUsed = 'local'
ytch.getChannelInfo(this.id).then((response) => { ytch.getChannelInfo(this.id).then((response) => {
this.id = response.authorId this.id = response.authorId
this.channelName = response.author this.channelName = response.author
this.subCount = response.subscriberCount this.subCount = response.subscriberCount
this.thumbnailUrl = response.authorThumbnails[2].url this.thumbnailUrl = response.authorThumbnails[2].url
this.bannerUrl = `https://${response.authorBanners[response.authorBanners.length - 1].url}`
this.channelDescription = response.description this.channelDescription = response.description
this.relatedChannels = response.relatedChannels 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 this.isLoading = false
}).catch((err) => { }).catch((err) => {
console.log(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.latestVideos = response.items
this.videoContinuationString = response.continuation this.videoContinuationString = response.continuation
this.isElementListLoading = false 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 () { channelLocalNextPage: function () {
console.log(this.videoContinuationString) ytch.getChannelVideosMore(this.videoContinuationString).then((response) => {
ytch.getChannelVideosMore(this.id, this.videoContinuationString).then((response) => {
this.latestVideos = this.latestVideos.concat(response.items) this.latestVideos = this.latestVideos.concat(response.items)
this.videoContinuationString = response.continuation this.videoContinuationString = response.continuation
console.log(this.videoContinuationString) }).catch((err) => {
console.log(err)
}) })
}, },
getChannelInfoInvidious: function () { getChannelInfoInvidious: function () {
this.isLoading = true this.isLoading = true
this.apiUsed = 'invidious'
this.$store.dispatch('invidiousGetChannelInfo', this.id).then((response) => { this.$store.dispatch('invidiousGetChannelInfo', this.id).then((response) => {
console.log(response) console.log(response)
@ -188,10 +238,14 @@ export default Vue.extend({
this.id = response.authorId this.id = response.authorId
this.subCount = response.subCount this.subCount = response.subCount
this.thumbnailUrl = response.authorThumbnails[3].url this.thumbnailUrl = response.authorThumbnails[3].url
this.bannerUrl = response.authorBanners[0].url
this.channelDescription = response.description this.channelDescription = response.description
this.relatedChannels = response.relatedChannels this.relatedChannels = response.relatedChannels
this.latestVideos = response.latestVideos this.latestVideos = response.latestVideos
if (typeof (response.authorBanners) !== 'undefined') {
this.bannerUrl = response.authorBanners[0].url
}
this.isLoading = false this.isLoading = false
}).catch((error) => { }).catch((error) => {
console.log(error) console.log(error)
@ -222,14 +276,25 @@ export default Vue.extend({
this.latestPlaylists = response.items this.latestPlaylists = response.items
this.playlistContinuationString = response.continuation this.playlistContinuationString = response.continuation
this.isElementListLoading = false 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 () { getPlaylistsLocalMore: function () {
ytch.getChannelPlaylistsMore(this.id, this.playlistContinuationString).then((response) => { ytch.getChannelPlaylistsMore(this.playlistContinuationString).then((response) => {
console.log(response) console.log(response)
this.latestPlaylists = this.latestPlaylists.concat(response.items) this.latestPlaylists = this.latestPlaylists.concat(response.items)
this.playlistContinuationString = response.continuation this.playlistContinuationString = response.continuation
}).catch((err) => {
console.log(err)
}) })
}, },
@ -252,8 +317,15 @@ export default Vue.extend({
this.playlistContinuationString = response.continuation this.playlistContinuationString = response.continuation
this.latestPlaylists = this.latestPlaylists.concat(response.playlists) this.latestPlaylists = this.latestPlaylists.concat(response.playlists)
this.isElementListLoading = false this.isElementListLoading = false
}).catch((error) => { }).catch((err) => {
console.log(error) 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.searchResults = response.items
this.isElementListLoading = false this.isElementListLoading = false
this.searchContinuationString = response.continuation 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 { } else {
ytch.searchChannelMore(this.id, this.searchContinuationString).then((response) => { ytch.searchChannelMore(this.searchContinuationString).then((response) => {
console.log(response) console.log(response)
this.searchResults = this.searchResults.concat(response.items) this.searchResults = this.searchResults.concat(response.items)
this.isElementListLoading = false this.isElementListLoading = false
this.searchContinuationString = response.continuation this.searchContinuationString = response.continuation
}).catch((err) => {
console.log(err)
}) })
} }
}, },
@ -349,6 +432,15 @@ export default Vue.extend({
this.searchResults = this.searchResults.concat(response) this.searchResults = this.searchResults.concat(response)
this.isElementListLoading = false this.isElementListLoading = false
this.searchPage++ 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" class="card"
> >
<img <img
v-if="bannerUrl !== null"
class="channelBanner" class="channelBanner"
:src="bannerUrl" :src="bannerUrl"
> >
<img
v-else
class="defaultChannelBanner"
>
<div class="channelInfoContainer"> <div class="channelInfoContainer">
<div class="channelInfo"> <div class="channelInfo">
<img <img
@ -102,8 +107,14 @@
v-html="channelDescription" v-html="channelDescription"
/> />
<br> <br>
<h2>Featured Channels</h2> <h2
<ft-flex-box> v-if="relatedChannels.length > 0"
>
Featured Channels
</h2>
<ft-flex-box
v-if="relatedChannels.length > 0"
>
<ft-channel-bubble <ft-channel-bubble
v-for="(channel, index) in relatedChannels" v-for="(channel, index) in relatedChannels"
:key="index" :key="index"
@ -124,15 +135,37 @@
v-show="currentTab === 'videos'" v-show="currentTab === 'videos'"
:data="latestVideos" :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 <ft-element-list
v-show="currentTab === 'playlists'" v-show="currentTab === 'playlists'"
:data="latestPlaylists" :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 <ft-element-list
v-show="currentTab === 'search'" v-show="currentTab === 'search'"
:data="searchResults" :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 <div
v-if="showFetchMoreButton"
class="getNextPage" class="getNextPage"
@click="handleFetchMore" @click="handleFetchMore"
> >

View File

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

View File

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

View File

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

View File

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

View File

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