Merge https://github.com/VueTubeApp/VueTube into main
81
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
name: 🐞 Issue Report
|
||||
description: Report a issue in VueTube
|
||||
labels: [bug]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Provide an example of the issue.
|
||||
placeholder: |
|
||||
Example:
|
||||
1. First step
|
||||
2. Second step
|
||||
3. Issue here
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
placeholder: |
|
||||
Example:
|
||||
"This should happen..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
placeholder: |
|
||||
Example:
|
||||
"This happened instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: vuetube-version
|
||||
attributes:
|
||||
label: VueTube version
|
||||
description: |
|
||||
You can find your VueTube version in **Settings**.
|
||||
placeholder: |
|
||||
Example: "1.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: android-version
|
||||
attributes:
|
||||
label: Android version
|
||||
description: |
|
||||
You can find this somewhere in your Android settings.
|
||||
placeholder: |
|
||||
Example: "Android 12"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to unstable version **[Latest](https://vuetube.app/install/)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,31 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Device Information**
|
||||
- OS: [e.g. iOS 15, Android 12]
|
||||
- App Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
35
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve the app
|
||||
labels: [feature request]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Describe your suggested feature
|
||||
description: How can an existing source be improved?
|
||||
placeholder: |
|
||||
Example:
|
||||
"It should work like this..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
Before Width: | Height: | Size: 8.6 KiB |
|
@ -1 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" version="1.1" viewBox="0 0 512 512" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><rect width="512" height="512" x="0" y="0" style="fill:url(#_Linear1)"/><g><circle cx="256" cy="256" r="200" style="fill:#fff;fill-opacity:.1"/></g><path d="M318.761,269.869c10.695,-6.153 10.695,-21.584 0,-27.737l-38.665,-22.246l39.873,-18.593l16.745,9.634l54.237,31.205c10.695,6.153 10.695,21.584 0,27.737l-54.219,31.193l-0.018,0.011l-18.071,10.397l-39.873,-18.593l39.991,-23.008Zm-121.761,24.599l-0,17.796c0.005,12.299 13.308,19.994 23.971,13.866l0.008,-0.004l19.658,-11.31l39.872,18.593l-41.577,23.921l-0.028,0.016l-53.925,31.025c-10.667,6.137 -23.979,-1.563 -23.979,-13.869l-0,-96.821l36,16.787Zm84.835,-115.114l-39.873,18.593l-20.983,-12.073l-0.013,-0.007c-10.664,-6.123 -23.966,1.575 -23.966,13.876l-0,19.17l-36,16.787l-0,-98.202c-0,-12.306 13.312,-20.006 23.979,-13.869l53.946,31.037l0.007,0.004l42.903,24.684Z" style="fill:#fff"/><defs><linearGradient id="_Linear1" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(512,512,-512,512,0,0)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#4a8;stop-opacity:1"/><stop offset="1" style="stop-color:#345;stop-opacity:1"/></linearGradient></defs></svg>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" fill="url(#paint0_linear_6_11)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M327.768 269.391C338.454 263.236 338.454 247.814 327.766 241.66L327.761 241.658L291.527 220.81L331.399 202.218L345.714 210.453L345.736 210.466L386.392 233.857C403.102 243.471 403.102 267.582 386.392 277.196L345.743 300.582L345.714 300.599L328.778 310.343L288.905 291.75L327.761 269.395L327.768 269.391ZM206 290.605V311.783V311.79C206.005 324.092 219.314 331.788 229.979 325.652L252.89 312.471L292.763 331.063L247.932 356.856L247.903 356.873L207.467 380.137C190.801 389.725 170 377.695 170 358.467L170 311.783L170 273.818L206 290.605ZM295.384 181.497L255.512 200.09L229.979 185.4L229.964 185.392C219.3 179.27 206 186.968 206 199.269V223.177L170 239.965L170 199.269L170 152.585C170 133.357 190.801 121.327 207.467 130.915L247.917 154.187L247.932 154.196L295.384 181.497Z" fill="url(#paint1_linear_6_11)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_6_11" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1B3245"/>
|
||||
<stop offset="1" stop-color="#0C2028"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_6_11" x1="170" y1="128" x2="431" y2="389" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00E1C3"/>
|
||||
<stop offset="1" stop-color="#00D1D5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 8.7 KiB |
|
@ -1,17 +1,22 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="commentRenderer"
|
||||
v-ripple
|
||||
class="comment-thread px-3"
|
||||
v-if="commentRenderer"
|
||||
@click="$emit('showReplies', comment)"
|
||||
>
|
||||
<a
|
||||
:href="
|
||||
this.$rendererUtils.getNavigationEndpoints(
|
||||
commentRenderer.authorEndpoint
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
to="/channel"
|
||||
class="avatar-link mr-4"
|
||||
style="height: 35px; width: 35px"
|
||||
@click.prevent="
|
||||
$store.dispatch(
|
||||
'channel/fetchChannel',
|
||||
$rendererUtils.getNavigationEndpoints(commentRenderer.authorEndpoint)
|
||||
)
|
||||
"
|
||||
class="avatar-link"
|
||||
>
|
||||
<v-img
|
||||
class="avatar-thumbnail"
|
||||
|
@ -21,83 +26,76 @@
|
|||
].url
|
||||
"
|
||||
/>
|
||||
</a>
|
||||
</v-btn>
|
||||
<div class="comment-content">
|
||||
<div class="comment-content--header subtitle-2">
|
||||
<div
|
||||
class="comment-content--header background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-5' : 'text--darken-4'"
|
||||
style="font-size: 0.8rem !important"
|
||||
>
|
||||
<div
|
||||
class="author-badge-name mr-1"
|
||||
:class="{ owner: commentRenderer.authorIsChannelOwner }"
|
||||
class="author-badge-name mr-2"
|
||||
:class="
|
||||
commentRenderer.authorIsChannelOwner
|
||||
? $vuetify.theme.dark
|
||||
? 'owner primary--text background lighten-2'
|
||||
: 'owner primary--text background darken-2'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<div class="author-name--wrapper">
|
||||
<span class="font-weight-bold author-name" v-emoji>
|
||||
<span class="author-name mr-1" v-emoji>
|
||||
{{ commentRenderer.authorText.simpleText }}
|
||||
</span>
|
||||
</div>
|
||||
<template
|
||||
v-for="(badge, index) in commentRenderer.authorCommentBadge"
|
||||
>
|
||||
<author-comment-badge-renderer
|
||||
:metadata="badge"
|
||||
:key="index"
|
||||
class="ml-1"
|
||||
/>
|
||||
<author-comment-badge-renderer :metadata="badge" :key="index" />
|
||||
</template>
|
||||
<template
|
||||
v-for="(badge, index) in commentRenderer.sponsorCommentBadge"
|
||||
>
|
||||
<sponsor-comment-badge-renderer
|
||||
:metadata="badge"
|
||||
:key="index"
|
||||
class="ml-1"
|
||||
/>
|
||||
<sponsor-comment-badge-renderer :metadata="badge" :key="index" />
|
||||
</template>
|
||||
</div>
|
||||
<span
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
class="background--text comment-timestamp"
|
||||
>
|
||||
·
|
||||
<span class="comment-timestamp ml-2">
|
||||
{{ commentRenderer.publishedTimeText.runs[0].text }}
|
||||
</span>
|
||||
</div>
|
||||
<collapsable-text
|
||||
:lines="4"
|
||||
:expandText="
|
||||
:lines="3"
|
||||
:expand-text="
|
||||
commentRenderer.expandButton.buttonRenderer.text.runs[0].text
|
||||
"
|
||||
:collapseText="
|
||||
:collapse-text="
|
||||
commentRenderer.collapseButton.buttonRenderer.text.runs[0].text
|
||||
"
|
||||
>
|
||||
<yt-text-formatter :textRuns="commentRenderer.contentText.runs">
|
||||
<yt-text-formatter
|
||||
style="font-size: 0.9rem"
|
||||
:text-runs="commentRenderer.contentText.runs"
|
||||
>
|
||||
</yt-text-formatter>
|
||||
</collapsable-text>
|
||||
<div class="toolbar">
|
||||
<div class="toolbar mt-2">
|
||||
<v-btn-toggle v-model="voteStatus" group>
|
||||
<div class="toolbar--item mr-1">
|
||||
<v-btn class="toolbar--button like" disabled icon x-small plain>
|
||||
<v-icon small>mdi-thumb-up</v-icon>
|
||||
</v-btn>
|
||||
<v-icon small>mdi-thumb-up-outline</v-icon>
|
||||
<span
|
||||
v-if="commentRenderer.voteCount"
|
||||
class="like-count caption"
|
||||
v-text="commentRenderer.voteCount.simpleText"
|
||||
class="like-count subtitle-2"
|
||||
></span>
|
||||
</div>
|
||||
<div class="toolbar--item">
|
||||
<v-btn class="toolbar--button dislike" disabled icon x-small plain>
|
||||
<v-icon small>mdi-thumb-down</v-icon>
|
||||
</v-btn>
|
||||
<v-icon class="ml-2" small>mdi-thumb-down-outline</v-icon>
|
||||
</div>
|
||||
</v-btn-toggle>
|
||||
<div class="toolbar--item">
|
||||
<v-btn class="toolbar--button reply ml-2" disabled icon x-small plain>
|
||||
<v-icon small>mdi-comment</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="toolbar--item" v-if="commentRenderer.replyCount">
|
||||
<div class="toolbar--item ml-6" v-if="commentRenderer.replyCount">
|
||||
<v-icon small>mdi-comment-outline</v-icon>
|
||||
<span
|
||||
class="like-count caption"
|
||||
v-text="commentRenderer.replyCount"
|
||||
class="like-count mr-1 subtitle-2"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -118,10 +116,9 @@
|
|||
padding: 10px 0;
|
||||
|
||||
.avatar-thumbnail {
|
||||
margin-right: 0.5rem;
|
||||
border-radius: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
|
@ -162,14 +159,9 @@
|
|||
}
|
||||
|
||||
.owner {
|
||||
padding: 0 0.6em;
|
||||
background-color: #888888;
|
||||
color: #fff;
|
||||
border-radius: 1em;
|
||||
|
||||
&::v-deep .author-badge {
|
||||
color: #fff;
|
||||
}
|
||||
padding: 0 0.3em 0 0.6em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.toolbar--button::v-deep.v-btn--active .v-btn__content {
|
||||
|
|
|
@ -22,6 +22,22 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["comment"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
boxRenderer: null,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.boxRenderer = this.comment?.createRenderer?.commentSimpleboxRenderer;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.entry {
|
||||
width: 100%; /* Prevent Loading Weirdness */
|
||||
|
@ -43,19 +59,3 @@
|
|||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["comment"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
boxRenderer: null,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.boxRenderer = this.comment?.createRenderer?.commentSimpleboxRenderer;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -15,27 +15,28 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-for="(comment, index) in comments">
|
||||
<v-list-item :key="index" class="px-0">
|
||||
<div
|
||||
v-for="(comment, index) in comments"
|
||||
:key="index"
|
||||
class="commentElement"
|
||||
>
|
||||
<v-list-item class="px-0">
|
||||
<component
|
||||
v-if="getComponents()[Object.keys(comment)[0]]"
|
||||
:is="Object.keys(comment)[0]"
|
||||
v-if="getComponents()[Object.keys(comment)[0]]"
|
||||
:comment="comment[Object.keys(comment)[0]]"
|
||||
@intersect="paginate"
|
||||
@showReplies="openReply"
|
||||
></component>
|
||||
</v-list-item>
|
||||
<v-divider
|
||||
v-if="getComponents()[Object.keys(comment)[0]]"
|
||||
:key="index"
|
||||
></v-divider>
|
||||
</template>
|
||||
<v-divider v-if="getComponents()[Object.keys(comment)[0]]"></v-divider>
|
||||
</div>
|
||||
|
||||
<div class="loading" v-if="loading">
|
||||
<v-sheet
|
||||
color="background"
|
||||
v-for="i in comments.length <= 0 ? 5 : 1"
|
||||
:key="i"
|
||||
color="background"
|
||||
>
|
||||
<v-skeleton-loader type="list-item-avatar-three-line" />
|
||||
</v-sheet>
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
<template>
|
||||
<comment-thread-renderer :comment="parentComment" />
|
||||
<v-divider></v-divider>
|
||||
<template v-for="index in 10">
|
||||
<comment-thread-renderer :comment="parentComment" v-bind:key="index" />
|
||||
</template>
|
||||
<comment-thread-renderer
|
||||
v-for="index in 10"
|
||||
v-bind:key="index"
|
||||
:comment="parentComment"
|
||||
/>
|
||||
</template>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
|
|
@ -1,15 +1,28 @@
|
|||
<template>
|
||||
<v-card
|
||||
class="entry gridVideoRenderer background"
|
||||
:to="`/watch?v=${video.videoId}`"
|
||||
flat
|
||||
to="/channel"
|
||||
class="entry gridVideoRenderer background"
|
||||
:class="
|
||||
roundThumb && roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'lighten-1'
|
||||
: 'darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: roundThumb ? `${roundTweak / 2}rem` : '0',
|
||||
margin:
|
||||
roundThumb && roundTweak > 0 ? '0 1rem 1rem 1rem' : '0 0 0.25rem 0',
|
||||
}"
|
||||
@click="$store.dispatch('channel/fetchChannel', video.channelId)"
|
||||
>
|
||||
<div id="details">
|
||||
<div id="details" class="pa-4">
|
||||
<a
|
||||
:href="
|
||||
this.$rendererUtils.getNavigationEndpoints(video.navigationEndpoint)
|
||||
"
|
||||
class="avatar-link pt-2"
|
||||
class="avatar-link"
|
||||
>
|
||||
<v-img
|
||||
class="avatar-thumbnail"
|
||||
|
@ -19,12 +32,11 @@
|
|||
"
|
||||
/>
|
||||
</a>
|
||||
<v-card-text class="video-info pt-2" v-emoji>
|
||||
<v-card-text class="video-info py-0" v-emoji>
|
||||
<div
|
||||
v-for="title in video.title.runs"
|
||||
:key="title.text"
|
||||
style="margin-top: 0.5em"
|
||||
class="vid-title"
|
||||
class="vid-title mt-1"
|
||||
>
|
||||
{{ title.text }}
|
||||
</div>
|
||||
|
@ -35,10 +47,43 @@
|
|||
v-text="parseBottom(video)"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
elevation="0"
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
style="width: 50px !important; height: 50px !important; z-index: 420"
|
||||
>
|
||||
<v-icon>mdi-share-outline</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
computed: {
|
||||
roundTweak() {
|
||||
return this.$store.state.tweaks.roundTweak;
|
||||
},
|
||||
roundThumb() {
|
||||
return this.$store.state.tweaks.roundThumb;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
parseBottom(video) {
|
||||
const bottomText = [
|
||||
video.subscriberCountText?.runs[0].text,
|
||||
video.videoCountText?.runs.map((run) => run.text).join(" "),
|
||||
];
|
||||
return bottomText.join(" · ");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.entry {
|
||||
width: 100%; /* Prevent Loading Weirdness */
|
||||
|
@ -53,8 +98,6 @@
|
|||
}
|
||||
|
||||
.avatar-thumbnail {
|
||||
margin-top: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -64,7 +107,6 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-basis: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@media screen and (orientation: landscape) {
|
||||
|
@ -76,19 +118,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
|
||||
methods: {
|
||||
parseBottom(video) {
|
||||
const bottomText = [
|
||||
video.subscriberCountText?.runs[0].text,
|
||||
video.videoCountText?.runs.map((run) => run.text).join(" "),
|
||||
];
|
||||
return bottomText.join(" · ");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
class="pa-0 min-height-0"
|
||||
>
|
||||
<component
|
||||
v-if="getComponents()[Object.keys(renderer)[0]]"
|
||||
:is="Object.keys(renderer)[0]"
|
||||
v-if="getComponents()[Object.keys(renderer)[0]]"
|
||||
:key="index"
|
||||
:render="renderer[Object.keys(renderer)[0]]"
|
||||
></component>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="fill-width">
|
||||
<v-list-item
|
||||
v-for="(video, index) in render.items"
|
||||
:key="index"
|
||||
class="pa-0 min-height-0"
|
||||
>
|
||||
<component
|
||||
v-if="getComponents()[Object.keys(video)[0]]"
|
||||
:is="Object.keys(video)[0]"
|
||||
v-if="getComponents()[Object.keys(video)[0]]"
|
||||
:key="video[Object.keys(video)[0]].videoId"
|
||||
:video="video[Object.keys(video)[0]]"
|
||||
></component>
|
||||
|
|
11
NUXT/components/Player/captions.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
disabled
|
||||
style="position: absolute; top: 0.25rem; right: 3rem"
|
||||
>
|
||||
<v-icon>mdi-closed-caption-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
13
NUXT/components/Player/close.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<!-- TODO: change /home to $router.goBack() or $router.go(-1) -->
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
style="position: absolute; top: 0.25rem; right: 0.25rem"
|
||||
to="/home"
|
||||
color="white"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
|
@ -1,95 +0,0 @@
|
|||
<template>
|
||||
<div class="controls" @click="toggleControls()">
|
||||
<div class="controlsWrap" ref="controlsWrap">
|
||||
|
||||
<div class="centerVideoControls">
|
||||
<v-btn @click="togglePlaying()" text style="height: 5em; width: 5em;">
|
||||
<v-icon size="5em" v-text="playing ? 'mdi-pause' : 'mdi-play' " ref="pausePlayIndicator" />
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="bottomVideoControls">
|
||||
{{ watched }} <span style="color: #999;">/ {{ $vuetube.humanTime(video.duration) }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.centerVideoControls {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.bottomVideoControls {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.pausePlay {
|
||||
min-height: 5em;
|
||||
min-width: 5em;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.controlsWrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
playing: true,
|
||||
controls: true,
|
||||
|
||||
watched: 0,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.video.ontimeupdate = () => {
|
||||
console.log(this.video.currentTime)
|
||||
this.watched = this.$vuetube.humanTime(this.video.currentTime);
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
togglePlaying() {
|
||||
if (this.video.paused) {
|
||||
this.video.play()
|
||||
this.playing = true;
|
||||
} else {
|
||||
this.video.pause()
|
||||
this.playing = false;
|
||||
};
|
||||
this.toggleControls(); // Prevent Screen From Closing
|
||||
},
|
||||
|
||||
toggleControls() {
|
||||
const setControls = this.controls ? 'none' : 'block';
|
||||
this.$refs.controlsWrap.style.display = setControls;
|
||||
this.controls = !this.controls;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
24
NUXT/components/Player/fullscreen.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
color="white"
|
||||
style="position: absolute; bottom: 0.25rem; right: 0.25rem"
|
||||
@click.stop="$emit('fullscreen')"
|
||||
>
|
||||
<v-icon>{{ fullscreen ? "mdi-fullscreen-exit" : "mdi-fullscreen" }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["fullscreen"],
|
||||
};
|
||||
</script>
|
|
@ -1,54 +1,268 @@
|
|||
<template>
|
||||
<div style="position: relative;">
|
||||
<!-- // TODO: down: () => minimize, -->
|
||||
<div
|
||||
ref="vidcontainer"
|
||||
v-touch="{
|
||||
up: () => (contain = false),
|
||||
down: () => (contain = true),
|
||||
}"
|
||||
class="d-flex flex-column"
|
||||
style="position: relative"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||
: '0',
|
||||
}"
|
||||
@click="controls = !controls"
|
||||
>
|
||||
<video
|
||||
ref="player"
|
||||
autoplay
|
||||
:src="vidSrc"
|
||||
width="100%"
|
||||
style="max-height: 50vh; display: block"
|
||||
@webkitfullscreenchange="handleFullscreenChange"
|
||||
:height="isFullscreen ? '100%' : 'auto'"
|
||||
:src="vidSrc"
|
||||
style="transition: filter 0.15s ease-in-out"
|
||||
:class="controls || seeking ? 'dim' : ''"
|
||||
:style="contain ? 'object-fit: contain;' : 'object-fit: cover;'"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="isFullscreen && controls"
|
||||
style="
|
||||
position: absolute;
|
||||
width: calc(100% - 12rem);
|
||||
left: 3rem;
|
||||
top: 0.5rem;
|
||||
"
|
||||
>
|
||||
<h4>{{ video.title }}</h4>
|
||||
<div style="color: #aaa; font-size: 0.75rem">{{ video.channelName }}</div>
|
||||
</div>
|
||||
|
||||
<seekbar :video=$refs.player v-if="$refs.player" />
|
||||
<controls v-if="$refs.player" :video="$refs.player" />
|
||||
<!-- // TODO: merge the bottom 2 into 1 reusable component -->
|
||||
<v-btn
|
||||
text
|
||||
tile
|
||||
style="
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
"
|
||||
@dblclick.stop="$refs.player.currentTime -= 10"
|
||||
>
|
||||
<v-icon>mdi-rewind</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <v-slider v-model="value" step="0"></v-slider> -->
|
||||
<v-btn
|
||||
text
|
||||
tile
|
||||
style="
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
"
|
||||
@dblclick.stop="$refs.player.currentTime += 10"
|
||||
>
|
||||
<v-icon>mdi-fast-forward</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<div
|
||||
style="transition: opacity 0.15s ease-in-out"
|
||||
:style="controls ? 'opacity: 1;' : 'opacity: 0; pointer-events: none'"
|
||||
>
|
||||
<minimize />
|
||||
<loop />
|
||||
<captions />
|
||||
<close />
|
||||
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
style="
|
||||
position: absolute;
|
||||
top: calc(50% - 1.25rem);
|
||||
left: calc(50% - 10rem);
|
||||
"
|
||||
color="white"
|
||||
@click.stop="$refs.player.currentTime -= 5"
|
||||
>
|
||||
<v-icon size="1rem">mdi-rewind-5</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
style="
|
||||
position: absolute;
|
||||
top: calc(50% - 1.75rem);
|
||||
left: calc(50% - 6.5rem);
|
||||
"
|
||||
color="white"
|
||||
disabled
|
||||
@click.stop=""
|
||||
>
|
||||
<v-icon size="2rem">mdi-skip-previous</v-icon>
|
||||
</v-btn>
|
||||
<playpause
|
||||
v-if="$refs.player"
|
||||
:video="$refs.player"
|
||||
@close="controls = false"
|
||||
/>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
style="
|
||||
position: absolute;
|
||||
top: calc(50% - 1.75rem);
|
||||
left: calc(50% + 3rem);
|
||||
"
|
||||
color="white"
|
||||
disabled
|
||||
@click.stop=""
|
||||
>
|
||||
<v-icon size="2rem">mdi-skip-next</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
style="
|
||||
position: absolute;
|
||||
top: calc(50% - 1.25rem);
|
||||
left: calc(50% + 7rem);
|
||||
"
|
||||
color="white"
|
||||
@click.stop="$refs.player.currentTime += 5"
|
||||
>
|
||||
<v-icon size="1rem">mdi-fast-forward-5</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<watchtime v-if="$refs.player" :video="$refs.player" />
|
||||
<!-- // TODO: merge the bottom 2 into 1 reusable component -->
|
||||
<quality v-if="$refs.player" :video="$refs.player" :sources="sources" />
|
||||
<speed v-if="$refs.player" :video="$refs.player" />
|
||||
<fullscreen
|
||||
:fullscreen="isFullscreen"
|
||||
@fullscreen="(controls = $refs.player.paused), handleFullscreenChange()"
|
||||
/>
|
||||
</div>
|
||||
<!-- NOTE: breaks in fullscreen -->
|
||||
<seekbar
|
||||
v-if="$refs.player"
|
||||
v-show="!isFullscreen || controls"
|
||||
:fullscreen="isFullscreen"
|
||||
:video="$refs.player"
|
||||
:sources="sources"
|
||||
:controls="controls"
|
||||
@seeking="seeking = !seeking"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import seekbar from '~/components/Player/seekbar.vue';
|
||||
import controls from '~/components/Player/controls.vue';
|
||||
|
||||
import loop from "~/components/Player/loop.vue";
|
||||
import close from "~/components/Player/close.vue";
|
||||
import speed from "~/components/Player/speed.vue";
|
||||
import seekbar from "~/components/Player/seekbar.vue";
|
||||
import quality from "~/components/Player/quality.vue";
|
||||
import minimize from "~/components/Player/minimize.vue";
|
||||
import captions from "~/components/Player/captions.vue";
|
||||
import playpause from "~/components/Player/playpause.vue";
|
||||
import watchtime from "~/components/Player/watchtime.vue";
|
||||
import fullscreen from "~/components/Player/fullscreen.vue";
|
||||
export default {
|
||||
props: ["sources"],
|
||||
components: {
|
||||
fullscreen,
|
||||
watchtime,
|
||||
playpause,
|
||||
captions,
|
||||
minimize,
|
||||
quality,
|
||||
seekbar,
|
||||
controls
|
||||
speed,
|
||||
close,
|
||||
loop,
|
||||
},
|
||||
props: {
|
||||
sources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
video: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false,
|
||||
controls: false,
|
||||
seeking: false,
|
||||
contain: true,
|
||||
vidSrc: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.vidSrc = this.sources[this.sources.length-1].url;
|
||||
console.log("sources", this.sources);
|
||||
this.vidSrc = this.sources[this.sources.length - 1].url;
|
||||
// TODO: detect orientation change and enter fullscreen
|
||||
// TODO: detect video loading state and send this.loading to play button :loading = loading
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.isFullscreen) this.exitFullscreen();
|
||||
},
|
||||
methods: {
|
||||
handleFullscreenChange() {
|
||||
if (document.fullscreenElement === this.$refs.player) {
|
||||
this.$vuetube.statusBar.hide();
|
||||
this.$vuetube.navigationBar.hide();
|
||||
if (document?.fullscreenElement === this.$refs.vidcontainer) {
|
||||
this.exitFullscreen();
|
||||
} else {
|
||||
this.$vuetube.statusBar.show();
|
||||
this.$vuetube.navigationBar.show();
|
||||
this.openFullscreen();
|
||||
}
|
||||
},
|
||||
|
||||
exitFullscreen() {
|
||||
const cancellFullScreen =
|
||||
document.exitFullscreen ||
|
||||
document.mozCancelFullScreen ||
|
||||
document.webkitExitFullscreen ||
|
||||
document.msExitFullscreen;
|
||||
cancellFullScreen.call(document);
|
||||
screen.orientation.lock("portrait");
|
||||
screen.orientation.unlock();
|
||||
this.$vuetube.navigationBar.show();
|
||||
this.$vuetube.statusBar.show();
|
||||
this.isFullscreen = false;
|
||||
},
|
||||
openFullscreen() {
|
||||
const element = this.$refs.vidcontainer;
|
||||
const requestFullScreen =
|
||||
element.requestFullscreen ||
|
||||
element.webkitRequestFullScreen ||
|
||||
element.mozRequestFullScreen ||
|
||||
element.msRequestFullScreen;
|
||||
requestFullScreen.call(element);
|
||||
screen.orientation.lock("landscape");
|
||||
this.$vuetube.navigationBar.hide();
|
||||
this.$vuetube.statusBar.hide();
|
||||
this.isFullscreen = true;
|
||||
},
|
||||
getPlayer() {
|
||||
return this.$refs.player;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dim {
|
||||
filter: brightness(42%);
|
||||
}
|
||||
.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<video
|
||||
ref="player"
|
||||
controls
|
||||
autoplay
|
||||
:src="vidSrc"
|
||||
width="100%"
|
||||
style="max-height: 50vh; display: block"
|
||||
@webkitfullscreenchange="handleFullscreenChange"
|
||||
/>
|
||||
<!-- <v-slider v-model="value" step="0"></v-slider> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["vidSrc"],
|
||||
methods: {
|
||||
handleFullscreenChange() {
|
||||
if (document.fullscreenElement === this.$refs.player) {
|
||||
this.$vuetube.statusBar.hide();
|
||||
this.$vuetube.navigationBar.hide();
|
||||
} else {
|
||||
this.$vuetube.statusBar.show();
|
||||
this.$vuetube.navigationBar.show();
|
||||
}
|
||||
},
|
||||
|
||||
getPlayer() {
|
||||
return this.$refs.player;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
11
NUXT/components/Player/loop.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
disabled
|
||||
style="position: absolute; top: 0.25rem; right: 6rem"
|
||||
>
|
||||
<v-icon>mdi-sync</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
11
NUXT/components/Player/minimize.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
disabled
|
||||
style="position: absolute; top: 0.25rem; left: 0.25rem"
|
||||
>
|
||||
<v-icon>mdi-chevron-down</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
27
NUXT/components/Player/playpause.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
large
|
||||
style="position: absolute; top: calc(50% - 2rem); left: calc(50% - 2rem)"
|
||||
color="white"
|
||||
@click.stop="
|
||||
(paused = !video.paused),
|
||||
video.paused ? (video.play(), $emit('close')) : video.pause()
|
||||
"
|
||||
>
|
||||
<v-icon size="3.5rem">
|
||||
{{ paused ? "mdi-play" : "mdi-pause" }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
emits: ["close"],
|
||||
data: () => ({
|
||||
paused: false,
|
||||
}),
|
||||
};
|
||||
</script>
|
66
NUXT/components/Player/quality.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-bottom-sheet
|
||||
v-model="sheet"
|
||||
:attach="$parent.$refs.vidcontainer"
|
||||
scrollable
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
style="position: absolute; bottom: 0.25rem; right: 3rem"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
{{ sources.find((src) => src.url == video.src).qualityLabel }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card
|
||||
v-touch="{
|
||||
down: () => (sheet = false),
|
||||
}"
|
||||
class="background"
|
||||
>
|
||||
<v-subheader>Quality for current video</v-subheader>
|
||||
<v-card-text style="max-height: 50vh" class="pa-0">
|
||||
<v-list-item
|
||||
v-for="src in sources"
|
||||
:key="src"
|
||||
@click="(sheet = false), (video.src = src.url)"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-icon
|
||||
:color="
|
||||
video.src === src.url
|
||||
? 'primary'
|
||||
: $vuetify.theme.dark
|
||||
? 'background lighten-2'
|
||||
: 'background darken-2'
|
||||
"
|
||||
v-text="
|
||||
video.src === src.url
|
||||
? 'mdi-radiobox-marked'
|
||||
: 'mdi-radiobox-blank'
|
||||
"
|
||||
></v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>
|
||||
{{ src.qualityLabel }} ({{ src.quality }})
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-bottom-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video", "sources"],
|
||||
data: () => ({
|
||||
sheet: false,
|
||||
}),
|
||||
};
|
||||
</script>
|
|
@ -1,42 +1,251 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
|
||||
<video
|
||||
ref="playerfake"
|
||||
muted
|
||||
autoplay
|
||||
style="display: none"
|
||||
:src="vidWrs"
|
||||
/>
|
||||
<v-progress-linear
|
||||
query
|
||||
active
|
||||
background-color="primary"
|
||||
style="width: 100%; background: #ffffff22"
|
||||
background-opacity="0.5"
|
||||
background-color="primary"
|
||||
:buffer-value="buffered"
|
||||
:value="percent"
|
||||
color="primary"
|
||||
height="3"
|
||||
query
|
||||
:value="percentage"
|
||||
:style="
|
||||
fullscreen
|
||||
? 'width: calc(100% - 2rem); left: 1rem; position: absolute; bottom: 3rem;'
|
||||
: 'width: 100%'
|
||||
"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Scrubber -->
|
||||
<v-slider
|
||||
id="scrubber"
|
||||
hide-details
|
||||
height="2"
|
||||
dense
|
||||
track-color="transparent"
|
||||
:class="!controls && !fullscreen && !scrubbing ? 'invisible' : ''"
|
||||
style="position: absolute; z-index: 2"
|
||||
:style="
|
||||
fullscreen
|
||||
? 'width: calc(100% - 2rem); left: 1rem; bottom: 3rem;'
|
||||
: 'width: calc(100% - 0.8rem); left: 0.4rem; bottom: 0;'
|
||||
"
|
||||
:thumb-size="0"
|
||||
:max="duration"
|
||||
:value="progress"
|
||||
@start="(scrubbing = true), $emit('seeking')"
|
||||
@end="(scrubbing = false), $emit('seeking')"
|
||||
@change="scrub($event)"
|
||||
@input="scrubbing ? seek($event) : null"
|
||||
>
|
||||
<template #thumb-label="{ value }">
|
||||
<div style="transform: translateY(-50%)">
|
||||
<canvas
|
||||
ref="preview"
|
||||
class="white"
|
||||
:width="video.clientWidth / 3"
|
||||
:height="video.clientHeight / 3"
|
||||
style="border: 2px solid white"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||
: '0',
|
||||
}"
|
||||
></canvas>
|
||||
<div class="text-center pb-4" style="font-size: 0.8rem">
|
||||
<b>{{ $vuetube.humanTime(value) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
props: ["sources", "video", "controls", "fullscreen"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
percentage: 0,
|
||||
buffered: 0
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.video.ontimeupdate = () => {
|
||||
this.percentage = (this.video.currentTime / this.video.duration) * 100;
|
||||
scrubbing: false,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
buffered: 0,
|
||||
duration: 0,
|
||||
vidSrc: "",
|
||||
vidWrs: "",
|
||||
};
|
||||
this.video.addEventListener("progress", () => {
|
||||
this.buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
|
||||
},
|
||||
mounted() {
|
||||
console.log("sources", this.sources);
|
||||
this.vidSrc = this.sources[this.sources.length - 1].url;
|
||||
this.vidWrs = this.sources[1].url;
|
||||
let vid = this.video;
|
||||
vid.addEventListener("loadeddata", (e) => {
|
||||
// console.log("%c loadeddata", "color: #00ff00");
|
||||
console.log(e);
|
||||
//Video should now be loaded but we can add a second check
|
||||
if (vid.readyState >= 3) {
|
||||
vid.ontimeupdate = () => {
|
||||
// console.log("%c timeupdate", "color: #aaaaff");
|
||||
this.duration = vid.duration;
|
||||
if (!this.scrubbing) this.progress = vid.currentTime;
|
||||
this.percent = (vid.currentTime / vid.duration) * 100;
|
||||
};
|
||||
vid.onprogress = () => {
|
||||
// console.log("%c progress", "color: #ff00ff");
|
||||
this.buffered = (vid.buffered.end(0) / vid.duration) * 100;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// TODO: better scrubbing preview
|
||||
loadVideoFrames() {
|
||||
// Exit loop if desired number of frames have been extracted
|
||||
if (this.frames.length >= frameCount) {
|
||||
this.visibleFrame = 0;
|
||||
|
||||
}
|
||||
// Append all canvases to container div
|
||||
this.frames.forEach((frame) => {
|
||||
this.frameContainerElement.appendChild(frame);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If extraction hasn’t started, set desired time for first frame
|
||||
if (this.frames.length === 0) {
|
||||
this.requestedTime = 0;
|
||||
} else {
|
||||
this.requestedTime = this.requestedTime + this.frameTimestep;
|
||||
}
|
||||
|
||||
// Send seek request to video player for the next frame.
|
||||
this.videoElement.currentTime = this.requestedTime;
|
||||
},
|
||||
extractFrame(videoWidth, videoHeight) {
|
||||
// Create DOM canvas object
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.className = "video-scrubber-frame";
|
||||
canvas.height = videoHeight;
|
||||
canvas.width = videoWidth;
|
||||
|
||||
// Copy current frame to canvas
|
||||
var context = canvas.getContext("2d");
|
||||
context.drawImage(this.videoElement, 0, 0, videoWidth, videoHeight);
|
||||
this.frames.push(canvas);
|
||||
|
||||
// Load the next frame
|
||||
loadVideoFrames();
|
||||
},
|
||||
prefetch_file(url, fetched_callback, progress_callback, error_callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
if (xhr.status === 200) {
|
||||
var URL = window.URL || window.webkitURL;
|
||||
var blob_url = URL.createObjectURL(xhr.response);
|
||||
fetched_callback(blob_url);
|
||||
} else {
|
||||
error_callback();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
var prev_pc = 0;
|
||||
xhr.addEventListener("progress", function (event) {
|
||||
if (event.lengthComputable) {
|
||||
var pc = Math.round((event.loaded / event.total) * 100);
|
||||
if (pc != prev_pc) {
|
||||
prev_pc = pc;
|
||||
progress_callback(pc);
|
||||
}
|
||||
}
|
||||
});
|
||||
xhr.send();
|
||||
},
|
||||
async extractFramesFromVideo(videoUrl, fps = 25) {
|
||||
// fully download it first (no buffering):
|
||||
console.log(videoUrl);
|
||||
console.log(fps);
|
||||
let videoBlob = await fetch(videoUrl, {
|
||||
headers: { range: "bytes=0-567139" },
|
||||
}).then((r) => r.blob());
|
||||
console.log(videoBlob);
|
||||
let videoObjectUrl = URL.createObjectURL(videoBlob);
|
||||
let video = document.createElement("video");
|
||||
|
||||
let seekResolve;
|
||||
video.addEventListener("seeked", async function () {
|
||||
if (seekResolve) seekResolve();
|
||||
});
|
||||
|
||||
video.src = videoObjectUrl;
|
||||
|
||||
// workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
|
||||
while (
|
||||
(video.duration === Infinity || isNaN(video.duration)) &&
|
||||
video.readyState < 2
|
||||
) {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
video.currentTime = 10000000 * Math.random();
|
||||
}
|
||||
let duration = video.duration;
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
let context = canvas.getContext("2d");
|
||||
let [w, h] = [video.videoWidth, video.videoHeight];
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
|
||||
let interval = 1;
|
||||
let currentTime = 0;
|
||||
|
||||
while (currentTime < duration) {
|
||||
video.currentTime = currentTime;
|
||||
await new Promise((r) => (seekResolve = r));
|
||||
|
||||
context.drawImage(video, 0, 0, w, h);
|
||||
let base64ImageData = canvas.toDataURL();
|
||||
console.log(base64ImageData);
|
||||
this.frames.push(base64ImageData);
|
||||
|
||||
currentTime += interval;
|
||||
}
|
||||
console.log("%c frames", "color: #00ff00");
|
||||
console.log(this.frames);
|
||||
},
|
||||
// TODO: scrubbing preview end
|
||||
seek(e) {
|
||||
// console.log(`scrubbing ${e}`);
|
||||
let vid = this.$refs.playerfake;
|
||||
let canvas = this.$refs.preview;
|
||||
this.$refs.playerfake.currentTime = e;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(
|
||||
vid,
|
||||
0,
|
||||
0,
|
||||
this.video.clientWidth / 3,
|
||||
this.video.clientHeight / 3
|
||||
);
|
||||
},
|
||||
scrub(e) {
|
||||
this.video.currentTime = e;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
64
NUXT/components/Player/speed.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-bottom-sheet
|
||||
v-model="sheet"
|
||||
:attach="$parent.$refs.vidcontainer"
|
||||
scrollable
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
style="position: absolute; bottom: 0.25rem; right: 6rem"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
{{ video.playbackRate }}X
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card
|
||||
v-touch="{
|
||||
down: () => (sheet = false),
|
||||
}"
|
||||
class="background"
|
||||
>
|
||||
<v-subheader>Playback Speed</v-subheader>
|
||||
<v-card-text style="height: 50vh" class="pa-0">
|
||||
<v-list-item
|
||||
v-for="sped in speeds"
|
||||
:key="sped"
|
||||
@click="(sheet = false), (video.playbackRate = sped)"
|
||||
>
|
||||
<!-- // TODO: save playbackRate to localStorage and manage via store/video/index.js -->
|
||||
<v-list-item-avatar>
|
||||
<v-icon
|
||||
:color="
|
||||
video.playbackRate === sped
|
||||
? 'primary'
|
||||
: $vuetify.theme.dark
|
||||
? 'background lighten-2'
|
||||
: 'background darken-2'
|
||||
"
|
||||
v-text="
|
||||
video.playbackRate === sped ? 'mdi-check' : 'mdi-speedometer'
|
||||
"
|
||||
></v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>{{ sped }}X</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-bottom-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
data: () => ({
|
||||
sheet: false,
|
||||
speeds: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 8, 16],
|
||||
}),
|
||||
};
|
||||
</script>
|
32
NUXT/components/Player/watchtime.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div
|
||||
style="
|
||||
color: #fff;
|
||||
left: 1rem;
|
||||
bottom: 1rem;
|
||||
font-size: 0.75rem;
|
||||
position: absolute;
|
||||
"
|
||||
>
|
||||
{{ watched }}
|
||||
<span style="color: #aaa"> / {{ duration }} </span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
data() {
|
||||
return {
|
||||
watched: 0,
|
||||
duration: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.video.addEventListener("timeupdate", () => {
|
||||
this.duration = this.$vuetube.humanTime(this.video.duration);
|
||||
this.watched = this.$vuetube.humanTime(this.video.currentTime);
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -14,7 +14,9 @@
|
|||
</v-list-item>
|
||||
<div
|
||||
v-if="
|
||||
render.separatorDetails && render.separatorDetails.hasBottomSeparator
|
||||
render.separatorDetails &&
|
||||
render.separatorDetails.hasBottomSeparator &&
|
||||
!($store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0)
|
||||
"
|
||||
class="separator-bottom background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
<template>
|
||||
<div class="fill-width">
|
||||
<h4 v-if="render.headerRenderer" class="font-weight-bold shelf-header">
|
||||
<h4
|
||||
v-if="render.headerRenderer"
|
||||
class="font-weight-bold shelf-header"
|
||||
:style="{
|
||||
marginLeft:
|
||||
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||
? '1rem'
|
||||
: '0',
|
||||
}"
|
||||
>
|
||||
{{
|
||||
render.headerRenderer.elementRenderer.newElement.type.componentType
|
||||
.model.shelfHeaderModel.shelfHeaderData.title
|
||||
}}
|
||||
</h4>
|
||||
<v-list-item class="pa-0 min-height-0">
|
||||
<div class="pa-0 min-height-0">
|
||||
<component
|
||||
v-if="render.content && getComponents()[Object.keys(render.content)[0]]"
|
||||
:is="Object.keys(render.content)[0]"
|
||||
v-if="render.content && getComponents()[Object.keys(render.content)[0]]"
|
||||
:render="render.content[Object.keys(render.content)[0]]"
|
||||
></component
|
||||
></v-list-item>
|
||||
<!-- <div
|
||||
></component>
|
||||
</div>
|
||||
<div
|
||||
v-if="render.separator && render.separator.hasBottomSeparator"
|
||||
class="separator-bottom background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
:style="{ height: render.separator.height + 'px' }"
|
||||
></div> -->
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.shelf-header {
|
||||
width: 100%; /* Prevent Loading Weirdness */
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import verticalListRenderer from "~/components/ListRenderers/verticalListRenderer.vue";
|
||||
import horizontalListRenderer from "~/components/ListRenderers/horizontalListRenderer.vue";
|
||||
|
@ -47,3 +49,10 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shelf-header {
|
||||
width: 100%; /* Prevent Loading Weirdness */
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<template>
|
||||
<v-card
|
||||
class="entry videoRenderer background"
|
||||
class="entry videoRenderer background overflow-hidden"
|
||||
:to="`/watch?v=${vidId}`"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2.5}rem`,
|
||||
borderRadius: roundThumb ? `${roundTweak / 2}rem` : '0',
|
||||
margin:
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? '0 1rem 1rem 1rem'
|
||||
: '0 0 0.25rem 0',
|
||||
roundThumb && roundTweak > 0 ? '0 1rem 1rem 1rem' : '0 0 0.25rem 0',
|
||||
}"
|
||||
flat
|
||||
>
|
||||
|
@ -16,7 +14,7 @@
|
|||
:aspect-ratio="16 / 9"
|
||||
:src="$youtube.getThumbnail(vidId, 'max', thumbnails)"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2.5}rem`,
|
||||
borderRadius: roundThumb ? `${roundTweak / 12}rem` : '0',
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
|
@ -27,8 +25,27 @@
|
|||
v-text="thumbnailOverlayText"
|
||||
/>
|
||||
</div>
|
||||
<div id="details">
|
||||
<a :href="channelUrl" class="avatar-link pl-2 pt-2">
|
||||
<div
|
||||
id="details"
|
||||
class="background mt-1"
|
||||
:class="
|
||||
roundThumb && roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'lighten-1'
|
||||
: 'darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: roundThumb ? `${roundTweak / 12}rem` : '0',
|
||||
}"
|
||||
>
|
||||
<a
|
||||
class="avatar-link pl-2 pt-2"
|
||||
@click.prevent="
|
||||
$store.dispatch('channel/fetchChannel', channelUrl),
|
||||
$router.push('/channel')
|
||||
"
|
||||
>
|
||||
<v-img class="avatar-thumbnail" :src="channelIcon" />
|
||||
</a>
|
||||
<v-card-text class="video-info pt-2" v-emoji>
|
||||
|
@ -42,8 +59,8 @@
|
|||
</span>
|
||||
|
||||
<div
|
||||
class="background--text text--lighten-5 caption"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
class="background--text caption"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-5' : 'text--darken-4'"
|
||||
v-text="bottomText"
|
||||
/>
|
||||
</v-card-text>
|
||||
|
@ -51,6 +68,52 @@
|
|||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
vidId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
thumbnails: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
channelUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
channelIcon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
titles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
bottomText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
thumbnailOverlayText: {
|
||||
type: String,
|
||||
},
|
||||
thumbnailOverlayStyle: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
roundTweak() {
|
||||
return this.$store.state.tweaks.roundTweak;
|
||||
},
|
||||
roundThumb() {
|
||||
return this.$store.state.tweaks.roundThumb;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.entry {
|
||||
width: 100%; /* Prevent Loading Weirdness */
|
||||
|
@ -59,8 +122,9 @@
|
|||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
border-radius: 5px;
|
||||
border-radius: 4px;
|
||||
padding: 0px 4px 0px 4px;
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
|
||||
.videoRuntimeFloat.style-DEFAULT {
|
||||
|
@ -119,46 +183,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
roundTweak() {
|
||||
return this.$store.state.tweaks.roundTweak;
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
vidId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
thumbnails: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
channelUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
channelIcon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
titles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
bottomText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
thumbnailOverlayText: {
|
||||
type: String,
|
||||
},
|
||||
thumbnailOverlayStyle: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,23 +1,39 @@
|
|||
<template>
|
||||
<!-- hide-on-scroll -->
|
||||
<v-bottom-navigation
|
||||
v-model="tabSelection"
|
||||
shift
|
||||
class="bottomNav py-4 background"
|
||||
:style="
|
||||
$vuetify.theme.dark
|
||||
? 'border-top: 1px solid var(--v-background-lighten1) !important;'
|
||||
: 'border-top: 1px solid var(--v-background-darken1) !important;'
|
||||
"
|
||||
>
|
||||
<v-btn
|
||||
v-for="(item, i) in tabs"
|
||||
:key="i"
|
||||
v-ripple="false"
|
||||
class="navButton"
|
||||
:to="item.link"
|
||||
plain
|
||||
<div class="bottomNav background">
|
||||
<v-divider v-if="!$store.state.tweaks.roundTweak" />
|
||||
<v-bottom-navigation
|
||||
v-model="tabSelection"
|
||||
style="padding: 0 !important; box-shadow: none !important"
|
||||
class="transparent"
|
||||
shift
|
||||
>
|
||||
<v-btn
|
||||
v-for="(item, i) in tabs"
|
||||
:key="i"
|
||||
v-ripple="false"
|
||||
class="navButton"
|
||||
:to="item.link"
|
||||
plain
|
||||
>
|
||||
<span v-text="item.name" />
|
||||
<v-icon
|
||||
:color="
|
||||
tabSelection == i
|
||||
? 'primary'
|
||||
: $vuetify.theme.dark
|
||||
? 'background lighten-4'
|
||||
: 'background darken-4'
|
||||
"
|
||||
:class="
|
||||
tabSelection == i
|
||||
? $vuetify.theme.dark
|
||||
? 'tab primary darken-4'
|
||||
: 'tab primary lighten-4'
|
||||
: ''
|
||||
"
|
||||
v-text="item.icon"
|
||||
/>
|
||||
<!--
|
||||
<span v-text="item.name" />
|
||||
<v-icon
|
||||
:color="
|
||||
|
@ -35,18 +51,20 @@
|
|||
: ''
|
||||
"
|
||||
v-text="item.icon"
|
||||
/>
|
||||
<!--
|
||||
Add the following to 'v-text- above to make the icons outlined unless active
|
||||
/> -->
|
||||
<!-- Add the following to 'v-text- above to make the icons outlined unless active
|
||||
+ (tabSelection == i ? '' : '-outline')
|
||||
|
||||
|
||||
-->
|
||||
</v-btn>
|
||||
<!-- <v-btn text class="navButton mr-2 fill-height" color="white" @click="searchBtn()"
|
||||
><v-icon>mdi-magnify</v-icon></v-btn
|
||||
> -->
|
||||
</v-bottom-navigation>
|
||||
</v-btn>
|
||||
<!-- <v-btn
|
||||
text
|
||||
class="navButton mr-2 fill-height"
|
||||
color="white"
|
||||
@click="searchBtn()"
|
||||
><v-icon>mdi-magnify</v-icon></v-btn
|
||||
> -->
|
||||
</v-bottom-navigation>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -56,31 +74,37 @@ export default {
|
|||
tabSelection: 0,
|
||||
tabs: [
|
||||
// TODO: pull from Vuex & localStorage for customizations
|
||||
{ name: "Home", icon: "mdi-home", link: "/home" },
|
||||
{ name: "...", icon: "mdi-home", link: "/home" },
|
||||
//{ name: "Shorts", icon: "mdi-lightning-bolt", link: "/shorts" },
|
||||
//{ name: "Upload", icon: "mdi-plus", link: "/upload" },
|
||||
{
|
||||
name: "Subscriptions",
|
||||
name: "...",
|
||||
icon: "mdi-youtube-subscription",
|
||||
link: "/subscriptions",
|
||||
},
|
||||
{ name: "Library", icon: "mdi-view-list", link: "/library" },
|
||||
{ name: "...", icon: "mdi-view-list", link: "/library" },
|
||||
// { name: "Settings", icon: "mdi-menu", link: "/settings" },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.tabs[0].name = this.$lang("global").home;
|
||||
this.tabs[1].name = this.$lang("global").subscriptions;
|
||||
this.tabs[2].name = this.$lang("global").library;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bottomNav {
|
||||
/* box-shadow: inset 0 1rem 10rem var(--v-background-base) !important; */
|
||||
/* box-shadow: inset 0 0 10rem var(--v-background-base) !important; */
|
||||
height: calc(4rem + env(safe-area-inset-bottom)) !important;
|
||||
padding-bottom: env(safe-area-inset-bottom) !important;
|
||||
box-shadow: none !important;
|
||||
/* ios gesture nav */
|
||||
bottom: env(safe-area-inset-bottom) !important;
|
||||
height: 4rem !important;
|
||||
padding: 0 !important;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
.navButton {
|
||||
width: 25vw !important;
|
||||
|
|
71
NUXT/components/communityCard.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<v-card
|
||||
v-ripple
|
||||
flat
|
||||
class="background"
|
||||
:class="
|
||||
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'lighten-1'
|
||||
: 'darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundThumb
|
||||
? `${$store.state.tweaks.roundTweak / 2}rem`
|
||||
: '0',
|
||||
margin:
|
||||
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||
? '0 1rem 1rem 1rem'
|
||||
: '0 0 0.25rem 0',
|
||||
}"
|
||||
>
|
||||
<div class="d-flex flex-row pa-4">
|
||||
<v-avatar size="50" color="primary" />
|
||||
<div class="d-flex flex-column my-auto pl-4">
|
||||
<b>Work in progress</b>
|
||||
<div
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
69 years ago
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
elevation="0"
|
||||
style="width: 50px !important; height: 50px !important"
|
||||
>
|
||||
<v-icon>mdi-share-outline</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<p class="px-4" v-emoji>Blurb Blurb Text Goes Here ...</p>
|
||||
<v-img
|
||||
contain
|
||||
class="background my-4"
|
||||
:class="$vuetify.theme.dark ? 'lighten-2' : 'darken-2'"
|
||||
style="max-height: 15rem"
|
||||
:style="{
|
||||
borderRadius: `${$store.state.tweaks.roundTweak / 4}rem`,
|
||||
marginLeft:
|
||||
!($store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak) >
|
||||
0
|
||||
? '1rem'
|
||||
: '0',
|
||||
marginRight:
|
||||
!($store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak) >
|
||||
0
|
||||
? '1rem'
|
||||
: '0',
|
||||
}"
|
||||
src="/dev.svg"
|
||||
/>
|
||||
<div class="d-flex flex-row px-4 pb-4">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
<b class="mx-2">123</b>
|
||||
<v-icon class="ml-2">mdi-thumb-down-outline</v-icon>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<v-card class="dialog-base">
|
||||
<v-expand-transition>
|
||||
<slot name="reveal"></slot>
|
||||
</v-expand-transition>
|
||||
<div class="toolbar-container">
|
||||
<v-card flat class="dialog-base background">
|
||||
<div
|
||||
class="toolbar-container d-flex flex-column background"
|
||||
style="flex-direction: column !important"
|
||||
>
|
||||
<v-toolbar color="background" flat>
|
||||
<slot name="header"></slot>
|
||||
</v-toolbar>
|
||||
|
@ -12,6 +12,9 @@
|
|||
<div class="dialog-body background">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<v-expand-transition>
|
||||
<slot name="reveal"></slot>
|
||||
</v-expand-transition>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
|
80
NUXT/components/playlistCard.vue
Normal file
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<v-card
|
||||
v-ripple
|
||||
class="background d-flex flex-row overflow-hidden mb-4 mx-4"
|
||||
style="height: 6rem !important"
|
||||
:class="
|
||||
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'lighten-1'
|
||||
: 'darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundThumb
|
||||
? `${$store.state.tweaks.roundTweak / 2}rem`
|
||||
: '0',
|
||||
}"
|
||||
flat
|
||||
>
|
||||
<v-img
|
||||
contain
|
||||
src="/dev.svg"
|
||||
class="background"
|
||||
style="position: relative; max-width: 8rem !important"
|
||||
:class="$vuetify.theme.dark ? 'lighten-3' : 'darken-3'"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundThumb
|
||||
? `${$store.state.tweaks.roundTweak / 2}rem`
|
||||
: '0',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column justify-center align-center"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: linear-gradient(var(--v-background-base) -1000%, #00000000 1000%);
|
||||
"
|
||||
>
|
||||
<div>420</div>
|
||||
<v-icon>mdi-playlist-play</v-icon>
|
||||
</div>
|
||||
</v-img>
|
||||
<div class="pa-4" v-emoji style="font-size: 0.75rem !important">
|
||||
<b>Work in Progress</b>
|
||||
|
||||
<div
|
||||
class="background--text caption mt-2"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
Bottom Text <br />
|
||||
420 videos
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<div class="d-flex flex-column">
|
||||
<v-btn
|
||||
text
|
||||
tile
|
||||
elevation="0"
|
||||
class="flex-grow-1"
|
||||
style="width: 2rem !important"
|
||||
>
|
||||
<v-icon>mdi-share-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
text
|
||||
tile
|
||||
elevation="0"
|
||||
class="flex-grow-1"
|
||||
style="width: 2rem !important"
|
||||
>
|
||||
<v-icon>mdi-playlist-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
221
NUXT/components/ryd.vue
Normal file
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<div class="ryd">
|
||||
<div class="ryd__container">
|
||||
<div class="ryd__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
apiUrl: "https://returnyoutubedislikeapi.com",
|
||||
storedData: {
|
||||
likes: 0,
|
||||
dislikes: 0,
|
||||
previousState: "NEUTRAL_STATE",
|
||||
},
|
||||
}),
|
||||
methods: {
|
||||
async solvePuzzle(puzzle) {
|
||||
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
let buffer = new ArrayBuffer(20);
|
||||
let uInt8View = new Uint8Array(buffer);
|
||||
let uInt32View = new Uint32Array(buffer);
|
||||
let maxCount = Math.pow(2, puzzle.difficulty) * 3;
|
||||
for (let i = 4; i < 20; i++) {
|
||||
uInt8View[i] = challenge[i - 4];
|
||||
}
|
||||
|
||||
for (let i = 0; i < maxCount; i++) {
|
||||
uInt32View[0] = i;
|
||||
let hash = await crypto.subtle.digest("SHA-512", buffer);
|
||||
let hashUint8 = new Uint8Array(hash);
|
||||
if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) {
|
||||
return {
|
||||
solution: btoa(
|
||||
String.fromCharCode.apply(null, uInt8View.slice(0, 4))
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
},
|
||||
generateUserID(length = 36) {
|
||||
const charset =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
if (crypto && crypto.getRandomValues) {
|
||||
const values = new Uint32Array(length);
|
||||
crypto.getRandomValues(values);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += charset[values[i] % charset.length];
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += charset[Math.floor(Math.random() * charset.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
storageChangeHandler(changes, area) {
|
||||
if (changes.disableVoteSubmission !== undefined) {
|
||||
handleDisableVoteSubmissionChangeEvent(
|
||||
changes.disableVoteSubmission.newValue
|
||||
);
|
||||
}
|
||||
if (changes.coloredThumbs !== undefined) {
|
||||
handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);
|
||||
}
|
||||
if (changes.coloredBar !== undefined) {
|
||||
handleColoredBarChangeEvent(changes.coloredBar.newValue);
|
||||
}
|
||||
if (changes.colorTheme !== undefined) {
|
||||
handleColorThemeChangeEvent(changes.colorTheme.newValue);
|
||||
}
|
||||
if (changes.numberDisplayRoundDown !== undefined) {
|
||||
handleNumberDisplayRoundDownChangeEvent(
|
||||
changes.numberDisplayRoundDown.newValue
|
||||
);
|
||||
}
|
||||
if (changes.numberDisplayFormat !== undefined) {
|
||||
handleNumberDisplayFormatChangeEvent(
|
||||
changes.numberDisplayFormat.newValue
|
||||
);
|
||||
}
|
||||
if (changes.numberDisplayReformatLikes !== undefined) {
|
||||
handleNumberDisplayReformatLikesChangeEvent(
|
||||
changes.numberDisplayReformatLikes.newValue
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async sendVote(videoId, vote) {
|
||||
api.storage.sync.get(null, async (storageResult) => {
|
||||
if (!storageResult.userId || !storageResult.registrationConfirmed) {
|
||||
await this.register();
|
||||
}
|
||||
let voteResponse = await fetch(`${apiUrl}/interact/vote`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: storageResult.userId,
|
||||
videoId,
|
||||
value: vote,
|
||||
}),
|
||||
});
|
||||
|
||||
if (voteResponse.status == 401) {
|
||||
await this.register();
|
||||
await this.sendVote(videoId, vote);
|
||||
return;
|
||||
}
|
||||
const voteResponseJson = await voteResponse.json();
|
||||
const solvedPuzzle = await this.solvePuzzle(voteResponseJson);
|
||||
if (!solvedPuzzle.solution) {
|
||||
await this.sendVote(videoId, vote);
|
||||
return;
|
||||
}
|
||||
|
||||
await fetch(`${apiUrl}/interact/confirmVote`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...solvedPuzzle,
|
||||
userId: storageResult.userId,
|
||||
videoId,
|
||||
}),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async register() {
|
||||
const userId = this.generateUserID();
|
||||
api.storage.sync.set({ userId });
|
||||
const registrationResponse = await fetch(
|
||||
`${apiUrl}/puzzle/registration?userId=${userId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
}
|
||||
).then((response) => response.json());
|
||||
const solvedPuzzle = await this.solvePuzzle(registrationResponse);
|
||||
if (!solvedPuzzle.solution) {
|
||||
await this.register();
|
||||
return;
|
||||
}
|
||||
const result = await fetch(
|
||||
`${apiUrl}/puzzle/registration?userId=${userId}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(solvedPuzzle),
|
||||
}
|
||||
).then((response) => response.json());
|
||||
if (result === true) {
|
||||
return api.storage.sync.set({ registrationConfirmed: true });
|
||||
}
|
||||
},
|
||||
like() {
|
||||
if (checkForSignInButton() === false) {
|
||||
if (this.storedData.previousState === "DISLIKED_STATE") {
|
||||
sendVote(1);
|
||||
if (this.storedData.dislikes > 0) this.storedData.dislikes--;
|
||||
this.storedData.likes++;
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
setDislikes(numberFormat(this.storedData.dislikes));
|
||||
this.storedData.previousState = "LIKED_STATE";
|
||||
} else if (this.storedData.previousState === "NEUTRAL_STATE") {
|
||||
sendVote(1);
|
||||
this.storedData.likes++;
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
this.storedData.previousState = "LIKED_STATE";
|
||||
} else if ((this.storedData.previousState = "LIKED_STATE")) {
|
||||
sendVote(0);
|
||||
if (this.storedData.likes > 0) this.storedData.likes--;
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
this.storedData.previousState = "NEUTRAL_STATE";
|
||||
}
|
||||
}
|
||||
},
|
||||
dislike() {
|
||||
if (checkForSignInButton() == false) {
|
||||
if (this.storedData.previousState === "NEUTRAL_STATE") {
|
||||
sendVote(-1);
|
||||
this.storedData.dislikes++;
|
||||
setDislikes(numberFormat(this.storedData.dislikes));
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
this.storedData.previousState = "DISLIKED_STATE";
|
||||
} else if (this.storedData.previousState === "DISLIKED_STATE") {
|
||||
sendVote(0);
|
||||
if (this.storedData.dislikes > 0) this.storedData.dislikes--;
|
||||
setDislikes(numberFormat(this.storedData.dislikes));
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
this.storedData.previousState = "NEUTRAL_STATE";
|
||||
} else if (this.storedData.previousState === "LIKED_STATE") {
|
||||
sendVote(-1);
|
||||
if (this.storedData.likes > 0) this.storedData.likes--;
|
||||
this.storedData.dislikes++;
|
||||
setDislikes(numberFormat(this.storedData.dislikes));
|
||||
createRateBar(this.storedData.likes, this.storedData.dislikes);
|
||||
this.storedData.previousState = "DISLIKED_STATE";
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<v-card
|
||||
scroll-off-screen
|
||||
style="height: 4rem !important; display: flex"
|
||||
class="rounded-0 pa-3 topNav background"
|
||||
>
|
||||
<!-- :style="
|
||||
$vuetify.theme.dark
|
||||
? 'border-bottom: 1px solid var(--v-background-lighten1) !important;'
|
||||
: 'border-bottom: 1px solid var(--v-background-darken1) !important;'
|
||||
" -->
|
||||
<h3 v-show="!search" class="my-auto ml-4" v-text="page" />
|
||||
<v-card style="display: flex" class="rounded-0 pa-3 topNav background">
|
||||
<!-- opacity with vue 😉 -->
|
||||
<!-- :style="{ background: $vuetify.theme.currentTheme.primary + '55' }" -->
|
||||
<h3
|
||||
v-show="!search"
|
||||
class="my-auto ml-4"
|
||||
v-text="
|
||||
$route.path.includes('channel') ? $store.state.channel.title : page
|
||||
"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
v-if="search"
|
||||
|
@ -26,8 +25,9 @@
|
|||
solo
|
||||
dense
|
||||
flat
|
||||
autofocus
|
||||
label="Search"
|
||||
style="margin-top: 1px"
|
||||
style="margin-top: 7px"
|
||||
:background-color="
|
||||
$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'
|
||||
"
|
||||
|
@ -49,6 +49,7 @@
|
|||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="$route.name !== 'settings' && !$route.path.includes('/mods')"
|
||||
icon
|
||||
tile
|
||||
class="ml-3 my-auto fill-height"
|
||||
|
@ -82,7 +83,7 @@ export default {
|
|||
default: "Home",
|
||||
},
|
||||
},
|
||||
events: ["searchBtn", "textChanged", "closeSearch"],
|
||||
events: ["searchBtn", "textChanged", "closeSearch", "scrollToTop"],
|
||||
data: () => ({
|
||||
text: "",
|
||||
}),
|
||||
|
@ -113,12 +114,15 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
.topNav {
|
||||
/* box-shadow: inset 0 1rem 10rem var(--v-background-base) !important; */
|
||||
/* opacity with hex, wow 😉 */
|
||||
/* background: linear-gradient(var(--v-background-base) -1000%, #00000000 1000%); */
|
||||
/* box-shadow: inset 0 0 5rem var(--v-background-base) !important; */
|
||||
height: calc(4rem + env(safe-area-inset-top)) !important;
|
||||
padding-top: env(safe-area-inset-top) !important;
|
||||
box-shadow: none !important;
|
||||
/* ios notch */
|
||||
top: env(safe-area-inset-top) !important;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.topNavSearch {
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
:search="search"
|
||||
:page="page"
|
||||
style="z-index: 696969"
|
||||
@close-search="search = !search"
|
||||
@search-btn="searchBtn"
|
||||
@text-changed="textChanged"
|
||||
@close-search="search = !search"
|
||||
@scroll-to-top="$refs.pgscroll.scrollTop = 0"
|
||||
/>
|
||||
|
||||
<!-- channel-tabs -->
|
||||
<v-tabs
|
||||
v-if="$route.path.includes('/channel')"
|
||||
v-if="$route.path.includes('/channel') && !search"
|
||||
mobile-breakpoint="0"
|
||||
style="
|
||||
position: fixed;
|
||||
|
@ -24,6 +26,7 @@
|
|||
v-for="tab in channelTabs"
|
||||
:key="tab.name"
|
||||
:to="tab.to"
|
||||
exact
|
||||
:v-ripple="false"
|
||||
>
|
||||
{{ tab.name }}
|
||||
|
@ -31,9 +34,15 @@
|
|||
</v-tabs>
|
||||
|
||||
<div
|
||||
style="height: 100%; padding-bottom: 4rem"
|
||||
style="
|
||||
height: 100%;
|
||||
padding-bottom: calc(4rem + env(safe-area-inset-bottom));
|
||||
"
|
||||
:style="{
|
||||
marginTop: $route.path.includes('/channel') ? '7rem' : '4rem',
|
||||
paddingTop:
|
||||
$route.path.includes('/channel') && !search
|
||||
? 'calc(7rem + env(safe-area-inset-top))'
|
||||
: 'calc(4rem + env(safe-area-inset-top))',
|
||||
}"
|
||||
>
|
||||
<div v-show="!search">
|
||||
|
@ -53,21 +62,17 @@
|
|||
>
|
||||
<div class="scroll-y" style="height: 100%">
|
||||
<div v-if="search" style="min-width: 180px">
|
||||
<v-list-item
|
||||
v-for="(item, index) in response"
|
||||
:key="index"
|
||||
class="px-0"
|
||||
>
|
||||
<v-list-item v-for="item in response" :key="item[0]" class="px-0">
|
||||
<v-btn
|
||||
v-emoji
|
||||
text
|
||||
tile
|
||||
dense
|
||||
class="searchButton text-left text-none"
|
||||
@click="youtubeSearch(item)"
|
||||
v-emoji
|
||||
>
|
||||
<v-icon class="mr-5">mdi-magnify</v-icon>
|
||||
{{ item[0] || item.text }}
|
||||
{{ item[0] }}
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
</div>
|
||||
|
@ -154,28 +159,28 @@ export default {
|
|||
return;
|
||||
} // No text found, no point in calling API
|
||||
|
||||
//--- User Pastes Link, Direct Them To Video ---//
|
||||
const isLink = linkParser(text);
|
||||
if (isLink) {
|
||||
this.response = [
|
||||
{
|
||||
text: `Watch Video from ID: ${isLink.searchParams.get("v")}`,
|
||||
id: isLink.searchParams.get("v"),
|
||||
},
|
||||
];
|
||||
return;
|
||||
}
|
||||
//--- End User Pastes Link, Direct Them To Video ---//
|
||||
|
||||
//--- Auto Suggest ---//
|
||||
this.$youtube.autoComplete(text, (res) => {
|
||||
const data = res.replace(/^.*?\(/, "").replace(/\)$/, ""); //Format Response
|
||||
this.response = JSON.parse(data)[1];
|
||||
});
|
||||
|
||||
//--- User Pastes Link, Direct Them To Video ---//
|
||||
const isLink = linkParser(text);
|
||||
if (isLink) {
|
||||
this.response = [
|
||||
`Watch Video from ID: ${isLink.searchParams.get("v")}`,
|
||||
{ id: isLink.searchParams.get("v") },
|
||||
];
|
||||
return;
|
||||
}
|
||||
//--- End User Pastes Link, Direct Them To Video ---//
|
||||
},
|
||||
|
||||
youtubeSearch(item) {
|
||||
const link = item.id ? `/watch?v=${item.id}` : `/search?q=${item[0]}`;
|
||||
const link = item[1].id
|
||||
? `/watch?v=${item[1].id}` // link pasted
|
||||
: `/search?q=${item[0]}`; // regular suggestion
|
||||
this.$router.push(link);
|
||||
this.search = false;
|
||||
},
|
||||
|
@ -226,11 +231,39 @@ export default {
|
|||
.v-slide-group__next {
|
||||
display: none !important;
|
||||
}
|
||||
.v-input--selection-controls__input {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
.v-input__slot {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.v-slider {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border: 2px solid var(--v-primary-base) !important;
|
||||
}
|
||||
.glassy {
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
.transparent-lighten-1 {
|
||||
background: #ffffff22;
|
||||
}
|
||||
.transparent-darken-1 {
|
||||
background: #00000022;
|
||||
}
|
||||
.debug {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.v-card--reveal {
|
||||
bottom: 0;
|
||||
opacity: 1 !important;
|
||||
position: absolute !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollcontainer {
|
||||
overflow: hidden;
|
||||
|
@ -248,7 +281,9 @@ export default {
|
|||
html,
|
||||
body {
|
||||
background: var(--v-background-base);
|
||||
/* overflow-x: hidden; */
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
p,
|
||||
|
|
|
@ -17,6 +17,7 @@ export default {
|
|||
{ src: "~/plugins/vuetube", mode: "client" },
|
||||
{ src: "~/plugins/ryd", mode: "client" },
|
||||
{ src: "~/plugins/thirdPartyPluginLoader", mode: "client" },
|
||||
{ src: "~/plugins/language", mode: "client" },
|
||||
],
|
||||
generate: {
|
||||
dir: "../dist",
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column align-center">
|
||||
<v-img
|
||||
height="120"
|
||||
:src="banner"
|
||||
class="background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
></v-img>
|
||||
<v-avatar size="60" class="mt-2 primary">
|
||||
<img :src="avatar" />
|
||||
</v-avatar>
|
||||
<h2 class="mt-2">{{ title }}</h2>
|
||||
<v-btn
|
||||
:aria-label="subscribeAlt"
|
||||
class="mt-2"
|
||||
text
|
||||
color="primary"
|
||||
style="height: 1rem"
|
||||
>
|
||||
{{ subscribe }}
|
||||
</v-btn>
|
||||
<div style="font-size: 0.75rem" class="mt-2">
|
||||
{{ subscribers }} · {{ videos }}
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.75rem"
|
||||
class="background--text text-center px-4"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
{{ descriptionPreview }}
|
||||
<v-icon
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
channel: null,
|
||||
avatar: null,
|
||||
banner: null,
|
||||
title: null,
|
||||
subscribe: null,
|
||||
subscribeAlt: null,
|
||||
descriptionPreview: null,
|
||||
subscribers: null,
|
||||
videos: null,
|
||||
}),
|
||||
mounted() {
|
||||
console.log(
|
||||
"%c getChannel ",
|
||||
"color: black; font-weight: bold; background: #f00; padding: .5rem .25rem; border-radius: .25rem;"
|
||||
);
|
||||
this.$youtube
|
||||
.getChannel(`https://youtube.com/channel/${this.$route.params.id}`)
|
||||
.then((channel) => {
|
||||
this.channel = channel;
|
||||
console.log(channel);
|
||||
this.banner =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelBanner.image.sources[0].url;
|
||||
this.avatar =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.avatarData.avatar.image.sources[0].url;
|
||||
this.title =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.title;
|
||||
this.subscribe =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.subscribeButton.subscribeButtonContent.buttonText;
|
||||
this.subscribeAlt =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.subscribeButton.subscribeButtonContent.accessibilityText;
|
||||
this.descriptionPreview =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.descriptionPreview.description;
|
||||
this.subscribers =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.metadata.subscriberCountText;
|
||||
this.videos =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.metadata.videosCountText;
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,3 +1,23 @@
|
|||
<template>
|
||||
<div></div>
|
||||
<div class="px-6 py-3">
|
||||
<h3 class="my-2">Description</h3>
|
||||
<p>{{ $store.state.channel.descriptionPreview }}</p>
|
||||
<br />
|
||||
<br />
|
||||
<h3 class="my-2">Links</h3>
|
||||
<div v-ripple class="py-2 d-flex flex-row">
|
||||
<v-avatar tile size="20" color="primary"> </v-avatar>
|
||||
<span class="ml-4 primary--text">Social Media</span>
|
||||
</div>
|
||||
<br />
|
||||
<h3 class="my-2">More Info</h3>
|
||||
<div v-ripple class="py-2 d-flex flex-row">
|
||||
<v-icon size="24" color="primary">mdi-web</v-icon>
|
||||
<span class="ml-4">https://www.youtube.com/c/todo</span>
|
||||
</div>
|
||||
<div v-ripple class="py-2 d-flex flex-row">
|
||||
<v-icon size="24" color="primary">mdi-earth</v-icon>
|
||||
<span class="ml-4">United States</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<v-list-item
|
||||
v-for="(channel, index) in $store.state.channel.featuredChannels"
|
||||
:key="index"
|
||||
class="pa-0 min-height-0"
|
||||
>
|
||||
<compact-channel-renderer
|
||||
:video="channel.gridChannelRenderer"
|
||||
></compact-channel-renderer>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import compactChannelRenderer from "../../components/CompactRenderers/compactChannelRenderer.vue";
|
||||
|
||||
export default {
|
||||
components: { compactChannelRenderer },
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<community-card />
|
||||
<div
|
||||
v-if="
|
||||
!($store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0)
|
||||
"
|
||||
class="separator-bottom background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
style="height: 4px"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import communityCard from "../../components/communityCard.vue";
|
||||
export default {
|
||||
components: {
|
||||
communityCard,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
45
NUXT/pages/channel/index.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column align-center">
|
||||
<v-img
|
||||
height="120"
|
||||
:src="$store.state.channel.banner"
|
||||
class="background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
></v-img>
|
||||
<v-avatar
|
||||
size="60"
|
||||
class="mt-2 background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
>
|
||||
<img
|
||||
v-if="!$store.state.channel.loading"
|
||||
:src="$store.state.channel.avatar"
|
||||
/>
|
||||
<v-progress-circular v-else indeterminate color="primary" size="60" />
|
||||
</v-avatar>
|
||||
<h2 class="mt-2">{{ $store.state.channel.title }}</h2>
|
||||
<v-btn :aria-label="subscribeAlt" class="py-2" text color="primary">
|
||||
{{ $store.state.channel.subscribe }}
|
||||
</v-btn>
|
||||
<div v-if="!$store.state.channel.loading" style="font-size: 0.75rem">
|
||||
{{ $store.state.channel.subscribers }} ·
|
||||
{{ $store.state.channel.videosCount }}
|
||||
</div>
|
||||
<v-card
|
||||
v-if="!$store.state.channel.loading"
|
||||
flat
|
||||
to="/channel/about"
|
||||
style="font-size: 0.75rem; text-oveflow: ellipsis; overflow: hidden"
|
||||
class="background background--text text-center px-4"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
{{ $store.state.channel.descriptionPreview }}
|
||||
<v-icon
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
|
@ -1,3 +1,14 @@
|
|||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<playlist-card />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import playlistCard from "../../components/playlistCard.vue";
|
||||
export default {
|
||||
components: {
|
||||
playlistCard,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<!-- <compact-video-renderer :video="$store.state.channel.videoExample" /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CompactVideoRenderer from "../../components/CompactRenderers/compactVideoRenderer.vue";
|
||||
export default {
|
||||
components: { CompactVideoRenderer },
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -18,9 +18,11 @@ export default {
|
|||
layout: "empty",
|
||||
|
||||
data: () => ({
|
||||
progressMsg: "Connecting",
|
||||
progressMsg: "...",
|
||||
}),
|
||||
async mounted() {
|
||||
this.progressMsg = this.$lang("index").connecting;
|
||||
|
||||
this.$store.commit("tweaks/initTweaks");
|
||||
const theming = new Promise((resolve) =>
|
||||
// Set timeout is required for $vuetify.theme... dont ask me why -Front
|
||||
|
@ -48,17 +50,17 @@ export default {
|
|||
this.$vuetify.theme.currentTheme.background,
|
||||
this.$vuetify.theme.dark
|
||||
);
|
||||
// this.$vuetube.statusBar.setTransparent();
|
||||
|
||||
// this.$vuetube.navigationBar.setTransparent();
|
||||
// this.$vuetube.statusBar.setTransparent();
|
||||
resolve();
|
||||
}, 0)
|
||||
);
|
||||
|
||||
await theming;
|
||||
await this.$youtube.getAPI();
|
||||
this.progressMsg = "Launching";
|
||||
await this.$vuetube.launchBackHandling();
|
||||
this.progressMsg = "Navigating";
|
||||
this.progressMsg = this.$lang("index").launching;
|
||||
|
||||
this.$router.replace(`/${localStorage.getItem("startPage") || "home"}`); // Prevent user from navigating back to the splash screen
|
||||
},
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
<template>
|
||||
<center class="px-4">
|
||||
<v-img
|
||||
contain
|
||||
style="margin-top: 5em; max-width: 80%; max-height: 15em"
|
||||
src="/dev.svg"
|
||||
/>
|
||||
<h2
|
||||
class="background--text mt-4"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
<div>
|
||||
<h4
|
||||
class="ml-7 mb-2 background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-3' : 'text--darken-3'"
|
||||
>
|
||||
Page Under Construction
|
||||
</h2>
|
||||
<p
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
Please read the VueTube FAQ for more information.
|
||||
</p>
|
||||
</center>
|
||||
Local Playlists
|
||||
</h4>
|
||||
<playlist-card />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
import playlistCard from "../components/playlistCard.vue";
|
||||
export default {
|
||||
components: { playlistCard },
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
"
|
||||
:style="{ borderRadius: `${roundTweak / 2}rem` }"
|
||||
>
|
||||
<v-card-title>App Information</v-card-title>
|
||||
<v-card-title>{{ languagePack.mods.about.appinformation }}</v-card-title>
|
||||
<v-card-text>
|
||||
<h3>App Version</h3>
|
||||
<h3>{{ languagePack.mods.about.appversion }}</h3>
|
||||
{{ version.substring(0, 7) || "Unknown" }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
@ -39,19 +39,21 @@
|
|||
"
|
||||
:style="{ borderRadius: `${roundTweak / 2}rem` }"
|
||||
>
|
||||
<v-card-title>Device Information</v-card-title>
|
||||
<v-card-title>{{
|
||||
languagePack.mods.about.deviceinformation
|
||||
}}</v-card-title>
|
||||
<v-card-text>
|
||||
<h3>Platform</h3>
|
||||
<h3>{{ languagePack.mods.about.platform }}</h3>
|
||||
{{ deviceInfo.platform || "Unknown" }}<br />
|
||||
<h3>Operating System</h3>
|
||||
<h3>{{ languagePack.mods.about.os }}</h3>
|
||||
{{ deviceInfo.operatingSystem || "Unknown" }} ({{
|
||||
deviceInfo.osVersion || "Unknown"
|
||||
}})<br />
|
||||
<h3>Model</h3>
|
||||
<h3>{{ languagePack.mods.about.model }}</h3>
|
||||
{{ deviceInfo.model || "Unknown" }}<br />
|
||||
<h3>Manufacturer</h3>
|
||||
<h3>{{ languagePack.mods.about.manufacturer }}</h3>
|
||||
{{ deviceInfo.manufacturer || "Unknown" }}<br />
|
||||
<h3>Emulator</h3>
|
||||
<h3>{{ languagePack.mods.about.emulator }}</h3>
|
||||
{{ deviceInfo.isVirtual ? "yes" : "no" }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
@ -69,7 +71,7 @@
|
|||
@click="openExternal('https://github.com/Frontesque/VueTube')"
|
||||
>
|
||||
<v-icon x-large class="actionIcon">mdi-github</v-icon>
|
||||
Github
|
||||
{{ languagePack.mods.about.github }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
depressed
|
||||
|
@ -81,7 +83,7 @@
|
|||
@click="openExternal('https://discord.gg/7P8KJrdd5W')"
|
||||
>
|
||||
<v-icon x-large class="actionIcon">mdi-discord</v-icon>
|
||||
Discord
|
||||
{{ languagePack.mods.about.discord }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,6 +98,7 @@ export default {
|
|||
return {
|
||||
version: process.env.appVersion,
|
||||
deviceInfo: "",
|
||||
languagePack: { mods: { about: {} } },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -107,6 +110,8 @@ export default {
|
|||
async mounted() {
|
||||
const info = await Device.getInfo();
|
||||
this.deviceInfo = info;
|
||||
|
||||
this.languagePack = this.$lang();
|
||||
},
|
||||
methods: {
|
||||
async openExternal(url) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
class="card background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
flat
|
||||
:style="{borderRadius: `${roundTweak / 2}rem`}"
|
||||
:style="{ borderRadius: `${roundTweak / 2}rem` }"
|
||||
>
|
||||
<v-card-title>
|
||||
<v-chip v-if="item.error" outlined class="errorChip" color="error"
|
||||
|
|
|
@ -1,59 +1,62 @@
|
|||
<template>
|
||||
<div class="mainContainer pt-1">
|
||||
<v-card
|
||||
flat
|
||||
class="pb-5 background"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
:style="{borderRadius: `${roundTweak / 2}rem`}"
|
||||
>
|
||||
<v-card flat class="pb-5 background" :class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'" :style="{borderRadius: `${roundTweak / 2}rem`}">
|
||||
<v-card-title>Default Page</v-card-title>
|
||||
<v-card-text>
|
||||
<v-select
|
||||
v-model="page"
|
||||
background-color="background"
|
||||
:items="pages"
|
||||
label="Default Page"
|
||||
solo
|
||||
></v-select>
|
||||
<v-select v-model="page" background-color="background" :items="pages" label="Default Page" solo></v-select>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
export default {
|
||||
|
||||
computed: {
|
||||
roundTweak() {
|
||||
return this.$store.state.tweaks.roundTweak;
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
page: "home",
|
||||
pages: ["home", "subscriptions", "library"],
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
page: function (newVal) {
|
||||
localStorage.setItem("startPage", newVal);
|
||||
computed: {
|
||||
roundTweak() {
|
||||
return this.$store.state.tweaks.roundTweak;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.page = localStorage.getItem("startPage") || "home";
|
||||
},
|
||||
};
|
||||
data() {
|
||||
return {
|
||||
page: "home",
|
||||
pages: [],
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
page: function (newVal) {
|
||||
localStorage.setItem("startPage", newVal);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.page = localStorage.getItem("startPage") || "home";
|
||||
|
||||
const langPack = this.$lang('global');
|
||||
this.pages = [{
|
||||
value: "home",
|
||||
text: langPack.home
|
||||
}, {
|
||||
value: "subscriptions",
|
||||
text: langPack.subscriptions
|
||||
}, {
|
||||
value: "library",
|
||||
text: langPack.library
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
margin: 1em;
|
||||
}
|
||||
.v-card {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0 1em 1em 1em;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0 1em 1em 1em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<template>
|
||||
<client-only>
|
||||
<!-- !IMPORTANT: don't let autoformatter format this style to multiline or else it breaks ¯\_(ツ)_/¯ -->
|
||||
<div
|
||||
class="d-flex flex-column justify-end"
|
||||
style="min-height: calc(100vh - 8rem)"
|
||||
style="
|
||||
min-height: calc(100vh - 8rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)) !important;
|
||||
"
|
||||
>
|
||||
<!-- ----------------------------------------------Background Colors------------------------ -->
|
||||
<!-- ----Background Colors---- -->
|
||||
<v-radio-group v-model="$vuetify.theme.currentTheme.background">
|
||||
<div
|
||||
class="d-flex flex-row px-6 no-wrap"
|
||||
|
@ -43,16 +46,28 @@
|
|||
? '2px solid var(--v-primary-darken4)'
|
||||
: '2px solid var(--v-primary-lighten4)',
|
||||
}"
|
||||
class="py-4 px-4 ma-2 rounded-lg"
|
||||
:value="
|
||||
$vuetify.theme.dark ? experimentalDark : experimentalLight
|
||||
"
|
||||
class="pa-4 ma-2 rounded-lg"
|
||||
:value="$vuetify.theme.dark ? adaptiveDark : adaptiveLight"
|
||||
/>
|
||||
Adaptive
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<!-- Custom Background -->
|
||||
<v-btn
|
||||
icon
|
||||
class="ma-2 rounded-lg background border-primary"
|
||||
style="height: 3.75rem; width: 3.75rem"
|
||||
:class="$vuetify.theme.dark ? 'lighten-2' : 'darken-2'"
|
||||
@click="(pickerState = true), (pickerMode = 'background')"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<br />
|
||||
Custom
|
||||
</div>
|
||||
</div>
|
||||
</v-radio-group>
|
||||
<!-- ----------------------------------------------Primary Colors------------------------ -->
|
||||
<!-- ----Primary Colors---- -->
|
||||
<v-radio-group v-model="$vuetify.theme.currentTheme.primary" class="mx-2">
|
||||
<div
|
||||
class="d-flex flex-row px-6 py-2 no-wrap align-center"
|
||||
|
@ -74,40 +89,53 @@
|
|||
class="mr-2 my-auto rounded-xl"
|
||||
:value="color"
|
||||
/>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
width="300"
|
||||
content-class="background rounded-lg"
|
||||
<!-- Custom Primary -->
|
||||
<v-btn
|
||||
icon
|
||||
class="background"
|
||||
style="height: 1.75rem; width: 1.75rem"
|
||||
:class="$vuetify.theme.dark ? 'lighten-2' : 'darken-2'"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
@click="(pickerState = true), (pickerMode = 'primary')"
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
icon
|
||||
class="background"
|
||||
style="height: 1.75rem; width: 1.75rem"
|
||||
:class="$vuetify.theme.dark ? 'lighten-2' : 'darken-2'"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-color-picker
|
||||
v-model="$vuetify.theme.currentTheme.primary"
|
||||
style="min-width: 100%"
|
||||
class="background"
|
||||
hide-mode-switch
|
||||
dot-size="50"
|
||||
mode="hexa"
|
||||
flat
|
||||
/>
|
||||
</v-dialog>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-radio-group>
|
||||
<!-- ----------------------------------------------Mode Switch------------------------ -->
|
||||
<!-- ----Color Picker---- -->
|
||||
<v-dialog
|
||||
v-model="pickerState"
|
||||
width="300"
|
||||
content-class="background rounded-lg"
|
||||
>
|
||||
<v-color-picker
|
||||
v-model="$vuetify.theme.currentTheme[pickerMode]"
|
||||
style="min-width: 100%"
|
||||
class="background"
|
||||
hide-mode-switch
|
||||
dot-size="50"
|
||||
mode="hexa"
|
||||
flat
|
||||
/>
|
||||
</v-dialog>
|
||||
<!-- ----Mode Switch---- -->
|
||||
<v-divider v-if="!$store.state.tweaks.roundTweak" />
|
||||
<v-card
|
||||
flat
|
||||
class="d-flex flex-row justify-between mx-8 mb-8 px-4 background rounded-lg"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
class="d-flex flex-row justify-between mx-8 mb-8 px-4 py-3 background"
|
||||
:class="
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'lighten-1'
|
||||
: 'darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${$store.state.tweaks.roundTweak / 2}rem`,
|
||||
padding: !$store.state.tweaks.roundTweak ? '2rem !important' : '',
|
||||
margin: !$store.state.tweaks.roundTweak ? '0 !important' : '',
|
||||
}"
|
||||
@click="
|
||||
($vuetify.theme.dark = !$vuetify.theme.dark),
|
||||
$vuetube.haptics.hapticsImpactLight(1)
|
||||
|
@ -127,6 +155,7 @@
|
|||
<v-switch
|
||||
v-model="$vuetify.theme.dark"
|
||||
style="pointer-events: none"
|
||||
class="mt-2"
|
||||
persistent-hint
|
||||
inset
|
||||
/>
|
||||
|
@ -153,9 +182,10 @@ export default {
|
|||
{ name: "Black", color: "#000000" },
|
||||
],
|
||||
backgroundsLight: [{ name: "Normal", color: "#ffffff" }],
|
||||
experimentalLight: "",
|
||||
experimentalDark: "",
|
||||
dialog: false,
|
||||
adaptiveLight: "",
|
||||
adaptiveDark: "",
|
||||
pickerState: false,
|
||||
pickerMode: "bg",
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -169,19 +199,30 @@ export default {
|
|||
: localStorage.setItem("backgroundLight", value);
|
||||
this.$vuetube.statusBar.setTheme(value, this.$vuetify.theme.dark);
|
||||
this.$vuetube.navigationBar.setTheme(value, !this.$vuetify.theme.dark);
|
||||
// WIP: luma-based light-dark auto-switching
|
||||
// let bg = this.$vuetify.theme.currentTheme.background;
|
||||
// console.log(this.$vuetube.hexToRgb(bg));
|
||||
// let luma =
|
||||
// 0.2126 * this.$vuetube.hexToRgb(bg).r +
|
||||
// 0.7152 * this.$vuetube.hexToRgb(bg).g +
|
||||
// 0.0722 * this.$vuetube.hexToRgb(bg).b;
|
||||
// if (luma < 40) {
|
||||
// this.$vuetify.theme.dark = true;
|
||||
// this.vuetify.theme.currentTheme.background = bg;
|
||||
// }
|
||||
},
|
||||
"$vuetify.theme.currentTheme.primary"(value) {
|
||||
if (value != undefined) {
|
||||
this.$vuetify.theme.dark
|
||||
? localStorage.setItem("primaryDark", value)
|
||||
: localStorage.setItem("primaryLight", value);
|
||||
let tempD = this.experimentalDark;
|
||||
let tempL = this.experimentalLight;
|
||||
let tempD = this.adaptiveDark;
|
||||
let tempL = this.adaptiveLight;
|
||||
this.adapt();
|
||||
if (this.$vuetify.theme.currentTheme.background === tempD)
|
||||
this.$vuetify.theme.currentTheme.background = this.experimentalDark;
|
||||
this.$vuetify.theme.currentTheme.background = this.adaptiveDark;
|
||||
if (this.$vuetify.theme.currentTheme.background === tempL)
|
||||
this.$vuetify.theme.currentTheme.background = this.experimentalLight;
|
||||
this.$vuetify.theme.currentTheme.background = this.adaptiveLight;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -198,30 +239,21 @@ export default {
|
|||
);
|
||||
// the menace above returns a hex string with A SPACE " " in front of it, that's why substring(1)
|
||||
// the SPACE " " is stored as part of the CSS variable itself to be used for chaining
|
||||
this.experimentalDark = hexD.substring(1).toUpperCase();
|
||||
this.experimentalLight = hexL.substring(1).toUpperCase();
|
||||
this.adaptiveDark = hexD.substring(1).toUpperCase();
|
||||
this.adaptiveLight = hexL.substring(1).toUpperCase();
|
||||
setTimeout(() => {
|
||||
if (
|
||||
this.$vuetify.theme.currentTheme.background ==
|
||||
hexD.substring(1).toUpperCase()
|
||||
)
|
||||
this.$vuetify.theme.currentTheme.background = this.experimentalDark;
|
||||
this.$vuetify.theme.currentTheme.background = this.adaptiveDark;
|
||||
if (
|
||||
this.$vuetify.theme.currentTheme.background ==
|
||||
hexL.substring(1).toUpperCase()
|
||||
)
|
||||
this.$vuetify.theme.currentTheme.background = this.experimentalLight;
|
||||
this.$vuetify.theme.currentTheme.background = this.adaptiveLight;
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.border-primary {
|
||||
border: 2px solid var(--v-primary-base) !important;
|
||||
}
|
||||
.v-input--selection-controls__input {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,67 +1,157 @@
|
|||
<template>
|
||||
<!-- !IMPORTANT: don't let autoformatter format this style to multiline or else it breaks ¯\_(ツ)_/¯ -->
|
||||
<div
|
||||
class="d-flex flex-column justify-end"
|
||||
style="min-height: calc(100vh - 8rem)"
|
||||
style="
|
||||
min-height: calc(100vh - 8rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)) !important;
|
||||
"
|
||||
>
|
||||
<!-- TODO: outer radius -->
|
||||
<!-- TODO: Dense Navbar -->
|
||||
<!-- TODO: Disable Top Bar -->
|
||||
<!-- TODO: Top and Bottom bar color selection -->
|
||||
<v-card
|
||||
flat
|
||||
class="mb-4 background"
|
||||
class="mx-4 my-2 px-4 py-2 d-flex flex-row justify-between background"
|
||||
style="transition-duration: 0.3s; transition-property: border-radius"
|
||||
:class="
|
||||
roundTweak > 0 ? ($vuetify.theme.dark ? 'lighten-1' : 'darken-1') : ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2}rem`,
|
||||
}"
|
||||
>
|
||||
<h3
|
||||
class="my-auto background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-3' : 'text--darken-3'"
|
||||
>
|
||||
Fullscreen (Soon™)
|
||||
</h3>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
disabled
|
||||
class="mt-2"
|
||||
style="pointer-events: none"
|
||||
persistent-hint
|
||||
inset
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
<v-divider v-if="!roundTweak" />
|
||||
|
||||
<v-card
|
||||
flat
|
||||
class="mx-4 my-2 px-4 py-2 d-flex flex-row justify-between background"
|
||||
style="transition-duration: 0.3s; transition-property: border-radius"
|
||||
:class="
|
||||
roundTweak > 0 ? ($vuetify.theme.dark ? 'lighten-1' : 'darken-1') : ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2}rem`,
|
||||
}"
|
||||
>
|
||||
<h3
|
||||
class="my-auto background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-3' : 'text--darken-3'"
|
||||
>
|
||||
Navbar Blur (Soon™)
|
||||
</h3>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
disabled
|
||||
class="mt-2"
|
||||
style="pointer-events: none"
|
||||
persistent-hint
|
||||
inset
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
<v-divider v-if="!roundTweak" />
|
||||
|
||||
<h3 class="ml-8 mt-8">Rounded Corners</h3>
|
||||
|
||||
<v-card
|
||||
flat
|
||||
class="mx-4 my-2 background"
|
||||
style="
|
||||
transition-duration: 0.3s;
|
||||
transition-property: border-radius;
|
||||
overflow: hidden;
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2}rem`,
|
||||
margin: $store.state.tweaks.roundTweak > 0 ? '0 1rem' : '0',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item"
|
||||
class="pa-4 mb-1 background text-center rounded-sm"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
@click="list.pop(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
<v-card-title
|
||||
v-ripple
|
||||
class="pa-4 background primary--text text--lighten-2 rounded-sm"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
@click="list.push('x')"
|
||||
>
|
||||
+++++++++++++++++++++++++++++
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-card
|
||||
flat
|
||||
class="px-6 background"
|
||||
style="transition-duration: 0.3s; transition-property: border-radius"
|
||||
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 2}rem`,
|
||||
}"
|
||||
>
|
||||
<!-- margin: $store.state.tweaks.roundTweak > 0 ? '0 1rem' : '0', -->
|
||||
<h3 class="mt-5">Rounded Corners</h3>
|
||||
<div
|
||||
class="background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
<v-card
|
||||
flat
|
||||
class="mb-1 px-4 py-2 d-flex flex-row justify-between background"
|
||||
:class="
|
||||
roundTweak > 0 ? ($vuetify.theme.dark ? 'lighten-1' : 'darken-1') : ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 12}rem`,
|
||||
}"
|
||||
@click="
|
||||
(roundThumb = !roundThumb), $vuetube.haptics.hapticsImpactLight(1)
|
||||
"
|
||||
>
|
||||
applies to only a few elements for now
|
||||
</div>
|
||||
<!-- TODO: outer radius -->
|
||||
<!-- TODO: Dense Navbar -->
|
||||
<!-- TODO: Disable Top Bar -->
|
||||
<!-- TODO: Top and Bottom bar color selection -->
|
||||
<div
|
||||
class="my-auto background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
Round Thumbnails
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
v-model="roundThumb"
|
||||
style="pointer-events: none"
|
||||
persistent-hint
|
||||
class="mt-2"
|
||||
inset
|
||||
/>
|
||||
</v-card>
|
||||
<v-card
|
||||
flat
|
||||
class="mb-1 px-4 py-2 d-flex flex-row justify-between background"
|
||||
:class="
|
||||
roundTweak > 0 ? ($vuetify.theme.dark ? 'lighten-1' : 'darken-1') : ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 12}rem`,
|
||||
}"
|
||||
@click="
|
||||
(roundWatch = !roundWatch), $vuetube.haptics.hapticsImpactLight(1)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="my-auto background--text"
|
||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||
>
|
||||
Round Watch Page Components
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
v-model="roundWatch"
|
||||
style="pointer-events: none"
|
||||
persistent-hint
|
||||
class="mt-2"
|
||||
inset
|
||||
/>
|
||||
</v-card>
|
||||
<v-slider
|
||||
v-model="roundTweak"
|
||||
class="mr-2 mt-5"
|
||||
label="Inner"
|
||||
class="pr-4 pl-4 pt-4 pb-1 background"
|
||||
:max="4"
|
||||
step="1"
|
||||
label="Radius"
|
||||
step=".25"
|
||||
thumb-size="64"
|
||||
:class="
|
||||
roundTweak > 0 ? ($vuetify.theme.dark ? 'lighten-1' : 'darken-1') : ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: `${roundTweak / 12}rem`,
|
||||
}"
|
||||
@input="$vuetube.haptics.hapticsImpactLight(0)"
|
||||
>
|
||||
<template #thumb-label="{ value }">
|
||||
|
@ -77,9 +167,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
list: ["x", "x"],
|
||||
}),
|
||||
computed: {
|
||||
roundTweak: {
|
||||
get() {
|
||||
|
@ -89,6 +176,22 @@ export default {
|
|||
this.$store.commit("tweaks/setRoundTweak", value);
|
||||
},
|
||||
},
|
||||
roundThumb: {
|
||||
get() {
|
||||
return this.$store.state.tweaks.roundThumb;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit("tweaks/setRoundThumb", value);
|
||||
},
|
||||
},
|
||||
roundWatch: {
|
||||
get() {
|
||||
return this.$store.state.tweaks.roundWatch;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit("tweaks/setRoundWatch", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="py-2">
|
||||
<div>
|
||||
<v-list-item v-for="(item, index) in commits" :key="index" class="my-1">
|
||||
<v-card
|
||||
flat
|
||||
|
|
|
@ -1,19 +1,67 @@
|
|||
<template>
|
||||
<div style="padding-top: 1em">
|
||||
<v-list-item v-for="(item, index) in settingsItems" :key="index">
|
||||
<div>
|
||||
<v-list-item
|
||||
v-for="(item, index) in settingsItems"
|
||||
:key="index"
|
||||
:style="{
|
||||
padding:
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? '0 1rem !important'
|
||||
: '0rem !important',
|
||||
}"
|
||||
>
|
||||
<v-btn
|
||||
text
|
||||
class="entry text-left text-capitalize"
|
||||
:to="item.to"
|
||||
:disabled="item.disabled"
|
||||
:style="{
|
||||
borderRadius: `${$store.state.tweaks.roundTweak / 2}rem`,
|
||||
paddingLeft:
|
||||
$store.state.tweaks.roundTweak > 0 ? '' : '1.5rem !important',
|
||||
}"
|
||||
>
|
||||
<v-icon size="30px" class="icon" v-text="item.icon" />
|
||||
{{ item.name }}
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
:style="{
|
||||
padding:
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? '0 1rem !important'
|
||||
: '0rem !important',
|
||||
}"
|
||||
>
|
||||
<!-- Dev Mode Open -->
|
||||
<v-btn
|
||||
v-if="!devmode"
|
||||
text
|
||||
class="entry"
|
||||
:style="{
|
||||
borderRadius: `${$store.state.tweaks.roundTweak / 2}rem`,
|
||||
paddingLeft:
|
||||
$store.state.tweaks.roundTweak > 0 ? '' : '1.5rem !important',
|
||||
}"
|
||||
@click="dev()"
|
||||
/>
|
||||
|
||||
<!-- Dev Mode Open -->
|
||||
<v-btn text class="entry" @click="dev()" />
|
||||
<v-btn
|
||||
v-if="devmode"
|
||||
text
|
||||
class="entry text-left text-capitalize"
|
||||
to="/mods/developer"
|
||||
:style="{
|
||||
borderRadius: `${$store.state.tweaks.roundTweak / 2}rem`,
|
||||
paddingLeft:
|
||||
$store.state.tweaks.roundTweak > 0 ? '' : '1.5rem !important',
|
||||
}"
|
||||
>
|
||||
<v-icon size="30px" class="icon">mdi-database-edit</v-icon>
|
||||
{{ devmodebuttonname }}
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
<!-- End Dev Mode -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -22,10 +70,22 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
devClicks: 0,
|
||||
devmode: false,
|
||||
|
||||
devmodebuttonname: "Developer Mode",
|
||||
|
||||
settingsItems: [
|
||||
{ name: "General", icon: "mdi-cog", to: "", disabled: true },
|
||||
{ name: "Theme", icon: "mdi-brush-variant", to: "/mods/theme" },
|
||||
{
|
||||
name: "General",
|
||||
icon: "mdi-cog",
|
||||
to: "",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "Theme",
|
||||
icon: "mdi-brush-variant",
|
||||
to: "/mods/theme",
|
||||
},
|
||||
{
|
||||
name: "Player",
|
||||
icon: "mdi-motion-play-outline",
|
||||
|
@ -37,10 +97,15 @@ export default {
|
|||
icon: "mdi-television-guide",
|
||||
to: "/mods/tweaks",
|
||||
},
|
||||
{ name: "Startup Options", icon: "mdi-restart", to: "/mods/startup" },
|
||||
{
|
||||
name: "Startup Options",
|
||||
icon: "mdi-restart",
|
||||
to: "/mods/startup",
|
||||
},
|
||||
{
|
||||
name: "Plugins",
|
||||
icon: "mdi-puzzle",
|
||||
to: "",
|
||||
to: "/mods/plugins",
|
||||
disabled: true,
|
||||
},
|
||||
|
@ -49,16 +114,41 @@ export default {
|
|||
icon: "mdi-cloud-download-outline",
|
||||
to: "/mods/updates",
|
||||
},
|
||||
{ name: "Logs", icon: "mdi-text-box-outline", to: "/mods/logs" },
|
||||
{ name: "About", icon: "mdi-information-outline", to: "/mods/about" },
|
||||
{
|
||||
name: "Logs",
|
||||
icon: "mdi-text-box-outline",
|
||||
to: "/mods/logs",
|
||||
},
|
||||
{
|
||||
name: "About",
|
||||
icon: "mdi-information-outline",
|
||||
to: "/mods/about",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.settingsItems[0].name = this.$lang("settings").general;
|
||||
this.settingsItems[1].name = this.$lang("settings").theme;
|
||||
this.settingsItems[2].name = this.$lang("settings").player;
|
||||
this.settingsItems[3].name = this.$lang("settings").uitweaker;
|
||||
this.settingsItems[4].name = this.$lang("settings").startupoptions;
|
||||
this.settingsItems[5].name = this.$lang("settings").plugins;
|
||||
this.settingsItems[6].name = this.$lang("settings").updates;
|
||||
this.settingsItems[7].name = this.$lang("settings").logs;
|
||||
this.settingsItems[8].name = this.$lang("settings").about;
|
||||
this.devmodebuttonname = this.$lang("settings").devmode;
|
||||
|
||||
this.devmode = localStorage.getItem("devmode");
|
||||
},
|
||||
|
||||
methods: {
|
||||
dev() {
|
||||
this.devClicks++;
|
||||
if (this.devClicks >= 6) {
|
||||
this.$router.push("/mods/developer");
|
||||
localStorage.setItem("devmode", "true");
|
||||
this.devmode = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
<template>
|
||||
<div class="background" id="watch-body">
|
||||
<div id="player-container">
|
||||
<v-btn text style="position: fixed; z-index: 69420" to="home">
|
||||
<v-icon>mdi-chevron-down</v-icon>
|
||||
</v-btn>
|
||||
<!-- VueTube Player V1 -->
|
||||
<vuetubePlayer
|
||||
:sources="sources"
|
||||
v-if="useBetaPlayer === 'true' && sources.length > 0"
|
||||
/>
|
||||
|
||||
<!-- Stock Player -->
|
||||
<legacyPlayer
|
||||
id="player"
|
||||
<!-- // TODO: move component to default.vue -->
|
||||
<!-- // TODO: pass sources through vuex instead of props -->
|
||||
<player
|
||||
v-if="sources.length > 0 && video.title && video.channelName"
|
||||
ref="player"
|
||||
v-touch="{ down: () => $router.push('/home') }"
|
||||
class="background"
|
||||
:vid-src="vidSrc"
|
||||
v-if="useBetaPlayer !== 'true'"
|
||||
:video="video"
|
||||
:sources="sources"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -69,14 +59,18 @@
|
|||
fab
|
||||
class="vertical-button mx-1"
|
||||
elevation="0"
|
||||
style="width: 4.2rem !important; height: 4.2rem !important"
|
||||
style="
|
||||
width: 4.2rem !important;
|
||||
height: 4.2rem !important;
|
||||
text-transform: none !important;
|
||||
"
|
||||
:disabled="item.disabled"
|
||||
@click="callMethodByName(item.actionName)"
|
||||
>
|
||||
<v-icon v-text="item.icon" />
|
||||
<div
|
||||
class="mt-2"
|
||||
style="font-size: 0.66rem"
|
||||
class="mt-1"
|
||||
style="font-size: 0.6rem"
|
||||
v-text="item.value || item.name"
|
||||
/>
|
||||
</v-btn>
|
||||
|
@ -108,14 +102,37 @@
|
|||
</v-sheet>
|
||||
</v-bottom-sheet> -->
|
||||
</v-card>
|
||||
<v-divider />
|
||||
|
||||
<v-divider
|
||||
v-if="
|
||||
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- Channel Bar -->
|
||||
<div v-if="loaded">
|
||||
<v-card
|
||||
flat
|
||||
class="channel-section background py-2 px-3 rounded-0"
|
||||
:to="video.channelUrl"
|
||||
class="channel-section py-2 px-3 background"
|
||||
:class="
|
||||
$store.state.tweaks.roundWatch && $store.state.tweaks.roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'background lighten-1'
|
||||
: 'background darken-1'
|
||||
: ''
|
||||
"
|
||||
to="/channel"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 2}rem`
|
||||
: '0',
|
||||
margin:
|
||||
$store.state.tweaks.roundWatch &&
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? '1rem'
|
||||
: '0',
|
||||
}"
|
||||
@click="$store.dispatch('channel/fetchChannel', video.channelUrl)"
|
||||
>
|
||||
<div id="details">
|
||||
<div class="avatar-link mr-3">
|
||||
|
@ -136,9 +153,14 @@
|
|||
subscribe
|
||||
</div>
|
||||
</v-card>
|
||||
<v-divider />
|
||||
</div>
|
||||
|
||||
<v-divider
|
||||
v-if="
|
||||
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- Description -->
|
||||
<div v-if="showMore">
|
||||
<div class="scroll-y ma-4">
|
||||
|
@ -146,12 +168,40 @@
|
|||
:render="video.renderedData.description"
|
||||
/>
|
||||
</div>
|
||||
<v-divider />
|
||||
</div>
|
||||
|
||||
<v-divider
|
||||
v-if="
|
||||
showMore &&
|
||||
(!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch)
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- Comments -->
|
||||
<div v-if="loaded && video.commentData" @click="toggleComment">
|
||||
<v-card flat tile class="background comment-renderer px-3">
|
||||
<v-card
|
||||
v-ripple
|
||||
flat
|
||||
tile
|
||||
class="comment-renderer px-3 background"
|
||||
:class="
|
||||
$store.state.tweaks.roundWatch && $store.state.tweaks.roundTweak > 0
|
||||
? $vuetify.theme.dark
|
||||
? 'background lighten-1'
|
||||
: 'background darken-1'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 2}rem !important`
|
||||
: '0',
|
||||
margin:
|
||||
$store.state.tweaks.roundWatch &&
|
||||
$store.state.tweaks.roundTweak > 0
|
||||
? '1rem'
|
||||
: '0',
|
||||
}"
|
||||
>
|
||||
<v-card-text class="comment-count keep-spaces px-0">
|
||||
<template v-for="text in video.commentData.headerText.runs">
|
||||
<template v-if="text.bold">
|
||||
|
@ -163,21 +213,26 @@
|
|||
<v-icon v-if="showComments" dense>mdi-unfold-less-horizontal</v-icon>
|
||||
<v-icon v-else dense>mdi-unfold-more-horizontal</v-icon>
|
||||
</v-card>
|
||||
<v-divider />
|
||||
</div>
|
||||
|
||||
<v-divider
|
||||
v-if="
|
||||
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
|
||||
"
|
||||
/>
|
||||
|
||||
<swipeable-bottom-sheet
|
||||
v-if="loaded && video.commentData"
|
||||
v-model="showComments"
|
||||
hide-overlay
|
||||
persistent
|
||||
no-click-animation
|
||||
attach="#content-container"
|
||||
v-if="loaded && video.commentData"
|
||||
>
|
||||
<mainCommentRenderer
|
||||
:defaultContinuation="video.commentContinuation"
|
||||
:commentData="video.commentData"
|
||||
v-model="showComments"
|
||||
:comment-data="video.commentData"
|
||||
:default-continuation="video.commentContinuation"
|
||||
></mainCommentRenderer>
|
||||
</swipeable-bottom-sheet>
|
||||
|
||||
|
@ -205,29 +260,27 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import player from "~/components/Player/index.vue";
|
||||
import { Share } from "@capacitor/share";
|
||||
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
|
||||
import { getCpn } from "~/plugins/utils";
|
||||
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
|
||||
import ItemSectionRenderer from "~/components/SectionRenderers/itemSectionRenderer.vue";
|
||||
import legacyPlayer from "~/components/Player/legacy.vue";
|
||||
import vuetubePlayer from "~/components/Player/index.vue";
|
||||
import ShelfRenderer from "~/components/SectionRenderers/shelfRenderer.vue";
|
||||
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
|
||||
import ItemSectionRenderer from "~/components/SectionRenderers/itemSectionRenderer.vue";
|
||||
import mainCommentRenderer from "~/components/Comments/mainCommentRenderer.vue";
|
||||
import SwipeableBottomSheet from "~/components/ExtendedComponents/swipeableBottomSheet";
|
||||
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
|
||||
|
||||
import backType from "~/plugins/classes/backType";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
player,
|
||||
ShelfRenderer,
|
||||
VidLoadRenderer,
|
||||
SlimVideoDescriptionRenderer,
|
||||
vuetubePlayer,
|
||||
legacyPlayer,
|
||||
ItemSectionRenderer,
|
||||
SwipeableBottomSheet,
|
||||
mainCommentRenderer,
|
||||
SwipeableBottomSheet,
|
||||
SlimVideoDescriptionRenderer,
|
||||
},
|
||||
layout: "empty",
|
||||
// transition(to) { // TODO: fix layout switching
|
||||
|
@ -247,7 +300,6 @@ export default {
|
|||
// Exit fullscreen if currently in fullscreen
|
||||
// if (this.$refs.player) this.$refs.player.webkitExitFullscreen();
|
||||
// Reset player and run getVideo function again
|
||||
// this.vidSrc = "";
|
||||
// this.startTime = Math.floor(Date.now() / 1000);
|
||||
// this.getVideo();
|
||||
clearInterval(this.interval);
|
||||
|
@ -272,18 +324,10 @@ export default {
|
|||
this.loaded = false;
|
||||
|
||||
this.$youtube.getVid(this.$route.query.v).then((result) => {
|
||||
this.video = result;
|
||||
console.log("Video info data", result);
|
||||
console.log(result.availableResolutions);
|
||||
|
||||
//--- VueTube Player v1 ---//
|
||||
// TODO: add other resolutions as well
|
||||
this.sources = result.availableResolutions;
|
||||
|
||||
//--- Legacy Player ---//
|
||||
this.vidSrc =
|
||||
result.availableResolutions[
|
||||
result.availableResolutions.length - 1
|
||||
].url; // Takes the highest available resolution with both video and Audio. Note this will be lower than the actual highest resolution
|
||||
console.log("Video info data", result);
|
||||
this.video = result;
|
||||
|
||||
//--- Content Stuff ---//
|
||||
this.likes = result.metadata.likes.toLocaleString();
|
||||
|
@ -313,7 +357,6 @@ export default {
|
|||
// using item.action in the v-for loop
|
||||
this[name]();
|
||||
},
|
||||
dislike() {},
|
||||
async share() {
|
||||
// this.share = !this.share;
|
||||
await Share.share({
|
||||
|
@ -365,7 +408,8 @@ export default {
|
|||
{
|
||||
name: "Likes",
|
||||
icon: "mdi-thumb-up-outline",
|
||||
// action: null,
|
||||
// action: this.like(),
|
||||
actionName: "like",
|
||||
value: this.likes,
|
||||
disabled: true,
|
||||
},
|
||||
|
@ -384,17 +428,33 @@ export default {
|
|||
actionName: "share",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
name: "Save",
|
||||
icon: "mdi-plus-box-multiple-outline",
|
||||
actionName: "enqueue",
|
||||
disabled: true,
|
||||
},
|
||||
// {
|
||||
// name: "Quality",
|
||||
// icon: "mdi-high-definition",
|
||||
// actionName: "quality",
|
||||
// disabled: false,
|
||||
// },
|
||||
// {
|
||||
// name: "Speed",
|
||||
// icon: "mdi-speedometer",
|
||||
// actionName: "speed",
|
||||
// disabled: false,
|
||||
// },
|
||||
],
|
||||
showMore: false,
|
||||
showComments: false,
|
||||
// share: false,
|
||||
vidSrc: null,
|
||||
sources: [],
|
||||
recommends: null,
|
||||
loaded: false,
|
||||
interval: null,
|
||||
video: null,
|
||||
useBetaPlayer: false,
|
||||
backHierarchy: [],
|
||||
};
|
||||
},
|
||||
|
@ -402,7 +462,6 @@ export default {
|
|||
mountedInit() {
|
||||
this.startTime = Math.floor(Date.now() / 1000);
|
||||
this.getVideo();
|
||||
this.useBetaPlayer = localStorage.getItem("debug.BetaPlayer");
|
||||
|
||||
// Reset vertical scrolling
|
||||
const scrollableList = document.querySelectorAll(".overflow-y-auto");
|
||||
|
|
12
NUXT/plugins/language.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
function module(text) {
|
||||
const selectedLanguage = localStorage.getItem(text) || "english";
|
||||
const languagePack = require('./languages/'+selectedLanguage);
|
||||
|
||||
if (!text) return languagePack;
|
||||
return languagePack[text];
|
||||
|
||||
}
|
||||
|
||||
export default ({ app }, inject) => {
|
||||
inject("lang", module);
|
||||
};
|
44
NUXT/plugins/languages/english.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
module.exports = {
|
||||
name: "English",
|
||||
|
||||
global: {
|
||||
home: "Home",
|
||||
subscriptions: "Subscriptions",
|
||||
library: "Library"
|
||||
},
|
||||
|
||||
index: {
|
||||
connecting: "Connecting",
|
||||
launching: "Launching"
|
||||
},
|
||||
|
||||
settings: {
|
||||
general: "General",
|
||||
theme: "Theme",
|
||||
player: "Player",
|
||||
uitweaker: "UI Tweaker",
|
||||
startupoptions: "Startup Options",
|
||||
plugins: "Plugins",
|
||||
updates: "Updates",
|
||||
logs: "Logs",
|
||||
about: "About",
|
||||
devmode: "Registry Editor"
|
||||
},
|
||||
|
||||
mods: {
|
||||
about: {
|
||||
appinformation: "App Information",
|
||||
appversion: "App Version",
|
||||
deviceinformation: "Device Information",
|
||||
platform: "Platform",
|
||||
os: "Operating System",
|
||||
model: "Model",
|
||||
manufacturer: "Manufacturer",
|
||||
emulator: "Emulator",
|
||||
github: "GitHub",
|
||||
discord: "Discord"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -85,5 +85,5 @@ module.exports = {
|
|||
getMutationByKey,
|
||||
linkParser,
|
||||
delay,
|
||||
parseEmoji
|
||||
parseEmoji,
|
||||
};
|
||||
|
|
76
NUXT/store/channel/index.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
const getDefaultState = () => {
|
||||
return {
|
||||
loading: null,
|
||||
error: null,
|
||||
avatar: null,
|
||||
banner: null,
|
||||
title: null,
|
||||
subscribe: null,
|
||||
subscribeAlt: null,
|
||||
descriptionPreview: null,
|
||||
subscribers: null,
|
||||
videosCount: null,
|
||||
featuredChannels: null,
|
||||
videoExample: null,
|
||||
};
|
||||
};
|
||||
export const state = () => {
|
||||
return getDefaultState();
|
||||
};
|
||||
export const actions = {
|
||||
fetchChannel({ state }, channelUrl) {
|
||||
Object.assign(state, getDefaultState());
|
||||
state.loading = true;
|
||||
console.log(channelUrl);
|
||||
const channelRequest =
|
||||
channelUrl.includes("/c/") ||
|
||||
channelUrl.includes("/user/") ||
|
||||
channelUrl.includes("/channel/")
|
||||
? `https://youtube.com/${channelUrl}`
|
||||
: `https://youtube.com/channel/${channelUrl}`;
|
||||
this.$youtube
|
||||
.getChannel(channelRequest)
|
||||
.then((channel) => {
|
||||
// console.log(channel);
|
||||
state.loading = false;
|
||||
state.banner =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelBanner?.image.sources[0].url;
|
||||
state.avatar =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.avatarData.avatar?.image.sources[0].url;
|
||||
state.title =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.title;
|
||||
state.subscribe =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.subscribeButton.subscribeButtonContent.buttonText;
|
||||
state.subscribeAlt =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.subscribeButton.subscribeButtonContent.accessibilityText;
|
||||
state.descriptionPreview =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.descriptionPreview.description;
|
||||
state.subscribers =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.metadata.subscriberCountText;
|
||||
state.videosCount =
|
||||
channel.header.channelMobileHeaderRenderer.channelHeader.elementRenderer.newElement.type.componentType.model.channelHeaderModel.channelProfile.metadata.videosCountText;
|
||||
const featuredSection =
|
||||
channel.contents.singleColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents.find(
|
||||
(i) => {
|
||||
return !!i?.shelfRenderer?.content?.horizontalListRenderer
|
||||
?.items[0].gridChannelRenderer;
|
||||
}
|
||||
);
|
||||
state.featuredChannels =
|
||||
featuredSection.shelfRenderer.content.horizontalListRenderer.items;
|
||||
console.log(
|
||||
channel.contents.singleColumnBrowseResultsRenderer.tabs[0].tabRenderer
|
||||
.content.sectionListRenderer.contents[0].shelfRenderer.content
|
||||
.verticalListRenderer.items[0].elementRenderer.newElement.type
|
||||
.componentType.model
|
||||
);
|
||||
state.videoExample =
|
||||
channel.contents.singleColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0].shelfRenderer.content.verticalListRenderer.items[0].elementRenderer.newElement.type.componentType.model.videoWithContextModel.videoWithContextData.videoData;
|
||||
})
|
||||
.catch((err) => {
|
||||
state.loading = false;
|
||||
state.error = err;
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,12 +1,18 @@
|
|||
export const state = () => ({
|
||||
roundTweak: 0,
|
||||
roundThumb: null,
|
||||
roundWatch: null,
|
||||
});
|
||||
export const mutations = {
|
||||
initTweaks(state) {
|
||||
// NOTE: localStorage is not reactive, so it will only be used on first load
|
||||
// currently called beforeCreate() in pages/default.vue
|
||||
// currently called on mounted() in pages/index.vue
|
||||
if (process.client) {
|
||||
state.roundTweak = localStorage.getItem("roundTweak") || 0;
|
||||
state.roundTweak = JSON.parse(localStorage.getItem("roundTweak")) || 0;
|
||||
state.roundThumb =
|
||||
JSON.parse(localStorage.getItem("roundThumb")) === true;
|
||||
state.roundWatch =
|
||||
JSON.parse(localStorage.getItem("roundWatch")) === true;
|
||||
}
|
||||
},
|
||||
setRoundTweak(state, payload) {
|
||||
|
@ -15,4 +21,12 @@ export const mutations = {
|
|||
localStorage.setItem("roundTweak", payload);
|
||||
}
|
||||
},
|
||||
setRoundThumb(state, payload) {
|
||||
state.roundThumb = payload;
|
||||
localStorage.setItem("roundThumb", payload);
|
||||
},
|
||||
setRoundWatch(state, payload) {
|
||||
state.roundWatch = payload;
|
||||
localStorage.setItem("roundWatch", payload);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="SERIAL_NUMBER" />
|
||||
<value value="adb-97QAY11P1S-NELaqI._adb-tls-connect._tcp." />
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_31_arm64-v8a.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-05-05T23:23:16.786886Z" />
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-05-14T02:50:17.689302Z" />
|
||||
</component>
|
||||
</project>
|
|
@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
|
|||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "com.Frontesque.youtube"
|
||||
applicationId "com.Frontesque.vuetube"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.Frontesque.youtube",
|
||||
"applicationId": "com.Frontesque.vuetube",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
|
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
@ -2,5 +2,9 @@
|
|||
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<access origin="*" />
|
||||
|
||||
<feature name="CDVOrientation">
|
||||
<param name="android-package" value="cordova.plugins.screenorientation.CDVOrientation"/>
|
||||
</feature>
|
||||
|
||||
|
||||
</widget>
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cordova.plugins.screenorientation;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.util.Log;
|
||||
|
||||
public class CDVOrientation extends CordovaPlugin {
|
||||
|
||||
private static final String TAG = "YoikScreenOrientation";
|
||||
|
||||
/**
|
||||
* Screen Orientation Constants
|
||||
*/
|
||||
|
||||
private static final String ANY = "any";
|
||||
private static final String PORTRAIT_PRIMARY = "portrait-primary";
|
||||
private static final String PORTRAIT_SECONDARY = "portrait-secondary";
|
||||
private static final String LANDSCAPE_PRIMARY = "landscape-primary";
|
||||
private static final String LANDSCAPE_SECONDARY = "landscape-secondary";
|
||||
private static final String PORTRAIT = "portrait";
|
||||
private static final String LANDSCAPE = "landscape";
|
||||
|
||||
@Override
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
||||
|
||||
Log.d(TAG, "execute action: " + action);
|
||||
|
||||
// Route the Action
|
||||
if (action.equals("screenOrientation")) {
|
||||
return routeScreenOrientation(args, callbackContext);
|
||||
}
|
||||
|
||||
// Action not found
|
||||
callbackContext.error("action not recognised");
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean routeScreenOrientation(JSONArray args, CallbackContext callbackContext) {
|
||||
|
||||
String action = args.optString(0);
|
||||
|
||||
|
||||
|
||||
String orientation = args.optString(1);
|
||||
|
||||
Log.d(TAG, "Requested ScreenOrientation: " + orientation);
|
||||
|
||||
Activity activity = cordova.getActivity();
|
||||
|
||||
if (orientation.equals(ANY)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
} else if (orientation.equals(LANDSCAPE_PRIMARY)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else if (orientation.equals(PORTRAIT_PRIMARY)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
} else if (orientation.equals(LANDSCAPE)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
} else if (orientation.equals(PORTRAIT)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
|
||||
} else if (orientation.equals(LANDSCAPE_SECONDARY)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
|
||||
} else if (orientation.equals(PORTRAIT_SECONDARY)) {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
|
||||
}
|
||||
|
||||
callbackContext.success();
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -2,5 +2,9 @@
|
|||
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<access origin="*" />
|
||||
|
||||
<feature name="CDVOrientation">
|
||||
<param name="ios-package" value="CDVOrientation"/>
|
||||
</feature>
|
||||
|
||||
|
||||
</widget>
|
|
@ -20,6 +20,7 @@ def capacitor_pods
|
|||
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
||||
pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast'
|
||||
pod 'HugotomaziCapacitorNavigationBar', :path => '../../node_modules/@hugotomazi/capacitor-navigation-bar'
|
||||
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
"@capacitor/status-bar": "^1.0.8",
|
||||
"@capacitor/toast": "^1.0.8",
|
||||
"@hugotomazi/capacitor-navigation-bar": "^1.1.1",
|
||||
"cordova-plugin-screen-orientation": "^3.0.2",
|
||||
"es6-promise-plugin": "^4.2.2",
|
||||
"iconv-lite": "^0.6.3"
|
||||
}
|
||||
}
|
||||
|
|
30
readme.md
|
@ -12,20 +12,21 @@ Pronounced View Tube (<code>/ˈvjuːˌtjuːb/</code>)
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Frontesque/VueTube/commits/main"><img src="https://img.shields.io/github/commit-activity/m/Frontesque/VueTube?label=Commits" alt="Commits"></img></a>
|
||||
<a href="https://github.com/Frontesque/VueTube/issues" alt="Issues"><img src="https://img.shields.io/github/issues/Frontesque/VueTube"></img></a>
|
||||
<a><img src="https://img.shields.io/github/languages/count/Frontesque/VueTube" alt="Languages"></img></a>
|
||||
<a href="https://github.com/Frontesque/VueTube/blob/main/LICENSE" alt="License"><img src="https://img.shields.io/github/license/Frontesque/VueTube"></img></a>
|
||||
<a><img src="https://img.shields.io/github/stars/Frontesque/VueTube" alt="Stars"></img></a>
|
||||
<a><img src="https://img.shields.io/snyk/vulnerabilities/github/FrontEsque/VueTube" alt="Vulnerabilities"></img></a>
|
||||
<a><img src="https://img.shields.io/librariesio/github/Frontesque/VueTube" alt="Dependencies"></img></a>
|
||||
<a><img src="https://img.shields.io/tokei/lines/github/Frontesque/VueTube" alt="Lines"></img></a>
|
||||
<a href="https://github.com/Frontesque/VueTube/actions/workflows/ci.yml" alt="CI"><img src="https://github.com/Frontesque/VueTube/actions/workflows/ci.yml/badge.svg"></img></a>
|
||||
<a href="https://github.com/VueTubeApp/VueTube/commits/main"><img src="https://img.shields.io/github/commit-activity/m/VueTubeApp/VueTube?label=Commits" alt="Commits"></img></a>
|
||||
<a href="https://github.com/VueTubeApp/VueTube/issues" alt="Issues"><img src="https://img.shields.io/github/issues/VueTubeApp/VueTube"></img></a>
|
||||
<a><img src="https://img.shields.io/github/languages/count/VueTubeApp/VueTube" alt="Languages"></img></a>
|
||||
<a href="https://github.com/VueTubeApp/VueTube/blob/main/LICENSE" alt="License"><img src="https://img.shields.io/github/license/VueTubeApp/VueTube"></img></a>
|
||||
<a><img src="https://img.shields.io/github/stars/VueTubeApp/VueTube" alt="Stars"></img></a>
|
||||
<a><img src="https://img.shields.io/snyk/vulnerabilities/github/VueTubeApp/VueTube" alt="Vulnerabilities"></img></a>
|
||||
<a><img src="https://img.shields.io/librariesio/github/VueTubeApp/VueTube" alt="Dependencies"></img></a>
|
||||
<a><img src="https://img.shields.io/tokei/lines/github/VueTubeApp/VueTube" alt="Lines"></img></a>
|
||||
<a href="https://github.com/VueTubeApp/VueTube/actions/workflows/ci.yml" alt="CI"><img src="https://github.com/VueTubeApp/VueTube/actions/workflows/ci.yml/badge.svg"></img></a>
|
||||
<a href="https://vuetube.app" alt="Website"><img src="https://img.shields.io/website?down_message=offline&up_message=online&url=https%3A%2F%2Fvuetube.app"></img></a>
|
||||
<a href="https://reddit.com/r/vuetube" alt="Reddit"><img src="https://img.shields.io/reddit/subreddit-subscribers/vuetube?label=r%2FVuetube&logo=reddit&logoColor=white"></img></a>
|
||||
<a href="https://t.me/VueTube" alt="Telegram"><img src="https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fvuetube"></img></a>
|
||||
<a href="https://discord.gg/7P8KJrdd5W" alt="Discord"><img src="https://img.shields.io/discord/946587366242533377?label=Discord&style=flat&logo=discord&logoColor=white"></img></a>
|
||||
<a href="https://twitter.com/VueTubeApp" alt="Twitter"><img src="https://img.shields.io/twitter/follow/VueTubeApp?label=Follow&style=flat&logo=twitter"></img></a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -50,11 +51,11 @@ To install please visit www.vuetube.app/install
|
|||
|
||||
## Screenshots
|
||||
|
||||
View on our website: [https://vuetube.app/info/screenshots](https://vuetube.app/info/screenshots)
|
||||
View on our website: www.vuetube.app/info/screenshots
|
||||
|
||||
### Technologies used
|
||||
|
||||
<a href="https://capacitorjs.com/solution/vue"><img src="https://cdn.discordapp.com/attachments/953538236716814356/955694368742834176/Capacitator-Dark.svg" height=40/></a> <a href="https://vuetifyjs.com/"><img src="https://cdn.discordapp.com/attachments/953538236716814356/955694368956760074/Vuetify-Dark.svg" height=40/></a> <a href="https://nuxtjs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/NuxtJS-Dark.svg" height=40/></a> <a href="https://vuejs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/VueJS-Dark.svg" height=40/></a> <a href="https://javascript.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/JavaScript.svg" height=40/></a> <a href="https://java.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Java-Dark.svg" height=40/></a> <a href="https://gradle.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/955691550560636958/Gradle.svg" height=40/></a> <a href="https://developer.apple.com/swift/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Swift.svg" height=40/></a>
|
||||
<a href="https://capacitorjs.com/solution/vue"><img src="https://cdn.discordapp.com/attachments/953538236716814356/955694368742834176/Capacitator-Dark.svg" height=40/></a> <a href="https://vuetifyjs.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/973719873467342908/Vuetify-Dark.svg" height=40/></a> <a href="https://nuxtjs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/NuxtJS-Dark.svg" height=40/></a> <a href="https://vuejs.org/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/VueJS-Dark.svg" height=40/></a> <a href="https://javascript.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/JavaScript.svg" height=40/></a> <a href="https://java.com/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Java-Dark.svg" height=40/></a> <a href="https://gradle.com/"><img src="https://cdn.discordapp.com/attachments/810799100940255260/955691550560636958/Gradle.svg" height=40/></a> <a href="https://developer.apple.com/swift/"><img src="https://github.com/tandpfun/skill-icons/raw/main/icons/Swift.svg" height=40/></a>
|
||||
|
||||
### Why am I doing this?
|
||||
|
||||
|
@ -62,12 +63,12 @@ Well this has been thrown around on the Return Youtube Dislike discord server fo
|
|||
|
||||
### Want to contribute?
|
||||
|
||||
Please read our website on how to do so: https://vuetube.app/contributing
|
||||
Please read our website on how to do so: www.vuetube.app/contributing
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/Frontesque/VueTube/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Frontesque/VueTube" />
|
||||
<a href="https://github.com/VueTubeApp/VueTube/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=VueTubeApp/VueTube" />
|
||||
</a>
|
||||
|
||||
<sub>Made with [contrib.rocks](https://contrib.rocks). </sub>
|
||||
|
@ -78,6 +79,7 @@ Please read our website on how to do so: https://vuetube.app/contributing
|
|||
- VueTube Logo by [@afnzmn](https://github.com/afnzmn)
|
||||
|
||||
## Disclamer
|
||||
|
||||
The VueTube project and its contents are not affiliated with, funded, authorized, endorsed by, or in any way accociated with YouTube, Google LLC or any of its affiliates and subsidaries. The official YouTube website can be found at [www.youtube.com](https://www.youtube.com).
|
||||
|
||||
Any trademark, service mark, trade name, or other intellectual property rights used in the VueTube project are owned by the respective owners.
|
||||
|
|