Push Latest Code to Repository
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"chrome": "73",
|
||||||
|
"node": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@babel/typescript"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/proposal-class-properties",
|
||||||
|
"@babel/proposal-object-rest-spread"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = crlf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
_scripts
|
||||||
|
dist
|
|
@ -0,0 +1,42 @@
|
||||||
|
module.exports = {
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#using-configuration-files-1
|
||||||
|
root: true,
|
||||||
|
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#specifying-environments
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#specifying-parser
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
|
|
||||||
|
// https://vuejs.github.io/eslint-plugin-vue/user-guide/#faq
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#extending-configuration-files
|
||||||
|
// order matters: from least important to most important in terms of overriding
|
||||||
|
// Prettier + Vue: https://medium.com/@gogl.alex/how-to-properly-set-up-eslint-with-prettier-for-vue-or-nuxt-in-vscode-e42532099a9c
|
||||||
|
extends: [
|
||||||
|
'prettier/vue',
|
||||||
|
'prettier',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:vue/recommended',
|
||||||
|
'standard',
|
||||||
|
],
|
||||||
|
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#configuring-plugins
|
||||||
|
plugins: ['vue'],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'vue/no-v-html': "off",
|
||||||
|
'no-console': 0,
|
||||||
|
'no-unused-vars': 1,
|
||||||
|
'no-undef': 1,
|
||||||
|
'vue/no-template-key': 1
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,104 +1,18 @@
|
||||||
# Logs
|
.DS_Store
|
||||||
logs
|
dist/electron/*
|
||||||
*.log
|
dist/web/*
|
||||||
npm-debug.log*
|
build/*
|
||||||
yarn-debug.log*
|
!build/icons
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
coverage
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
npm-debug.log
|
||||||
|
npm-debug.log.*
|
||||||
# TypeScript v1 declaration files
|
thumbs.db
|
||||||
typings/
|
!.gitkeep
|
||||||
|
data/tmp/
|
||||||
# TypeScript cache
|
.tmp/
|
||||||
*.tsbuildinfo
|
tmp/
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
dist
|
||||||
|
coverage
|
||||||
# Gatsby files
|
__coverage__
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode9.3
|
||||||
|
language: node_js
|
||||||
|
node_js: '12'
|
||||||
|
env:
|
||||||
|
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||||
|
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||||
|
|
||||||
|
- os: linux
|
||||||
|
services: docker
|
||||||
|
language: node_js
|
||||||
|
node_js: '12'
|
||||||
|
env:
|
||||||
|
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||||
|
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- $HOME/.cache/electron
|
||||||
|
- $HOME/.cache/electron-builder
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v2.3.1/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-2.3.1.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1
|
||||||
|
export PATH="/tmp/git-lfs:$PATH"
|
||||||
|
fi
|
||||||
|
before_script:
|
||||||
|
- git lfs pull
|
||||||
|
|
||||||
|
script:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||||
|
docker run --rm \
|
||||||
|
--env-file <(env | grep -v '\r' | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \
|
||||||
|
-v ${PWD}:/project \
|
||||||
|
-v ~/.cache/electron:/root/.cache/electron \
|
||||||
|
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||||
|
electronuserland/builder:wine
|
||||||
|
fi
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -rf $HOME/.cache/electron-builder/wine
|
||||||
|
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
|
@ -0,0 +1,8 @@
|
||||||
|
##########################################################
|
||||||
|
#### WhiteSource "Bolt for Github" configuration file ####
|
||||||
|
##########################################################
|
||||||
|
|
||||||
|
# Configuration #
|
||||||
|
#---------------#
|
||||||
|
ws.repo.scan=true
|
||||||
|
vulnerable.check.run.conclusion.level=failure
|
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 264 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 579 B |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="120" viewBox="0 0 400 120"><style>.st0{fill:#FFFFFF;width:16px;height:16px} .st1{fill:none;stroke:#FFFFFF;stroke-width:1.5;stroke-linecap:round;} .st2{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-linecap:round;} .st3{fill:none;stroke:#FFFFFF;} .st4{fill:#231F20;} .st5{opacity:0.75;fill:none;stroke:#FFFFFF;stroke-width:5;enable-background:new;} .st6{fill:none;stroke:#FFFFFF;stroke-width:5;} .st7{opacity:0.4;fill:#FFFFFF;enable-background:new;} .st8{opacity:0.6;fill:#FFFFFF;enable-background:new;} .st9{opacity:0.8;fill:#FFFFFF;enable-background:new;} .st10{opacity:0.9;fill:#FFFFFF;enable-background:new;} .st11{opacity:0.3;fill:#FFFFFF;enable-background:new;} .st12{opacity:0.5;fill:#FFFFFF;enable-background:new;} .st13{opacity:0.7;fill:#FFFFFF;enable-background:new;}</style><path class="st0" d="M16.5 8.5c.3.1.4.5.2.8-.1.1-.1.2-.2.2l-11.4 7c-.5.3-.8.1-.8-.5V2c0-.5.4-.8.8-.5l11.4 7z"/><path class="st0" d="M24 1h2.2c.6 0 1 .4 1 1v14c0 .6-.4 1-1 1H24c-.6 0-1-.4-1-1V2c0-.5.4-1 1-1zm9.8 0H36c.6 0 1 .4 1 1v14c0 .6-.4 1-1 1h-2.2c-.6 0-1-.4-1-1V2c0-.5.4-1 1-1z"/><path class="st0" d="M81 1.4c0-.6.4-1 1-1h5.4c.6 0 .7.3.3.7l-6 6c-.4.4-.7.3-.7-.3V1.4zm0 15.8c0 .6.4 1 1 1h5.4c.6 0 .7-.3.3-.7l-6-6c-.4-.4-.7-.3-.7.3v5.4zM98.8 1.4c0-.6-.4-1-1-1h-5.4c-.6 0-.7.3-.3.7l6 6c.4.4.7.3.7-.3V1.4zm0 15.8c0 .6-.4 1-1 1h-5.4c-.6 0-.7-.3-.3-.7l6-6c.4-.4.7-.3.7.3v5.4z"/><path class="st0" d="M112.7 5c0 .6.4 1 1 1h4.1c.6 0 .7-.3.3-.7L113.4.6c-.4-.4-.7-.3-.7.3V5zm-7.1 1c.6 0 1-.4 1-1V.9c0-.6-.3-.7-.7-.3l-4.7 4.7c-.4.4-.3.7.3.7h4.1zm1 7.1c0-.6-.4-1-1-1h-4.1c-.6 0-.7.3-.3.7l4.7 4.7c.4.4.7.3.7-.3v-4.1zm7.1-1c-.6 0-1 .4-1 1v4.1c0 .5.3.7.7.3l4.7-4.7c.4-.4.3-.7-.3-.7h-4.1z"/><path class="st0" d="M67 5.8c-.5.4-1.2.6-1.8.6H62c-.6 0-1 .4-1 1v5.7c0 .6.4 1 1 1h4.2c.3.2.5.4.8.6l3.5 2.6c.4.3.8.1.8-.4V3.5c0-.5-.4-.7-.8-.4L67 5.8z"/><path class="st1" d="M73.9 2.5s3.9-.8 3.9 7.7-3.9 7.8-3.9 7.8"/><path class="st1" d="M72.6 6.4s2.6-.4 2.6 3.8-2.6 3.9-2.6 3.9"/><path class="st0" d="M47 5.8c-.5.4-1.2.6-1.8.6H42c-.6 0-1 .4-1 1v5.7c0 .6.4 1 1 1h4.2c.3.2.5.4.8.6l3.5 2.6c.4.3.8.1.8-.4V3.5c0-.5-.4-.7-.8-.4L47 5.8z"/><path class="st2" d="M52.8 7l5.4 5.4m-5.4 0L58.2 7"/><path class="st3" d="M128.7 8.6c-6.2-4.2-6.5 7.8 0 3.9m6.5-3.9c-6.2-4.2-6.5 7.8 0 3.9"/><path class="st0" d="M122.2 3.4h15.7v13.1h-15.7V3.4zM120.8 2v15.7h18.3V2h-18.3z"/><path class="st0" d="M143.2 3h14c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2h-14c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2z"/><path class="st4" d="M146.4 13.8c-.8 0-1.6-.4-2.1-1-1.1-1.4-1-3.4.1-4.8.5-.6 2-1.7 4.6.2l-.6.8c-1.4-1-2.6-1.1-3.3-.3-.8 1-.8 2.4-.1 3.5.7.9 1.9.8 3.4-.1l.5.9c-.7.5-1.6.7-2.5.8zm7.5 0c-.8 0-1.6-.4-2.1-1-1.1-1.4-1-3.4.1-4.8.5-.6 2-1.7 4.6.2l-.5.8c-1.4-1-2.6-1.1-3.3-.3-.8 1-.8 2.4-.1 3.5.7.9 1.9.8 3.4-.1l.5.9c-.8.5-1.7.7-2.6.8z"/><path class="st0" d="M60.3 77c.6.2.8.8.6 1.4-.1.3-.3.5-.6.6L30 96.5c-1 .6-1.7.1-1.7-1v-35c0-1.1.8-1.5 1.7-1L60.3 77z"/><path class="st5" d="M2.5 79c0-20.7 16.8-37.5 37.5-37.5S77.5 58.3 77.5 79 60.7 116.5 40 116.5 2.5 99.7 2.5 79z"/><path class="st0" d="M140.3 77c.6.2.8.8.6 1.4-.1.3-.3.5-.6.6L110 96.5c-1 .6-1.7.1-1.7-1v-35c0-1.1.8-1.5 1.7-1L140.3 77z"/><path class="st6" d="M82.5 79c0-20.7 16.8-37.5 37.5-37.5s37.5 16.8 37.5 37.5-16.8 37.5-37.5 37.5S82.5 99.7 82.5 79z"/><circle class="st0" cx="201.9" cy="47.1" r="8.1"/><circle class="st7" cx="233.9" cy="79" r="5"/><circle class="st8" cx="201.9" cy="110.9" r="6"/><circle class="st9" cx="170.1" cy="79" r="7"/><circle class="st10" cx="178.2" cy="56.3" r="7.5"/><circle class="st11" cx="226.3" cy="56.1" r="4.5"/><circle class="st12" cx="225.8" cy="102.8" r="5.5"/><circle class="st13" cx="178.2" cy="102.8" r="6.5"/><path class="st0" d="M178 9.4c0 .4-.4.7-.9.7-.1 0-.2 0-.2-.1L172 8.2c-.5-.2-.6-.6-.1-.8l6.2-3.6c.5-.3.8-.1.7.5l-.8 5.1z"/><path class="st0" d="M169.4 15.9c-1 0-2-.2-2.9-.7-2-1-3.2-3-3.2-5.2.1-3.4 2.9-6 6.3-6 2.5.1 4.8 1.7 5.6 4.1l.1-.1 2.1 1.1c-.6-4.4-4.7-7.5-9.1-6.9-3.9.6-6.9 3.9-7 7.9 0 2.9 1.7 5.6 4.3 7 1.2.6 2.5.9 3.8 1 2.6 0 5-1.2 6.6-3.3l-1.8-.9c-1.2 1.2-3 2-4.8 2z"/><path class="st0" d="M183.4 3.2c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5-1.5-.7-1.5-1.5c0-.9.7-1.5 1.5-1.5zm5.1 0h8.5c.9 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5h-8.5c-.9 0-1.5-.7-1.5-1.5-.1-.9.6-1.5 1.5-1.5zm-5.1 5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5-1.5-.7-1.5-1.5c0-.9.7-1.5 1.5-1.5zm5.1 0h8.5c.9 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5h-8.5c-.9 0-1.5-.7-1.5-1.5-.1-.9.6-1.5 1.5-1.5zm-5.1 5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5-1.5-.7-1.5-1.5c0-.9.7-1.5 1.5-1.5zm5.1 0h8.5c.9 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5h-8.5c-.9 0-1.5-.7-1.5-1.5-.1-.9.6-1.5 1.5-1.5z"/></svg>
|
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,77 @@
|
||||||
|
const os = require('os')
|
||||||
|
const builder = require('electron-builder')
|
||||||
|
|
||||||
|
const Platform = builder.Platform
|
||||||
|
const { name, productName } = require('../package.json')
|
||||||
|
|
||||||
|
let targets
|
||||||
|
var platform = os.platform()
|
||||||
|
|
||||||
|
if (platform == 'darwin') {
|
||||||
|
targets = Platform.MAC.createTarget()
|
||||||
|
} else if (platform == 'win32') {
|
||||||
|
targets = Platform.WINDOWS.createTarget()
|
||||||
|
} else if (platform == 'linux') {
|
||||||
|
targets = Platform.LINUX.createTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
appId: `io.freetubeapp.${name}`,
|
||||||
|
copyright: 'Copyleft ©2020 freetubeapp@protonmail.com',
|
||||||
|
// asar: false,
|
||||||
|
// compression: 'store',
|
||||||
|
// productName,
|
||||||
|
directories: {
|
||||||
|
output: './build/',
|
||||||
|
},
|
||||||
|
files: ['_icons/icon.*', './dist/**/*'],
|
||||||
|
dmg: {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
path: '/Applications',
|
||||||
|
type: 'link',
|
||||||
|
x: 410,
|
||||||
|
y: 230,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'file',
|
||||||
|
x: 130,
|
||||||
|
y: 230,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
window: {
|
||||||
|
height: 380,
|
||||||
|
width: 540,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
icon: '_icons/icon.png',
|
||||||
|
target: ['deb', 'snap', 'AppImage'],
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
category: 'public.app-category.utilities',
|
||||||
|
icon: '_icons/icon.icns',
|
||||||
|
target: ['dmg', 'zip'],
|
||||||
|
type: 'distribution',
|
||||||
|
},
|
||||||
|
win: {
|
||||||
|
icon: '_icons/icon.ico',
|
||||||
|
target: ['nsis', 'zip', 'portable'],
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
allowToChangeInstallationDirectory: true,
|
||||||
|
oneClick: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.build({
|
||||||
|
targets,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
.then(m => {
|
||||||
|
console.log(m)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
})
|
|
@ -0,0 +1,120 @@
|
||||||
|
process.env.NODE_ENV = 'development'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const WebpackDevServer = require('webpack-dev-server')
|
||||||
|
const kill = require('tree-kill')
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
|
|
||||||
|
const mainConfig = require('./webpack.main.config')
|
||||||
|
const rendererConfig = require('./webpack.renderer.config')
|
||||||
|
const workersConfig = require('./webpack.workers.config')
|
||||||
|
|
||||||
|
let electronProcess = null
|
||||||
|
let manualRestart = null
|
||||||
|
const remoteDebugging = !!(
|
||||||
|
process.argv[2] && process.argv[2] === '--remote-debug'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (remoteDebugging) {
|
||||||
|
// disable dvtools open in electron
|
||||||
|
process.env.RENDERER_REMOTE_DEBUGGING = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function killElectron(pid) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (pid) {
|
||||||
|
kill(pid, err => {
|
||||||
|
if (err) reject(err)
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restartElectron() {
|
||||||
|
console.log('\nStarting electron...')
|
||||||
|
|
||||||
|
const { pid } = electronProcess || {}
|
||||||
|
await killElectron(pid)
|
||||||
|
|
||||||
|
electronProcess = spawn(electron, [
|
||||||
|
path.join(__dirname, '../dist/main.js'),
|
||||||
|
// '--enable-logging', Enable to show logs from all electron processes
|
||||||
|
remoteDebugging ? '--inspect=9222' : '',
|
||||||
|
remoteDebugging ? '--remote-debugging-port=9223' : '',
|
||||||
|
])
|
||||||
|
|
||||||
|
electronProcess.on('exit', (code, signal) => {
|
||||||
|
if (!manualRestart) process.exit(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMain() {
|
||||||
|
const webpackSetup = webpack([mainConfig, workersConfig])
|
||||||
|
|
||||||
|
webpackSetup.compilers.forEach(compiler => {
|
||||||
|
const { name } = compiler
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'workers':
|
||||||
|
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
||||||
|
console.log(`\nCompiled ${name} script!`)
|
||||||
|
console.log(`\nWatching file changes for ${name} script...`)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'main':
|
||||||
|
default:
|
||||||
|
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
||||||
|
console.log(`\nCompiled ${name} script!`)
|
||||||
|
|
||||||
|
manualRestart = true
|
||||||
|
await restartElectron()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
manualRestart = false
|
||||||
|
}, 2500)
|
||||||
|
|
||||||
|
console.log(`\nWatching file changes for ${name} script...`)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
webpackSetup.watch({
|
||||||
|
aggregateTimeout: 500,
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err) console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRenderer(callback) {
|
||||||
|
const compiler = webpack(rendererConfig)
|
||||||
|
const { name } = compiler
|
||||||
|
|
||||||
|
compiler.hooks.afterEmit.tap('afterEmit', () => {
|
||||||
|
console.log(`\nCompiled ${name} script!`)
|
||||||
|
console.log(`\nWatching file changes for ${name} script...`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const server = new WebpackDevServer(compiler, {
|
||||||
|
contentBase: path.join(__dirname, '../'),
|
||||||
|
hot: true,
|
||||||
|
overlay: true,
|
||||||
|
clientLogLevel: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(9080, '', err => {
|
||||||
|
if (err) console.error(err)
|
||||||
|
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startRenderer(startMain)
|
|
@ -0,0 +1,75 @@
|
||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
|
const {
|
||||||
|
dependencies,
|
||||||
|
devDependencies,
|
||||||
|
productName,
|
||||||
|
} = require('../package.json')
|
||||||
|
|
||||||
|
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies))
|
||||||
|
const isDevMode = process.env.NODE_ENV === 'development'
|
||||||
|
const whiteListedModules = []
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'main',
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
devtool: isDevMode ? 'eval' : false,
|
||||||
|
entry: {
|
||||||
|
main: path.join(__dirname, '../src/main/index.js'),
|
||||||
|
},
|
||||||
|
externals: externals.filter(d => !whiteListedModules.includes(d)),
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(j|t)s$/,
|
||||||
|
loader: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
use: 'node-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
__dirname: isDevMode,
|
||||||
|
__filename: isDevMode,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.PRODUCT_NAME': JSON.stringify(productName),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
path: path.join(__dirname, '../dist'),
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js', '.json'],
|
||||||
|
alias: {
|
||||||
|
'@': path.join(__dirname, '../src/'),
|
||||||
|
src: path.join(__dirname, '../src/'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
target: 'electron-main',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDevMode) {
|
||||||
|
config.plugins.push(
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../src/data'),
|
||||||
|
to: path.join(__dirname, '../dist/data'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../static'),
|
||||||
|
to: path.join(__dirname, '../dist/static'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
|
@ -0,0 +1,165 @@
|
||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
|
|
||||||
|
const {
|
||||||
|
dependencies,
|
||||||
|
devDependencies,
|
||||||
|
productName,
|
||||||
|
} = require('../package.json')
|
||||||
|
|
||||||
|
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies))
|
||||||
|
const isDevMode = process.env.NODE_ENV === 'development'
|
||||||
|
const whiteListedModules = ['vue']
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'renderer',
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
devtool: isDevMode ? 'eval' : false,
|
||||||
|
entry: {
|
||||||
|
renderer: path.join(__dirname, '../src/renderer/main.js'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
path: path.join(__dirname, '../dist'),
|
||||||
|
filename: '[name].js',
|
||||||
|
},
|
||||||
|
externals: externals.filter(d => !whiteListedModules.includes(d)),
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(j|t)s$/,
|
||||||
|
use: 'babel-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
use: 'node-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.vue$/,
|
||||||
|
loader: 'vue-loader',
|
||||||
|
// use: {
|
||||||
|
// loader: 'vue-loader',
|
||||||
|
// options: {
|
||||||
|
// loaders: {
|
||||||
|
// sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s(c|a)ss$/,
|
||||||
|
use: [
|
||||||
|
// {
|
||||||
|
// loader: 'vue-style-loader',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: {
|
||||||
|
hmr: isDevMode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
options: {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
implementation: require('sass'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: {
|
||||||
|
hmr: isDevMode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|tif?f|bmp|webp|svg)(\?.*)?$/,
|
||||||
|
use: {
|
||||||
|
loader: 'url-loader',
|
||||||
|
query: {
|
||||||
|
limit: 10000,
|
||||||
|
name: 'imgs/[name]--[folder].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||||
|
use: {
|
||||||
|
loader: 'url-loader',
|
||||||
|
query: {
|
||||||
|
limit: 10000,
|
||||||
|
name: 'fonts/[name]--[folder].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
__dirname: isDevMode,
|
||||||
|
__filename: isDevMode,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// new WriteFilePlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
excludeChunks: ['processTaskWorker'],
|
||||||
|
filename: 'index.html',
|
||||||
|
template: path.resolve(__dirname, '../src/index.ejs'),
|
||||||
|
nodeModules: isDevMode
|
||||||
|
? path.resolve(__dirname, '../node_modules')
|
||||||
|
: false,
|
||||||
|
}),
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.PRODUCT_NAME': JSON.stringify(productName),
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].css',
|
||||||
|
chunkFilename: '[id].css',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
vue$: 'vue/dist/vue.common.js',
|
||||||
|
'@': path.join(__dirname, '../src/'),
|
||||||
|
src: path.join(__dirname, '../src/'),
|
||||||
|
icons: path.join(__dirname, '../_icons/'),
|
||||||
|
},
|
||||||
|
extensions: ['.ts', '.js', '.vue', '.json'],
|
||||||
|
},
|
||||||
|
target: 'electron-renderer',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust rendererConfig for production settings
|
||||||
|
*/
|
||||||
|
if (isDevMode) {
|
||||||
|
// any dev only config
|
||||||
|
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
|
} else {
|
||||||
|
config.plugins.push(
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../static'),
|
||||||
|
to: path.join(__dirname, '../dist/static'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
|
@ -0,0 +1,67 @@
|
||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
|
||||||
|
const {
|
||||||
|
dependencies,
|
||||||
|
devDependencies,
|
||||||
|
productName,
|
||||||
|
} = require('../package.json')
|
||||||
|
|
||||||
|
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies))
|
||||||
|
const isDevMode = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'workers',
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
devtool: isDevMode ? 'eval' : false,
|
||||||
|
entry: {
|
||||||
|
workerSample: path.join(__dirname, '../src/utilities/workerSample.ts'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
path: path.join(__dirname, '../dist'),
|
||||||
|
filename: '[name].js',
|
||||||
|
},
|
||||||
|
externals: externals,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(j|t)s$/,
|
||||||
|
use: 'babel-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
use: 'node-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
__dirname: isDevMode,
|
||||||
|
__filename: isDevMode,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// new WriteFilePlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.PRODUCT_NAME': JSON.stringify(productName),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.join(__dirname, '../src/'),
|
||||||
|
src: path.join(__dirname, '../src/'),
|
||||||
|
},
|
||||||
|
extensions: ['.ts', '.js', '.json'],
|
||||||
|
},
|
||||||
|
target: 'node',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust rendererConfig for production settings
|
||||||
|
*/
|
||||||
|
if (isDevMode) {
|
||||||
|
// any dev only config
|
||||||
|
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
|
@ -0,0 +1,20 @@
|
||||||
|
image: Visual Studio 2017
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- node_modules
|
||||||
|
- '%USERPROFILE%\.electron'
|
||||||
|
|
||||||
|
init:
|
||||||
|
- git config --global core.autocrlf input
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: Install-Product node 12 x64
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
test: off
|
|
@ -0,0 +1,124 @@
|
||||||
|
{
|
||||||
|
"author": {
|
||||||
|
"name": "PrestonN",
|
||||||
|
"email": "FreeTubeApp@protonmail.com",
|
||||||
|
"url": "https://github.com/FreeTubeApp/FreeTube"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/FreeTubeApp/FreeTube/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.27",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.12.1",
|
||||||
|
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||||
|
"autolinker": "^3.11.1",
|
||||||
|
"bulma-pro": "^0.1.8",
|
||||||
|
"dateformat": "^3.0.3",
|
||||||
|
"electron-context-menu": "^0.16.0",
|
||||||
|
"jquery": "^3.4.1",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"material-design-icons": "^3.0.1",
|
||||||
|
"mediaelement": "^4.2.14",
|
||||||
|
"nedb": "^1.8.0",
|
||||||
|
"opml-to-json": "0.0.3",
|
||||||
|
"video.js": "^7.6.6",
|
||||||
|
"videojs-replay": "^1.1.0",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-electron": "^1.0.6",
|
||||||
|
"vue-router": "^3.1.5",
|
||||||
|
"vuex": "^3.1.2",
|
||||||
|
"xml2json": "^0.12.0",
|
||||||
|
"youtube-chat": "^1.0.2",
|
||||||
|
"youtube-comments-fetch": "^1.0.1",
|
||||||
|
"youtube-comments-task": "^1.3.14",
|
||||||
|
"youtube-suggest": "^1.1.0",
|
||||||
|
"ytdl-core": "^1.0.7",
|
||||||
|
"ytpl": "^0.1.20",
|
||||||
|
"ytsr": "^0.1.10"
|
||||||
|
},
|
||||||
|
"description": "A private YouTube client",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.8.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
|
||||||
|
"@babel/preset-env": "^7.8.4",
|
||||||
|
"@babel/preset-typescript": "^7.8.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.19.0",
|
||||||
|
"@typescript-eslint/parser": "^2.19.0",
|
||||||
|
"acorn": "^7.1.0",
|
||||||
|
"babel-eslint": "^10.0.3",
|
||||||
|
"babel-loader": "^8.0.6",
|
||||||
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
|
"devtron": "^1.4.0",
|
||||||
|
"electron": "^8.0.0",
|
||||||
|
"electron-builder": "^22.3.2",
|
||||||
|
"electron-debug": "^3.0.1",
|
||||||
|
"electron-rebuild": "^1.10.0",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-prettier": "^6.10.0",
|
||||||
|
"eslint-config-standard": "^14.1.0",
|
||||||
|
"eslint-plugin-import": "^2.20.1",
|
||||||
|
"eslint-plugin-node": "^11.0.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
|
"eslint-plugin-vue": "^6.1.2",
|
||||||
|
"fast-glob": "^3.1.1",
|
||||||
|
"file-loader": "^5.0.2",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
|
"node-loader": "^0.6.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass": "^1.25.0",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"style-loader": "^1.1.3",
|
||||||
|
"tree-kill": "1.2.2",
|
||||||
|
"typescript": "^3.7.5",
|
||||||
|
"url-loader": "^3.0.0",
|
||||||
|
"vue-devtools": "^5.1.3",
|
||||||
|
"vue-eslint-parser": "^7.0.0",
|
||||||
|
"vue-loader": "^15.8.3",
|
||||||
|
"vue-style-loader": "^4.1.2",
|
||||||
|
"vue-template-compiler": "^2.6.11",
|
||||||
|
"webpack": "^4.41.5",
|
||||||
|
"webpack-cli": "^3.3.10",
|
||||||
|
"webpack-dev-server": "^3.10.3"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"main": "./dist/main.js",
|
||||||
|
"name": "freetube",
|
||||||
|
"private": true,
|
||||||
|
"productName": "FreeTube",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/mubaidr/vue-electron-template.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "run-s rebuild:electron pack build-release",
|
||||||
|
"build-release": "node _scripts/build.js",
|
||||||
|
"debug": "run-s rebuild:electron debug-runner",
|
||||||
|
"debug-runner": "node _scripts/dev-runner.js --remote-debug",
|
||||||
|
"dev": "run-s rebuild:electron dev-runner",
|
||||||
|
"dev-runner": "node _scripts/dev-runner.js",
|
||||||
|
"electron-builder-install": "electron-builder install-app-deps",
|
||||||
|
"electron-rebuild": "electron-rebuild",
|
||||||
|
"jest": "jest",
|
||||||
|
"jest:coverage": "jest --collect-coverage",
|
||||||
|
"jest:watch": "jest --watch",
|
||||||
|
"lint": "eslint --fix --ext .js,.ts,.vue ./",
|
||||||
|
"pack": "run-p pack:main pack:renderer pack:workers",
|
||||||
|
"pack:main": "webpack --mode=production --env.NODE_ENV=production --hide-modules --config _scripts/webpack.main.config.js",
|
||||||
|
"pack:renderer": "webpack --mode=production --env.NODE_ENV=production --hide-modules --config _scripts/webpack.renderer.config.js",
|
||||||
|
"pack:workers": "webpack --mode=production --env.NODE_ENV=production --hide-modules --config _scripts/webpack.workers.config.js",
|
||||||
|
"postinstall": "electron-rebuild",
|
||||||
|
"prettier": "prettier --write \"{src,_scripts}/**/*.{js,ts,vue}\"",
|
||||||
|
"rebuild:electron": "run-s electron-builder-install electron-rebuild",
|
||||||
|
"rebuild:node": "npm rebuild",
|
||||||
|
"release": "run-s test build",
|
||||||
|
"test": "run-s rebuild:node pack:workers jest",
|
||||||
|
"test:watch": "run-s rebuild:node pack:workers jest:watch"
|
||||||
|
},
|
||||||
|
"version": "0.8.0"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title></title>
|
||||||
|
<% if (htmlWebpackPlugin.options.nodeModules) { %>
|
||||||
|
<script>
|
||||||
|
require('module').globalPaths.push(
|
||||||
|
`<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>`
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<% } %>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="redLight">
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- Set `__static` path to static files in production -->
|
||||||
|
<script>
|
||||||
|
if (process.env.NODE_ENV !== 'development')
|
||||||
|
window.__static = require('path')
|
||||||
|
.join(__dirname, '/static')
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
</script>
|
||||||
|
<!-- webpack builds are automatically injected -->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,247 @@
|
||||||
|
import { app, BrowserWindow, Menu } from 'electron'
|
||||||
|
import { productName } from '../../package.json'
|
||||||
|
|
||||||
|
// set app name
|
||||||
|
app.setName(productName)
|
||||||
|
|
||||||
|
// disable electron warning
|
||||||
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
|
||||||
|
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
const isDebug = process.argv.includes('--debug')
|
||||||
|
let mainWindow
|
||||||
|
|
||||||
|
// only allow single instance of application
|
||||||
|
if (!isDev) {
|
||||||
|
if (gotTheLock) {
|
||||||
|
app.on('second-instance', () => {
|
||||||
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
|
if (mainWindow && mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore()
|
||||||
|
}
|
||||||
|
mainWindow.focus()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
app.quit()
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
require('electron-debug')({
|
||||||
|
showDevTools: !(process.env.RENDERER_REMOTE_DEBUGGING === 'true')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installDevTools () {
|
||||||
|
try {
|
||||||
|
/* eslint-disable */
|
||||||
|
require('devtron').install()
|
||||||
|
require('vue-devtools').install()
|
||||||
|
/* eslint-enable */
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow () {
|
||||||
|
/**
|
||||||
|
* Initial window options
|
||||||
|
*/
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
minWidth: 960,
|
||||||
|
minHeight: 540,
|
||||||
|
// useContentSize: true,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
nodeIntegrationInWorker: false,
|
||||||
|
webSecurity: false
|
||||||
|
},
|
||||||
|
show: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
setMenu()
|
||||||
|
|
||||||
|
// load root file/url
|
||||||
|
if (isDev) {
|
||||||
|
mainWindow.loadURL('http://localhost:9080')
|
||||||
|
} else {
|
||||||
|
mainWindow.loadFile(`${__dirname}/index.html`)
|
||||||
|
|
||||||
|
global.__static = require('path')
|
||||||
|
.join(__dirname, '/static')
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show when loaded
|
||||||
|
mainWindow.on('ready-to-show', () => {
|
||||||
|
mainWindow.show()
|
||||||
|
mainWindow.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
console.log('closed')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
createWindow()
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
installDevTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDebug) {
|
||||||
|
mainWindow.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (mainWindow === null) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto Updater
|
||||||
|
*
|
||||||
|
* Uncomment the following code below and install `electron-updater` to
|
||||||
|
* support auto updating. Code Signing with a valid certificate is required.
|
||||||
|
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { autoUpdater } from 'electron-updater'
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', () => {
|
||||||
|
autoUpdater.quitAndInstall()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
const sendMenuEvent = async data => {
|
||||||
|
mainWindow.webContents.send('change-view', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = [{
|
||||||
|
label: 'File',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
role: 'quit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [{
|
||||||
|
role: 'cut'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'copy',
|
||||||
|
accelerator: 'CmdOrCtrl+C',
|
||||||
|
selector: 'copy:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'paste',
|
||||||
|
accelerator: 'CmdOrCtrl+V',
|
||||||
|
selector: 'paste:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'pasteandmatchstyle'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'delete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [{
|
||||||
|
role: 'reload'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'forcereload',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+R'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'toggledevtools'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'resetzoom'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomout'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'togglefullscreen'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'window',
|
||||||
|
submenu: [{
|
||||||
|
role: 'minimize'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'close'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
function setMenu () {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
template.unshift({
|
||||||
|
label: app.getName(),
|
||||||
|
submenu: [
|
||||||
|
{ role: 'about' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'services' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideothers' },
|
||||||
|
{ role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'quit' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
template.push({
|
||||||
|
role: 'window'
|
||||||
|
})
|
||||||
|
|
||||||
|
template.push({
|
||||||
|
role: 'help'
|
||||||
|
})
|
||||||
|
|
||||||
|
template.push({ role: 'services' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template)
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: Roboto;
|
||||||
|
src: url(assets/font/Roboto-Regular.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.routerView {
|
||||||
|
margin-left: 200px;
|
||||||
|
margin-top: 80px;
|
||||||
|
transition-property: margin;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand {
|
||||||
|
margin-left: 80px;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import TopNav from './components/top-nav/top-nav.vue'
|
||||||
|
import SideNav from './components/side-nav/side-nav.vue'
|
||||||
|
import $ from 'jquery'
|
||||||
|
import { shell } from 'electron'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
TopNav,
|
||||||
|
SideNav
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isOpen: function () {
|
||||||
|
return this.$store.getters.getIsSideNavOpen
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
// Open links externally by default
|
||||||
|
$(document).on('click', 'a[href^="http"]', (event) => {
|
||||||
|
const el = event.currentTarget
|
||||||
|
console.log(el)
|
||||||
|
if (typeof (shell) !== 'undefined') {
|
||||||
|
event.preventDefault()
|
||||||
|
shell.openExternal(el.href)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<top-nav ref="topNav" />
|
||||||
|
<side-nav ref="sideNav" />
|
||||||
|
<Transition
|
||||||
|
mode="out-in"
|
||||||
|
name="slide-up"
|
||||||
|
>
|
||||||
|
<!-- <keep-alive> -->
|
||||||
|
<RouterView
|
||||||
|
ref="router"
|
||||||
|
class="routerView"
|
||||||
|
:class="{ expand: !isOpen }"
|
||||||
|
/>
|
||||||
|
<!-- </keep-alive> -->
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./App.js" />
|
||||||
|
|
||||||
|
<style src="./themes.css" />
|
||||||
|
<style src="./videoJS.css" />
|
||||||
|
<style scoped src="./App.css" />
|
|
@ -0,0 +1,50 @@
|
||||||
|
.btn {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 10px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 5px;
|
||||||
|
box-shadow: 0 0 2px -2px rgba(29, 39, 231, .1), 0 0 3px 0 rgba(29, 39, 231, .1), 0 0 5px 0 rgba(29, 39, 231, .1), 0 2px 2px -4px rgba(29, 39, 231, .1), 0 4px 8px 0 rgba(29, 39, 231, .1), 0 2px 15px 0 rgba(29, 39, 231, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripple {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripple:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image: radial-gradient(circle, #fff 10%, transparent 10.01%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50%;
|
||||||
|
transform: scale(10, 10);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform .5s, opacity 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripple:active:after {
|
||||||
|
transform: scale(0, 0);
|
||||||
|
opacity: .3;
|
||||||
|
transition: 0s;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import FtFlexBox from '../ft-flex-box/ft-flex-box.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: 'FtElementList',
|
||||||
|
components: {
|
||||||
|
'ft-flex-box': FtFlexBox,
|
||||||
|
'ft-list-video': FtListVideo,
|
||||||
|
'ft-list-channel': FtListChannel,
|
||||||
|
'ft-list-playlist': FtListPlaylist
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
textColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#2196F3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
class="btn ripple"
|
||||||
|
:style="{
|
||||||
|
color: textColor,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
border: `2px solid ${backgroundColor}`
|
||||||
|
}"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-button.js" />
|
||||||
|
<style scoped src="./ft-button.css" />
|
|
@ -0,0 +1,8 @@
|
||||||
|
.ft-card {
|
||||||
|
background-color: var(--card-bg-color);
|
||||||
|
margin: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,.1);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtCard'
|
||||||
|
})
|
|
@ -0,0 +1,8 @@
|
||||||
|
<template>
|
||||||
|
<div class="ft-card">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-card.js" />
|
||||||
|
<style scoped src="./ft-card.css" />
|
|
@ -0,0 +1,33 @@
|
||||||
|
.bubblePadding {
|
||||||
|
width: 100px;
|
||||||
|
height: 115px;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-transition: background 0.2s ease-out;
|
||||||
|
-moz-transition: background 0.2s ease-out;
|
||||||
|
-o-transition: background 0.2s ease-out;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubblePadding:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: 25px;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channelName {
|
||||||
|
font-size: 13px;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtChannelBubble',
|
||||||
|
props: {
|
||||||
|
channelName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
channelId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
channelThumbnail: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToChannel: function () {
|
||||||
|
console.log('Go to channel')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bubblePadding"
|
||||||
|
@click="goToChannel(channelId)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="bubble"
|
||||||
|
:src="channelThumbnail"
|
||||||
|
>
|
||||||
|
<div class="channelName">
|
||||||
|
{{ channelName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-channel-bubble.js" />
|
||||||
|
<style scoped src="./ft-channel-bubble.css" />
|
|
@ -0,0 +1,3 @@
|
||||||
|
.maxWidth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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: 'FtElementList',
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,50 @@
|
||||||
|
<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="./ft-element-list.js" />
|
||||||
|
<style scoped src="./ft-element-list.css" />
|
|
@ -0,0 +1,5 @@
|
||||||
|
.ft-flex-box {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtFlexBox'
|
||||||
|
})
|
|
@ -0,0 +1,8 @@
|
||||||
|
<template>
|
||||||
|
<div class="ft-flex-box">
|
||||||
|
<slot class="flex-container" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-flex-box.js" />
|
||||||
|
<style scoped src="./ft-flex-box.css" />
|
|
@ -0,0 +1,6 @@
|
||||||
|
.ft-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 240px);
|
||||||
|
justify-content: space-evenly;
|
||||||
|
grid-gap: 5px;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtGrid'
|
||||||
|
})
|
|
@ -0,0 +1,8 @@
|
||||||
|
<template>
|
||||||
|
<div class="ft-grid">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-grid.js" />
|
||||||
|
<style scoped src="./ft-grid.css" />
|
|
@ -0,0 +1,46 @@
|
||||||
|
.ft-input-component {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ft-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 7px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font: 16px;
|
||||||
|
height: 45px;
|
||||||
|
border-bottom: 1px solid var(--primary-input-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ft-input-component ::-webkit-input-placeholder {
|
||||||
|
color: var(--primary-input-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputAction {
|
||||||
|
position: absolute;
|
||||||
|
padding: 10px;
|
||||||
|
top: 10px;
|
||||||
|
right: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputAction:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputAction:active {
|
||||||
|
background-color: var(--teritary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtInput',
|
||||||
|
props: {
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
showArrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
inputData: '',
|
||||||
|
component: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.id = this._uid
|
||||||
|
|
||||||
|
setTimeout(this.addListener, 200)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick: function () {
|
||||||
|
this.$emit('click', this.inputData)
|
||||||
|
},
|
||||||
|
|
||||||
|
addListener: function () {
|
||||||
|
const inputElement = document.getElementById(this.id)
|
||||||
|
|
||||||
|
if (inputElement !== null) {
|
||||||
|
inputElement.addEventListener('keydown', (event) => {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
this.handleClick()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="ft-input-component">
|
||||||
|
<input
|
||||||
|
:id="id"
|
||||||
|
v-model="inputData"
|
||||||
|
class="ft-input"
|
||||||
|
type="text"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="showArrow"
|
||||||
|
icon="arrow-right"
|
||||||
|
class="inputAction"
|
||||||
|
@click="handleClick(inputData, component)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-input.js" />
|
||||||
|
<style scoped src="./ft-input.css" />
|
|
@ -0,0 +1,93 @@
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtListChannel',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
thumbnail: '',
|
||||||
|
channelName: '',
|
||||||
|
subscriberCount: 0,
|
||||||
|
videoCount: '',
|
||||||
|
uploadedTime: '',
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
if (typeof (this.data.avatar) !== 'undefined') {
|
||||||
|
this.parseLocalData()
|
||||||
|
} else {
|
||||||
|
this.parseInvidiousData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToChannel: function () {
|
||||||
|
this.$router.push({ path: `/channel/${this.id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLocalData: function () {
|
||||||
|
this.thumbnail = this.data.avatar
|
||||||
|
this.channelName = this.data.name
|
||||||
|
this.id = this.data.channel_id
|
||||||
|
this.subscriberCount = this.data.followers.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
this.videoCount = this.data.videos.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
this.description = this.data.description_short
|
||||||
|
},
|
||||||
|
|
||||||
|
parseInvidiousData: function () {
|
||||||
|
this.thumbnail = this.data.authorThumbnails[2].url
|
||||||
|
this.channelName = this.data.author
|
||||||
|
this.id = this.data.authorId
|
||||||
|
this.subscriberCount = this.data.subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
this.videoCount = this.data.videoCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
this.description = this.data.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ft-list-channel"
|
||||||
|
:class="{ list: listType === 'list', grid: listType === 'grid' }"
|
||||||
|
>
|
||||||
|
<div class="channelThumbnail">
|
||||||
|
<img
|
||||||
|
:src="thumbnail"
|
||||||
|
@click="goToChannel(id)"
|
||||||
|
>
|
||||||
|
</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" />
|
|
@ -0,0 +1,68 @@
|
||||||
|
.dropDown {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropDown ul {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropDown ul li {
|
||||||
|
width: 100%;
|
||||||
|
float: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropDown:hover ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropDown ul li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonTitle {
|
||||||
|
height: 30px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--teritary-text-color);
|
||||||
|
background-color: var(--secondary-card-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonOption {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--teritary-text-color);
|
||||||
|
background-color: var(--secondary-card-bg-color);
|
||||||
|
-webkit-transition: background 0.2s ease-out;
|
||||||
|
-moz-transition: background 0.2s ease-out;
|
||||||
|
-o-transition: background 0.2s ease-out;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonOption:hover {
|
||||||
|
background-color: var(--card-bg-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtListDropdown',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
labelNames: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
labelValues: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
thumbnail: '',
|
||||||
|
channelName: '',
|
||||||
|
subscriberCount: 0,
|
||||||
|
videoCount: '',
|
||||||
|
uploadedTime: '',
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToChannel: function () {
|
||||||
|
console.log('TODO: ft-list-channel method goToChannel')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div class="dropDown">
|
||||||
|
<div class="buttonTitle">
|
||||||
|
{{ title }}
|
||||||
|
<font-awesome-icon
|
||||||
|
class="angleDownIcon"
|
||||||
|
icon="angle-down"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="(label, index) in labelNames"
|
||||||
|
:key="index"
|
||||||
|
class="buttonOption"
|
||||||
|
@click="$emit('click', labelValues[index])"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-list-dropdown.js" />
|
||||||
|
<style scoped src="./ft-list-dropdown.css" />
|
|
@ -0,0 +1,106 @@
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtListVideo',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
playlistLink: '',
|
||||||
|
channelLink: '',
|
||||||
|
title: 'Pop Music Playlist - Timeless Pop Songs (Updated Weekly 2020)',
|
||||||
|
thumbnail: 'https://i.ytimg.com/vi/JGwWNGJdvx8/mqdefault.jpg',
|
||||||
|
channelName: '#RedMusic: Just Hits',
|
||||||
|
videoCount: 200,
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
},
|
||||||
|
|
||||||
|
playlistId: function () {
|
||||||
|
return this.playlistLink.replace('https://www.youtube.com/playlist?list=', '')
|
||||||
|
},
|
||||||
|
|
||||||
|
channelId: function () {
|
||||||
|
let id = this.channelLink.replace('https://www.youtube.com/user/', '')
|
||||||
|
id = id.replace('https://www.youtube.com/channel/', '')
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
if (typeof (this.data.author) === 'object') {
|
||||||
|
this.parseLocalData()
|
||||||
|
} else {
|
||||||
|
this.parseInvidiousData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
parseInvidiousData: function () {
|
||||||
|
this.title = this.data.title
|
||||||
|
this.thumbnail = this.data.playlistThumbnail
|
||||||
|
this.channelName = this.data.author
|
||||||
|
this.channelLink = this.data.authorUrl
|
||||||
|
this.playlistLink = this.data.playlistId
|
||||||
|
this.videoCount = this.data.videoCount
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLocalData: function () {
|
||||||
|
this.title = this.data.title
|
||||||
|
this.thumbnail = this.data.thumbnail
|
||||||
|
this.channelName = this.data.author.name
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ft-list-video"
|
||||||
|
:class="{ list: listType === 'list', grid: listType === 'grid' }"
|
||||||
|
>
|
||||||
|
<div class="videoThumbnail">
|
||||||
|
<img
|
||||||
|
:src="thumbnail"
|
||||||
|
@click="goToPlaylist(playlistId)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="videoCountContainer"
|
||||||
|
@click="goToPlaylist(playlistId)"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ videoCount }}
|
||||||
|
<br>
|
||||||
|
<font-awesome-icon icon="list" />
|
||||||
|
</span>
|
||||||
|
</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" />
|
|
@ -0,0 +1,172 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 .videoTitle {
|
||||||
|
max-height: 55px;
|
||||||
|
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 .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;
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtListVideo',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
forceListType: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
thumbnail: '',
|
||||||
|
channelName: '',
|
||||||
|
channelId: '',
|
||||||
|
viewCount: 0,
|
||||||
|
uploadedTime: '',
|
||||||
|
duration: '',
|
||||||
|
description: '',
|
||||||
|
watched: false,
|
||||||
|
progressPercentage: 0,
|
||||||
|
isLive: false,
|
||||||
|
isFavorited: false,
|
||||||
|
hideViews: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
},
|
||||||
|
|
||||||
|
clickBaitRemoverPreference: function () {
|
||||||
|
return this.$store.getters.getClickBaitRemoverPreference
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
// Check if data came from Invidious or from local backend
|
||||||
|
|
||||||
|
if (typeof (this.data.descriptionHtml) !== 'undefined' ||
|
||||||
|
typeof (this.data.index) !== 'undefined' ||
|
||||||
|
typeof (this.data.publishedText) !== 'undefined' ||
|
||||||
|
typeof (this.data.authorThumbnails) === 'object'
|
||||||
|
) {
|
||||||
|
this.parseInvidiousData()
|
||||||
|
} else {
|
||||||
|
console.log('parsing local data')
|
||||||
|
this.parseLocalData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
play: function () {
|
||||||
|
this.$router.push({ path: `/watch/${this.id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
goToChannel: function () {
|
||||||
|
console.log(this.data)
|
||||||
|
this.$router.push({ path: `/channel/${this.channelId}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleSave: function () {
|
||||||
|
console.log('TODO: ft-list-video method toggleSave')
|
||||||
|
},
|
||||||
|
|
||||||
|
// For Invidious data, as duration is sent in seconds
|
||||||
|
calculateVideoDuration: function (lengthSeconds) {
|
||||||
|
let durationText = ''
|
||||||
|
let time = lengthSeconds
|
||||||
|
let hours = 0
|
||||||
|
|
||||||
|
if (time >= 3600) {
|
||||||
|
hours = Math.floor(time / 3600)
|
||||||
|
time = time - hours * 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
let minutes = Math.floor(time / 60)
|
||||||
|
let seconds = time - minutes * 60
|
||||||
|
|
||||||
|
if (seconds < 10) {
|
||||||
|
seconds = '0' + seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes < 10 && hours > 0) {
|
||||||
|
minutes = '0' + minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
durationText = hours + ':' + minutes + ':' + seconds
|
||||||
|
} else {
|
||||||
|
durationText = minutes + ':' + seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
return durationText
|
||||||
|
},
|
||||||
|
|
||||||
|
parseInvidiousData: function () {
|
||||||
|
this.id = this.data.videoId
|
||||||
|
this.title = this.data.title
|
||||||
|
// this.thumbnail = this.data.videoThumbnails[4].url
|
||||||
|
switch (this.clickBaitRemoverPreference) {
|
||||||
|
case 'start':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq1.jpg`
|
||||||
|
break
|
||||||
|
case 'middle':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq2.jpg`
|
||||||
|
break
|
||||||
|
case 'end':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq3.jpg`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mqdefault.jpg`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
this.channelName = this.data.author
|
||||||
|
this.channelId = this.data.authorId
|
||||||
|
this.duration = this.calculateVideoDuration(this.data.lengthSeconds)
|
||||||
|
this.description = this.data.description
|
||||||
|
this.isLive = this.data.liveNow
|
||||||
|
|
||||||
|
if (typeof (this.data.publishedText) !== 'undefined') {
|
||||||
|
this.uploadedTime = this.data.publishedText
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (this.data.viewCount) !== 'undefined' && this.data.viewCount !== null) {
|
||||||
|
this.viewCount = this.data.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
} else if (typeof (this.data.viewCountText) !== 'undefined') {
|
||||||
|
this.viewCount = this.data.viewCountText.replace(' views', '')
|
||||||
|
} else {
|
||||||
|
this.hideViews = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLocalData: function () {
|
||||||
|
if (typeof (this.data.id) !== 'undefined') {
|
||||||
|
this.id = this.data.id
|
||||||
|
} else {
|
||||||
|
this.id = this.data.link.replace('https://www.youtube.com/watch?v=', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.title = this.data.title
|
||||||
|
// this.thumbnail = this.data.thumbnail
|
||||||
|
|
||||||
|
switch (this.clickBaitRemoverPreference) {
|
||||||
|
case 'start':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq1.jpg`
|
||||||
|
break
|
||||||
|
case 'middle':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq2.jpg`
|
||||||
|
break
|
||||||
|
case 'end':
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mq3.jpg`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.thumbnail = `https://i.ytimg.com/vi/${this.id}/mqdefault.jpg`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (this.data.author) === 'string') {
|
||||||
|
this.channelName = this.data.author
|
||||||
|
this.channelId = this.data.ucid
|
||||||
|
|
||||||
|
// Data is returned as a literal string names 'undefined'
|
||||||
|
if (this.data.length_seconds !== 'undefined') {
|
||||||
|
this.duration = this.calculateVideoDuration(parseInt(this.data.length_seconds))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.channelName = this.data.author.name
|
||||||
|
this.duration = this.data.duration
|
||||||
|
this.description = this.data.description
|
||||||
|
this.channelId = this.data.author.ref.replace('https://www.youtube.com/user/', '')
|
||||||
|
this.channelId = this.channelId.replace('https://www.youtube.com/channel/', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (this.data.uploaded_at) !== 'undefined') {
|
||||||
|
this.uploadedTime = this.data.uploaded_at
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.data.views !== null && typeof (this.data.views) !== 'undefined') {
|
||||||
|
this.viewCount = this.data.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
} else if (typeof (this.data.view_count) !== 'undefined') {
|
||||||
|
const viewCount = this.data.view_count.replace(',', '')
|
||||||
|
this.viewCount = viewCount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
} else {
|
||||||
|
this.hideViews = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (this.data.uploaded_at) !== 'undefined' && this.data.uploaded_at.includes('watching')) {
|
||||||
|
const uploadSplit = this.data.uploaded_at.split(' ')
|
||||||
|
this.viewCount = parseInt(uploadSplit[0])
|
||||||
|
this.isLive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ft-list-video"
|
||||||
|
:class="{
|
||||||
|
list: (listType === 'list' || forceListType === 'list') && forceListType !== 'grid',
|
||||||
|
grid: (listType === 'grid' || forceListType === 'list') && forceListType !== 'list'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="videoThumbnail">
|
||||||
|
<img
|
||||||
|
:src="thumbnail"
|
||||||
|
@click="play(id)"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
v-if="!isLive"
|
||||||
|
class="videoDuration"
|
||||||
|
@click="play(id)"
|
||||||
|
>
|
||||||
|
{{ duration }}
|
||||||
|
</p>
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="!isLive"
|
||||||
|
icon="star"
|
||||||
|
class="favoritesIcon"
|
||||||
|
:class="{ favorited: isFavorited }"
|
||||||
|
@click="toggleSave(id)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="watched"
|
||||||
|
class="videoWatched"
|
||||||
|
>
|
||||||
|
WATCHED
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="watched"
|
||||||
|
class="watchedProgressBar"
|
||||||
|
:style="{width: progressPercentage + '%'}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="videoTitle">
|
||||||
|
{{ title }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="channelName"
|
||||||
|
@click="goToChannel"
|
||||||
|
>
|
||||||
|
{{ channelName }}
|
||||||
|
</p>
|
||||||
|
<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>
|
||||||
|
<p
|
||||||
|
v-if="listType !== 'grid'"
|
||||||
|
class="description"
|
||||||
|
>
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
<span
|
||||||
|
v-if="isLive"
|
||||||
|
class="liveText"
|
||||||
|
>
|
||||||
|
LIVE NOW
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-list-video.js" />
|
||||||
|
<style scoped src="./ft-list-video.css" />
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
This file is part of FreeTube.
|
||||||
|
|
||||||
|
FreeTube is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
FreeTube is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen {
|
||||||
|
height: 85vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thanks to @tobiasahlin for the loading animation.
|
||||||
|
* Find it here: http://tobiasahlin.com/spinkit/
|
||||||
|
* Twitter: https://twitter.com/tobiasahlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
position: relative;
|
||||||
|
margin: 100px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.double-bounce1,
|
||||||
|
.double-bounce2 {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
|
||||||
|
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
|
||||||
|
animation: sk-bounce 2.0s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.double-bounce2 {
|
||||||
|
-webkit-animation-delay: -1.0s;
|
||||||
|
animation-delay: -1.0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sk-bounce {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(0.0)
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: scale(1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-bounce {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0.0);
|
||||||
|
-webkit-transform: scale(0.0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
-webkit-transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtLoader',
|
||||||
|
props: {
|
||||||
|
fullscreen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
:class="{ fullscreen: fullscreen }"
|
||||||
|
>
|
||||||
|
<div class="spinner">
|
||||||
|
<div class="double-bounce1" />
|
||||||
|
<div class="double-bounce2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-loader.js" />
|
||||||
|
<style scoped src="./ft-loader.css" />
|
|
@ -0,0 +1,106 @@
|
||||||
|
pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], .pure-checkbox input[type="radio"], .pure-radiobutton input[type="radio"] {
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:focus + label:before, .pure-radiobutton input[type="checkbox"]:focus + label:before, .pure-checkbox input[type="radio"]:focus + label:before, .pure-radiobutton input[type="radio"]:focus + label:before, .pure-checkbox input[type="checkbox"]:hover + label:before, .pure-radiobutton input[type="checkbox"]:hover + label:before, .pure-checkbox input[type="radio"]:hover + label:before, .pure-radiobutton input[type="radio"]:hover + label:before {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:active + label:before, .pure-radiobutton input[type="checkbox"]:active + label:before, .pure-checkbox input[type="radio"]:active + label:before, .pure-radiobutton input[type="radio"]:active + label:before { transition-duration: 0s; }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"] + label, .pure-radiobutton input[type="checkbox"] + label, .pure-checkbox input[type="radio"] + label, .pure-radiobutton input[type="radio"] + label {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2em;
|
||||||
|
vertical-align: middle;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"] + label:before, .pure-radiobutton input[type="checkbox"] + label:before, .pure-checkbox input[type="radio"] + label:before, .pure-radiobutton input[type="radio"] + label:before {
|
||||||
|
box-sizing: content-box;
|
||||||
|
content: '';
|
||||||
|
color: var(--primary-color);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-top: -9px;
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"] + label:after, .pure-radiobutton input[type="checkbox"] + label:after, .pure-checkbox input[type="radio"] + label:after, .pure-radiobutton input[type="radio"] + label:after {
|
||||||
|
box-sizing: content-box;
|
||||||
|
content: '';
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 4px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
transform: scale(0);
|
||||||
|
transform-origin: 50%;
|
||||||
|
transition: transform 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:disabled + label:before, .pure-radiobutton input[type="checkbox"]:disabled + label:before, .pure-checkbox input[type="radio"]:disabled + label:before, .pure-radiobutton input[type="radio"]:disabled + label:before { border-color: #cccccc; }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:disabled:focus + label:before, .pure-radiobutton input[type="checkbox"]:disabled:focus + label:before, .pure-checkbox input[type="radio"]:disabled:focus + label:before, .pure-radiobutton input[type="radio"]:disabled:focus + label:before, .pure-checkbox input[type="checkbox"]:disabled:hover + label:before, .pure-radiobutton input[type="checkbox"]:disabled:hover + label:before, .pure-checkbox input[type="radio"]:disabled:hover + label:before, .pure-radiobutton input[type="radio"]:disabled:hover + label:before { background-color: inherit; }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:disabled:checked + label:before, .pure-radiobutton input[type="checkbox"]:disabled:checked + label:before, .pure-checkbox input[type="radio"]:disabled:checked + label:before, .pure-radiobutton input[type="radio"]:disabled:checked + label:before { background-color: #cccccc; }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"] + label:after, .pure-radiobutton input[type="checkbox"] + label:after {
|
||||||
|
background-color: transparent;
|
||||||
|
top: 50%;
|
||||||
|
left: 4px;
|
||||||
|
width: 8px;
|
||||||
|
height: 3px;
|
||||||
|
margin-top: -4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 0 3px 3px;
|
||||||
|
border-image: none;
|
||||||
|
transform: rotate(-45deg) scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:checked + label:after, .pure-radiobutton input[type="checkbox"]:checked + label:after {
|
||||||
|
content: '';
|
||||||
|
transform: rotate(-45deg) scale(1);
|
||||||
|
transition: transform 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="radio"]:checked + label:before, .pure-radiobutton input[type="radio"]:checked + label:before {
|
||||||
|
animation: borderscale 300ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="radio"]:checked + label:after, .pure-radiobutton input[type="radio"]:checked + label:after { transform: scale(1); }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="radio"] + label:before, .pure-radiobutton input[type="radio"] + label:before, .pure-checkbox input[type="radio"] + label:after, .pure-radiobutton input[type="radio"] + label:after { border-radius: 50%; }
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:checked + label:before, .pure-radiobutton input[type="checkbox"]:checked + label:before {
|
||||||
|
animation: borderscale 200ms ease-in;
|
||||||
|
background: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-checkbox input[type="checkbox"]:checked + label:after, .pure-radiobutton input[type="checkbox"]:checked + label:after { transform: rotate(-45deg) scale(1); }
|
||||||
|
|
||||||
|
@keyframes
|
||||||
|
borderscale { 50% {
|
||||||
|
box-shadow: 0 0 0 2px var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioTitle {
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtElementList',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
selectedValue: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
inputName: function () {
|
||||||
|
const name = this.title.replace(' ', '')
|
||||||
|
return name.toLowerCase() + this.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.id = this._uid
|
||||||
|
this.selectedValue = this.values[0]
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="pure-radiobutton filter">
|
||||||
|
<h3 class="radioTitle">
|
||||||
|
{{ title }}
|
||||||
|
</h3>
|
||||||
|
<!-- eslint-disable vue/no-template-key -->
|
||||||
|
<template
|
||||||
|
v-for="(label, index) in labels"
|
||||||
|
class="radioButtonContainer"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:id="values[index] + id"
|
||||||
|
:key="index"
|
||||||
|
v-model="selectedValue"
|
||||||
|
:name="inputName"
|
||||||
|
:value="values[index]"
|
||||||
|
:checked="index === 0"
|
||||||
|
class="radio"
|
||||||
|
type="radio"
|
||||||
|
@change="$emit('change', values[index])"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
:key="label"
|
||||||
|
:for="values[index] + id"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-radio-button.js" />
|
||||||
|
<style scoped src="./ft-radio-button.css" />
|
|
@ -0,0 +1,20 @@
|
||||||
|
.searchFilter {
|
||||||
|
background-color: var(--card-bg-color);
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 70px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchRadio {
|
||||||
|
width: 170px;
|
||||||
|
border-right: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioFlexBox {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||||
|
import FtRadioButton from '../ft-radio-button/ft-radio-button.vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtSearchFilters',
|
||||||
|
components: {
|
||||||
|
'ft-flex-box': FtFlexBox,
|
||||||
|
'ft-radio-button': FtRadioButton
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
sortByTitle: 'Sort By',
|
||||||
|
sortByLabels: [
|
||||||
|
'Most Relevant',
|
||||||
|
'Rating',
|
||||||
|
'Upload Date',
|
||||||
|
'View Count'
|
||||||
|
],
|
||||||
|
sortByValues: [
|
||||||
|
'relevance',
|
||||||
|
'rating',
|
||||||
|
'upload_date',
|
||||||
|
'view_count'
|
||||||
|
],
|
||||||
|
timeTitle: 'Time',
|
||||||
|
timeLabels: [
|
||||||
|
'Any Time',
|
||||||
|
'Last Hour',
|
||||||
|
'Today',
|
||||||
|
'This Week',
|
||||||
|
'This Month',
|
||||||
|
'This Year'
|
||||||
|
],
|
||||||
|
timeValues: [
|
||||||
|
'',
|
||||||
|
'hour',
|
||||||
|
'today',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'year'
|
||||||
|
],
|
||||||
|
typeTitle: 'Type',
|
||||||
|
typeLabels: [
|
||||||
|
'All Types',
|
||||||
|
'Videos',
|
||||||
|
'Channels',
|
||||||
|
'Playlists'
|
||||||
|
],
|
||||||
|
typeValues: [
|
||||||
|
'all',
|
||||||
|
'video',
|
||||||
|
'channel',
|
||||||
|
'playlist'
|
||||||
|
],
|
||||||
|
durationTitle: 'Duration',
|
||||||
|
durationLabels: [
|
||||||
|
'All Durations',
|
||||||
|
'Short (< 4 minutes)',
|
||||||
|
'Long (> 20 minutes)'
|
||||||
|
],
|
||||||
|
durationValues: [
|
||||||
|
'',
|
||||||
|
'short',
|
||||||
|
'long'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchSettings: function () {
|
||||||
|
return this.$store.getters.getSearchSettings
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateSortBy: function (value) {
|
||||||
|
this.$store.commit('setSearchSortBy', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTime: function (value) {
|
||||||
|
this.$store.commit('setSearchTime', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateType: function (value) {
|
||||||
|
this.$store.commit('setSearchType', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDuration: function (value) {
|
||||||
|
this.$store.commit('setSearchDuration', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div class="searchFilter">
|
||||||
|
<h2 class="center">
|
||||||
|
Search Filters
|
||||||
|
</h2>
|
||||||
|
<ft-flex-box class="radioFlexBox">
|
||||||
|
<ft-radio-button
|
||||||
|
:title="sortByTitle"
|
||||||
|
:labels="sortByLabels"
|
||||||
|
:values="sortByValues"
|
||||||
|
class="searchRadio"
|
||||||
|
@change="updateSortBy"
|
||||||
|
/>
|
||||||
|
<ft-radio-button
|
||||||
|
:title="timeTitle"
|
||||||
|
:labels="timeLabels"
|
||||||
|
:values="timeValues"
|
||||||
|
class="searchRadio radioMargin"
|
||||||
|
@change="updateTime"
|
||||||
|
/>
|
||||||
|
<ft-radio-button
|
||||||
|
:title="typeTitle"
|
||||||
|
:labels="typeLabels"
|
||||||
|
:values="typeValues"
|
||||||
|
class="searchRadio radioMargin"
|
||||||
|
@change="updateType"
|
||||||
|
/>
|
||||||
|
<ft-radio-button
|
||||||
|
:title="durationTitle"
|
||||||
|
:labels="durationLabels"
|
||||||
|
:values="durationValues"
|
||||||
|
class="radioMargin"
|
||||||
|
@change="updateDuration"
|
||||||
|
/>
|
||||||
|
</ft-flex-box>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-search-filters.js" />
|
||||||
|
<style scoped src="./ft-search-filters.css" />
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
This file is part of FreeTube.
|
||||||
|
|
||||||
|
FreeTube is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
FreeTube is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Credit goes to pavelvaravko for making this css.
|
||||||
|
* https://codepen.io/pavelvaravko/pen/qjojOr
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* select starting stylings ------------------------------*/
|
||||||
|
.select {
|
||||||
|
position: relative;
|
||||||
|
width: 200px;
|
||||||
|
padding: 10px 10px 10px 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-text {
|
||||||
|
position: relative;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
width: 200px;
|
||||||
|
padding: 10px 10px 10px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove focus */
|
||||||
|
.select-text:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use custom arrow */
|
||||||
|
.select .select-text {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 10px;
|
||||||
|
/* Styling the down arrow */
|
||||||
|
padding: 0;
|
||||||
|
content: '';
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--teritary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* LABEL ======================================= */
|
||||||
|
.select-label {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: normal;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 0;
|
||||||
|
top: 10px;
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
color: var(--teritary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active state */
|
||||||
|
.select-text:focus ~ .select-label, .select-text:valid ~ .select-label {
|
||||||
|
color: var(--accent-color);
|
||||||
|
top: -20px;
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BOTTOM BARS ================================= */
|
||||||
|
.select-bar {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:before, .select-bar:after {
|
||||||
|
content: '';
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
bottom: 1px;
|
||||||
|
position: absolute;
|
||||||
|
background: var(--accent-color);
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:before {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:after {
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active state */
|
||||||
|
.select-text:focus ~ .select-bar:before, .select-text:focus ~ .select-bar:after {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HIGHLIGHTER ================================== */
|
||||||
|
.select-highlight {
|
||||||
|
position: absolute;
|
||||||
|
height: 60%;
|
||||||
|
width: 100px;
|
||||||
|
top: 25%;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtSelect',
|
||||||
|
props: {
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectNames: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectValues: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<div class="select">
|
||||||
|
<select
|
||||||
|
class="select-text"
|
||||||
|
:value="value"
|
||||||
|
@change="$emit('change', $event.target.value)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(name, index) in selectNames"
|
||||||
|
:key="index"
|
||||||
|
:value="selectValues[index]"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="sort-down"
|
||||||
|
class="iconSelect"
|
||||||
|
/>
|
||||||
|
<span class="select-highlight" />
|
||||||
|
<span class="select-bar" />
|
||||||
|
<label class="select-label">
|
||||||
|
{{ placeholder }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-select.js" />
|
||||||
|
<style scoped src="./ft-select.css" />
|
|
@ -0,0 +1,8 @@
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ftVideoPlayer {
|
||||||
|
width: 85%;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import FtCard from '../ft-card/ft-card.vue'
|
||||||
|
|
||||||
|
// I haven't decided which video player I want to use
|
||||||
|
// Need to expirement with both of them to see which one will work best.
|
||||||
|
import videojs from 'video.js'
|
||||||
|
// import mediaelement from 'mediaelement'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtVideoPlayer',
|
||||||
|
components: {
|
||||||
|
'ft-card': FtCard
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => { return [] }
|
||||||
|
},
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
player: null,
|
||||||
|
dataSetup: {
|
||||||
|
aspectRatio: '16:9',
|
||||||
|
playbackRates: [
|
||||||
|
0.25,
|
||||||
|
0.5,
|
||||||
|
0.75,
|
||||||
|
1,
|
||||||
|
1.25,
|
||||||
|
1.5,
|
||||||
|
1.75,
|
||||||
|
2,
|
||||||
|
2.25,
|
||||||
|
2.5,
|
||||||
|
2.75,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.id = this._uid
|
||||||
|
setTimeout(this.initializePlayer, 100)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initializePlayer: function () {
|
||||||
|
console.log(this.id)
|
||||||
|
const videoPlayer = document.getElementById(this.id)
|
||||||
|
console.log(videoPlayer)
|
||||||
|
if (videoPlayer !== null) {
|
||||||
|
this.player = videojs(videoPlayer)
|
||||||
|
console.log(videojs.players)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<video
|
||||||
|
:id="id"
|
||||||
|
class="ftVideoPlayer video-js vjs-default-skin"
|
||||||
|
width="800"
|
||||||
|
height="600"
|
||||||
|
controls
|
||||||
|
preload="auto"
|
||||||
|
:data-setup="JSON.stringify(dataSetup)"
|
||||||
|
>
|
||||||
|
<source
|
||||||
|
:src="src"
|
||||||
|
type="video/mp4"
|
||||||
|
>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./ft-video-player.js" />
|
||||||
|
<style scoped src="./ft-video-player.css" />
|
|
@ -0,0 +1,29 @@
|
||||||
|
.playListThumbnail {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistThumbnail img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistChannel {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistChannel img {
|
||||||
|
width: 70px;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistChannel h3 {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin-left: 10px;
|
||||||
|
top: 5px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue'
|
||||||
|
import { shell } from 'electron'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'FtElementList',
|
||||||
|
components: {
|
||||||
|
'ft-list-dropdown': FtListDropdown
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
thumbnail: '',
|
||||||
|
channelThumbnail: '',
|
||||||
|
channelName: '',
|
||||||
|
channelId: '',
|
||||||
|
videoCount: 0,
|
||||||
|
viewCount: 0,
|
||||||
|
lastUpdated: '',
|
||||||
|
description: '',
|
||||||
|
shareHeaders: [
|
||||||
|
'Copy YouTube Link',
|
||||||
|
'Open in YouTube',
|
||||||
|
'Copy Invidious Link',
|
||||||
|
'Open in Invidious'
|
||||||
|
],
|
||||||
|
shareValues: [
|
||||||
|
'copyYoutube',
|
||||||
|
'openYoutube',
|
||||||
|
'copyInvidious',
|
||||||
|
'openInvidious'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listType: function () {
|
||||||
|
return this.$store.getters.getListType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
console.log(this.data)
|
||||||
|
this.id = this.data.id
|
||||||
|
this.title = this.data.title
|
||||||
|
this.thumbnail = this.data.thumbnail
|
||||||
|
this.channelName = this.data.channelName
|
||||||
|
this.channelThumbnail = this.data.channelThumbnail
|
||||||
|
this.uploadedTime = this.data.uploaded_at
|
||||||
|
this.description = this.data.description
|
||||||
|
|
||||||
|
// Causes errors if not put inside of a check
|
||||||
|
if (typeof (this.data.viewCount) !== 'undefined') {
|
||||||
|
this.viewCount = this.data.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (this.data.videoCount) !== 'undefined') {
|
||||||
|
this.videoCount = this.data.videoCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastUpdated = this.data.lastUpdated
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sharePlaylist: function (method) {
|
||||||
|
const youtubeUrl = `https://youtube.com/playlist?list=${this.id}`
|
||||||
|
const invidiousUrl = `https://invidio.us/playlist?list=${this.id}`
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'copyYoutube':
|
||||||
|
navigator.clipboard.writeText(youtubeUrl)
|
||||||
|
break
|
||||||
|
case 'openYoutube':
|
||||||
|
shell.openExternal(youtubeUrl)
|
||||||
|
break
|
||||||
|
case 'copyInvidious':
|
||||||
|
navigator.clipboard.writeText(invidiousUrl)
|
||||||
|
break
|
||||||
|
case 'openInvidious':
|
||||||
|
shell.openExternal(invidiousUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<div class="playlistInfo">
|
||||||
|
<div
|
||||||
|
class="playlistThumbnail"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="thumbnail"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<h2>
|
||||||
|
{{ title }}
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
{{ videoCount }} videos - {{ viewCount }} views - Last updated on {{ lastUpdated }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<div
|
||||||
|
class="playlistChannel"
|
||||||
|
>
|
||||||
|
<img :src="channelThumbnail">
|
||||||
|
<h3>
|
||||||
|
{{ channelName }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<ft-list-dropdown
|
||||||
|
title="SHARE PLAYLIST"
|
||||||
|
:label-names="shareHeaders"
|
||||||
|
:label-values="shareValues"
|
||||||
|
@click="sharePlaylist"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./playlist-info.js" />
|
||||||
|
<style scoped src="./playlist-info.css" />
|
|
@ -0,0 +1,92 @@
|
||||||
|
.sideNav {
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
width: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 60px;
|
||||||
|
-webkit-box-shadow: 1px -1px 1px -1px var(--primary-shadow-color);
|
||||||
|
background-color: var(--side-nav-color);
|
||||||
|
transition-property: width;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topNavOption {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navOption {
|
||||||
|
position: relative;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navOption:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navOption:active {
|
||||||
|
background-color: var(--side-nav-active-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navIcon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLabel {
|
||||||
|
margin-left: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav hr {
|
||||||
|
width: 90%;
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refreshIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed .refreshIcon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed .navOption {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed .navIcon {
|
||||||
|
margin-left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed .navLabel {
|
||||||
|
margin-left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
left: 0px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import router from '../../router/index.js'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'SideNav',
|
||||||
|
computed: {
|
||||||
|
isOpen: function () {
|
||||||
|
return this.$store.getters.getIsSideNavOpen
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate: function (route) {
|
||||||
|
router.push('/' + route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="sideNav"
|
||||||
|
class="sideNav"
|
||||||
|
:class="{closed: !isOpen}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="navOption topNavOption"
|
||||||
|
@click="navigate('subscriptions')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="rss"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
Subscriptions
|
||||||
|
</p>
|
||||||
|
<font-awesome-icon
|
||||||
|
class="refreshIcon"
|
||||||
|
icon="sync"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('trending')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="fire"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
Trending
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('popular')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="users"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
Most Popular
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('userplaylists')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="star"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
Playlists
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('history')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="history"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
History
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('settings')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="sliders-h"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
Settings
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="navOption"
|
||||||
|
@click="navigate('about')"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="info-circle"
|
||||||
|
class="navIcon"
|
||||||
|
/>
|
||||||
|
<p class="navLabel">
|
||||||
|
About
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./side-nav.js" />
|
||||||
|
<style scoped src="./side-nav.css" />
|
|
@ -0,0 +1,173 @@
|
||||||
|
.topNav {
|
||||||
|
background-color: var(--card-bg-color);
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 4;
|
||||||
|
-webkit-box-shadow: 0px 2px 1px 0px var(--primary-shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-transition: background 0.2s ease-out;
|
||||||
|
-moz-transition: background 0.2s ease-out;
|
||||||
|
-o-transition: background 0.2s ease-out;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIcon:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIcon:active {
|
||||||
|
background-color: var(--teritary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navBackIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 50px;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-transition: background 0.2s ease-out;
|
||||||
|
-moz-transition: background 0.2s ease-out;
|
||||||
|
-o-transition: background 0.2s ease-out;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navBackIcon:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navBackIcon:active {
|
||||||
|
background-color: var(--teritary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navForwardIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 85px;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-transition: background 0.2s ease-out;
|
||||||
|
-moz-transition: background 0.2s ease-out;
|
||||||
|
-o-transition: background 0.2s ease-out;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navForwardIcon:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navForwardIcon:active {
|
||||||
|
background-color: var(--teritary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoIcon {
|
||||||
|
background-image: url("/_icons/iconColorSmall.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right top;
|
||||||
|
background-size: 25px;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 140px;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoText {
|
||||||
|
background-image: url("/_icons/textColorSmall.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right top;
|
||||||
|
background-size: 100px;
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
left: 175px;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchContainer {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 500px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navFilterIcon {
|
||||||
|
position: absolute;
|
||||||
|
padding: 10px;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navFilterIcon:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navFilterIcon:active {
|
||||||
|
background-color: var(--teritary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchFilters {
|
||||||
|
margin-left: 270px;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition-property: margin;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand {
|
||||||
|
margin-left: 100px;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import FtInput from '../ft-input/ft-input.vue'
|
||||||
|
import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue'
|
||||||
|
import router from '../../router/index.js'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'TopNav',
|
||||||
|
components: {
|
||||||
|
FtInput,
|
||||||
|
FtSearchFilters
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
component: this,
|
||||||
|
showFilters: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchSettings: function () {
|
||||||
|
return this.$store.getters.getSearchSettings
|
||||||
|
},
|
||||||
|
|
||||||
|
isSideNavOpen: function () {
|
||||||
|
return this.$store.getters.getIsSideNavOpen
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToSearch: function (query) {
|
||||||
|
console.log(this)
|
||||||
|
this.showFilters = false
|
||||||
|
router.push(
|
||||||
|
{
|
||||||
|
path: `/search/${query}`,
|
||||||
|
query: {
|
||||||
|
sortBy: this.searchSettings.sortBy,
|
||||||
|
time: this.searchSettings.time,
|
||||||
|
type: this.searchSettings.type,
|
||||||
|
duration: this.searchSettings.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
historyBack: function () {
|
||||||
|
window.history.back()
|
||||||
|
},
|
||||||
|
|
||||||
|
historyForward: function () {
|
||||||
|
window.history.forward()
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleSideNav: function () {
|
||||||
|
this.$store.commit('toggleSideNav')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="topNav">
|
||||||
|
<font-awesome-icon
|
||||||
|
class="menuIcon"
|
||||||
|
icon="bars"
|
||||||
|
@click="toggleSideNav"
|
||||||
|
/>
|
||||||
|
<font-awesome-icon
|
||||||
|
class="navBackIcon"
|
||||||
|
icon="arrow-left"
|
||||||
|
@click="historyBack"
|
||||||
|
/>
|
||||||
|
<font-awesome-icon
|
||||||
|
class="navForwardIcon"
|
||||||
|
icon="arrow-right"
|
||||||
|
@click="historyForward"
|
||||||
|
/>
|
||||||
|
<div class="logoIcon" />
|
||||||
|
<div class="logoText" />
|
||||||
|
<div class="searchContainer">
|
||||||
|
<ft-input
|
||||||
|
placeholder="Search / Go to URL"
|
||||||
|
class="search"
|
||||||
|
@click="goToSearch"
|
||||||
|
/>
|
||||||
|
<font-awesome-icon
|
||||||
|
class="navFilterIcon"
|
||||||
|
icon="filter"
|
||||||
|
@click="showFilters = !showFilters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ft-search-filters
|
||||||
|
v-show="showFilters"
|
||||||
|
class="searchFilters"
|
||||||
|
:class="{ expand: !isSideNavOpen }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./top-nav.js" />
|
||||||
|
<style scoped src="./top-nav.css" />
|