Merge branch 'dev' of github.com-picklenik:Frontesque/VueTube into channel-stuff

This commit is contained in:
Nikita Krupin 2022-05-04 21:35:35 -04:00
commit 199b6041c0
18 changed files with 274 additions and 93 deletions

View File

@ -2,6 +2,17 @@
<v-card
class="entry gridVideoRenderer background"
:to="`/watch?v=${video.videoId}`"
:class="
$store.state.tweaks.roundTweak > 0
? $vuetify.theme.dark
? 'lighten-1'
: 'darken-1'
: ''
"
:style="{
borderRadius: `${roundTweak / 2}rem`,
margin: $store.state.tweaks.roundTweak > 0 ? '0.5rem 1rem' : '0',
}"
flat
>
<div id="details">

View File

@ -3,7 +3,7 @@
<v-list-item
v-for="(video, index) in render.items"
:key="index"
class="pa-0"
class="pa-0 min-height-0"
>
<component
v-if="getComponents()[Object.keys(video)[0]]"

View File

@ -3,7 +3,7 @@
<v-list-item
v-for="(renderer, index) in render.contents"
:key="index"
class="pa-0"
class="pa-0 min-height-0"
>
<component
v-if="getComponents()[Object.keys(renderer)[0]]"

View File

@ -3,7 +3,7 @@
<v-list-item
v-for="(video, index) in render.items"
:key="index"
class="pa-0"
class="pa-0 min-height-0"
>
<component
v-if="getComponents()[Object.keys(video)[0]]"

View File

@ -1,9 +1,9 @@
<template>
<div>
<div
<div class="fill-width">
<v-list-item
v-for="(video, index) in render.contents"
:key="index"
class="pa-0 fill-screen"
class="pa-0 min-height-0"
>
<component
v-if="getComponents()[Object.keys(video)[0]]"
@ -11,13 +11,13 @@
:key="video[Object.keys(video)[0]].videoId"
:video="video[Object.keys(video)[0]]"
></component>
</div>
</v-list-item>
<div
v-if="
render.separatorDetails && render.separatorDetails.hasBottomSeparator
"
class="separator-bottom background"
:class="$vuetify.theme.dark ? 'lighten-4' : 'darken-4'"
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
:style="{ height: render.separatorDetails.height + 'px' }"
></div>
</div>
@ -28,10 +28,6 @@
width: 100%; /* Prevent Loading Weirdness */
padding: 10px;
}
.fill-screen {
width: 100vw; /* Very Hacky */
}
</style>
<script>

View File

@ -1,20 +1,22 @@
<template>
<div>
<div class="fill-width">
<h4 v-if="render.headerRenderer" class="font-weight-bold shelf-header">
{{
render.headerRenderer.elementRenderer.newElement.type.componentType
.model.shelfHeaderModel.shelfHeaderData.title
}}
</h4>
<component
v-if="render.content && getComponents()[Object.keys(render.content)[0]]"
:is="Object.keys(render.content)[0]"
:render="render.content[Object.keys(render.content)[0]]"
></component>
<v-list-item class="pa-0 min-height-0">
<component
v-if="render.content && getComponents()[Object.keys(render.content)[0]]"
:is="Object.keys(render.content)[0]"
:render="render.content[Object.keys(render.content)[0]]"
></component
></v-list-item>
<div
v-if="render.separator && render.separator.hasBottomSeparator"
class="separator-bottom background"
:class="$vuetify.theme.dark ? 'lighten-4' : 'darken-4'"
:class="$vuetify.theme.dark ? 'lighten-1' : 'darken-1'"
:style="{ height: render.separator.height + 'px' }"
></div>
</div>

View File

@ -1,6 +1,21 @@
<template>
<v-card class="entry videoRenderer background" :to="`/watch?v=${vidId}`" flat>
<div style="position: relative" class="thumbnail-container">
<v-card
class="entry videoRenderer background"
:to="`/watch?v=${vidId}`"
:class="
$store.state.tweaks.roundTweak > 0
? $vuetify.theme.dark
? 'lighten-1'
: 'darken-1'
: ''
"
:style="{
borderRadius: `${roundTweak / 2}rem`,
margin: $store.state.tweaks.roundTweak > 0 ? '0.5rem 1rem' : '0',
}"
flat
>
<div style="position: relative" class="thumbnail-container overflow-hidden">
<v-img
:aspect-ratio="16 / 9"
:src="$youtube.getThumbnail(vidId, 'max', thumbnails)"
@ -108,6 +123,12 @@
<script>
export default {
computed: {
roundTweak() {
return this.$store.state.tweaks.roundTweak;
},
},
props: {
vidId: {
type: String,

View File

@ -56,11 +56,13 @@
</v-app>
</template>
<script>
import { App as CapacitorApp } from "@capacitor/app";
import { mapState } from "vuex";
import constants from "~/plugins/constants";
import { linkParser } from "~/plugins/utils";
import backType from "~/plugins/classes/backType";
export default {
data: () => ({
@ -92,22 +94,7 @@ export default {
},
mounted() {
//--- Back Button Listener ---//
this.backHandler = CapacitorApp.addListener(
"backButton",
({ canGoBack }) => {
//--- Back Closes Search ---//
if (this.search) {
this.search = false;
//--- Back Goes Back ---//
} else if (!canGoBack) {
CapacitorApp.exitApp();
} else {
window.history.back();
}
}
);
this.$vuetube.resetBackActions();
// --- External URL Handling --- //
CapacitorApp.addListener("appUrlOpen", (event) => {
@ -176,6 +163,17 @@ export default {
}
} else {
this.search = true;
// Adds to the back stack
const closeSearch = new backType(
() => {
this.search = false;
},
() => {
return this.search;
}
);
this.$vuetube.addBackAction(closeSearch);
}
},
},
@ -235,6 +233,14 @@ div {
vertical-align: -0.1em;
margin: 0 2px;
}
.min-height-0 {
min-height: 0 !important;
}
.fill-width {
width: 100% !important;
}
</style>
<style scoped>

View File

@ -26,7 +26,7 @@ export default {
computed: {
recommends: {
get() {
return this.$store.state.recommendedVideos;
return [...this.$store.state.recommendedVideos];
},
set(val) {
this.$store.commit("updateRecommendedVideos", val);

View File

@ -55,8 +55,11 @@ export default {
);
await theming;
this.progressMsg = "Communicating with the API";
await this.$youtube.getAPI();
this.progressMsg = "Launching";
this.progressMsg = "Launching startup tasks";
await this.$vuetube.launchBackHandling();
this.progressMsg = "Navigating to the home page";
this.$router.replace(`/${localStorage.getItem("startPage") || "home"}`); // Prevent user from navigating back to the splash screen
},

View File

@ -28,7 +28,7 @@
}"
id="content-container"
>
<v-card v-if="loaded" class="ml-2 mr-2 background rounded-0" flat>
<v-card v-if="loaded" class="px-2 background rounded-0" flat>
<div
v-ripple
class="d-flex justify-space-between align-start px-3 pt-3"
@ -152,21 +152,18 @@
</div>
<!-- Comments -->
<div
v-if="loaded && video.commentData"
@click="showComments = !showComments"
>
<v-card flat class="background comment-renderer">
<v-text class="comment-count keep-spaces">
<div v-if="loaded && video.commentData" @click="toggleComment">
<v-card flat tile class="background comment-renderer py-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">
<strong :key="text.text">{{ text.text }}</strong>
</template>
<template v-else>{{ text.text }}</template>
</template>
</v-text>
<v-icon v-if="showComments">mdi-unfold-less-horizontal</v-icon>
<v-icon v-else>mdi-unfold-more-horizontal</v-icon>
</v-card-text>
<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>
@ -209,13 +206,13 @@ 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 legacyPlayer from "~/components/Player/legacy.vue";
import vuetubePlayer from "~/components/Player/index.vue";
import ShelfRenderer from "~/components/SectionRenderers/shelfRenderer.vue";
import mainCommentRenderer from "~/components/Comments/mainCommentRenderer.vue";
import SwipeableBottomSheet from "~/components/ExtendedComponents/swipeableBottomSheet";
import { App as CapacitorApp } from "@capacitor/app";
import backType from "~/plugins/classes/backType";
export default {
components: {
@ -259,29 +256,11 @@ export default {
mounted() {
this.mountedInit();
this.backHandler = CapacitorApp.addListener(
"backButton",
({ canGoBack }) => {
//--- Back Closes Search ---//
if (this.showComments) {
this.showComments = false;
//--- Back Goes Back ---//
} else if (!canGoBack) {
this.$router.replace(
`/${localStorage.getItem("startPage") || "home"}`
);
} else {
window.history.back();
}
}
);
this.$vuetube.resetBackActions();
},
beforeDestroy() {
clearInterval(this.interval);
if (this.backHandler) this.backHandler.remove();
},
methods: {
@ -412,6 +391,7 @@ export default {
interval: null,
video: null,
useBetaPlayer: false,
backHierarchy: [],
};
},
@ -426,6 +406,22 @@ export default {
scrollable.scrollTo(0, 0);
});
},
// Toggle this.showComments to true or false. If it is true, then add the dismiss function to backStack.
toggleComment() {
this.showComments = !this.showComments;
if (this.showComments) {
const dismissComment = new backType(
() => {
this.showComments = false;
},
() => {
return this.showComments;
}
);
this.$vuetube.addBackAction(dismissComment);
}
},
},
};
</script>

View File

@ -0,0 +1,61 @@
import { App as CapacitorApp } from "@capacitor/app";
import backType from "./backType";
export default class backHandler {
constructor() {
this.backStack = []; // This should only contain instances of backType. Any other type will be ignored.
this.startUp();
}
startUp() {
this.reset();
// Add a listener for the back button.
this.backHandler = CapacitorApp.addListener("backButton", (context) => { this.back(context) });
// Start garbage collection. Run every 5 minutes.
setInterval(() => {
() => { this.garbageCollect };
}, 5 * 60 * 1000);
}
reset() {
this.backStack = [];
}
back({ canGoBack }) {
console.log("backStack", this.backStack)
// Check if backStack contains any backType objects. If so, call the goBack() function.
if (this.backStack.length > 0) {
// Loop through the backStack array.
let lastResult = false;
while (!lastResult && this.backStack.length > 0) {
const backAction = this.backStack.pop();
lastResult = backAction.goBack();
}
// Since a function was successfully called, no need to continue.
if (lastResult) return;
}
if (!canGoBack) {
// If we can't go back, then we should exit the app.
CapacitorApp.exitApp();
} else {
// If we can go back, then we should go back.
window.history.back();
}
}
addAction(callback) {
if (callback instanceof backType) {
this.backStack.push(callback);
} else {
throw new TypeError("backType object expected");
}
}
// Loops through the backStack array if array larger than 10. If backType.check() returns false, then remove it from the backStack array.
garbageCollect() {
if (this.backStack.length > 10) {
this.backStack = this.backStack.filter((backType) => backType.check());
}
}
}

View File

@ -0,0 +1,16 @@
// This is the class that populates the backStack array in the backHandler class.
export default class backType {
constructor(callback, check) {
this.callback = callback;
this.check = check || (() => true);
}
goBack() {
if (this.check()) {
this.callback();
return true;
} else {
return false;
}
}
}

View File

@ -33,6 +33,7 @@ module.exports = {
recommendations: "Recommendations",
init: "Initialize",
innertube: "Innertube",
channel: "Channel",
},
INNERTUBE_HEADER: (info) => {

View File

@ -76,18 +76,23 @@ class Innertube {
//--- API Calls ---//
async browseAsync(action_type) {
async browseAsync(action_type, args) {
let data = { context: this.context };
switch (action_type) {
case "recommendations":
data.browseId = "FEwhat_to_watch";
args.browseId = "FEwhat_to_watch";
break;
case "playlist":
data.browseId = args.browse_id;
break;
case "channel":
if (args && args.browseId) {
break;
} else {
throw new ReferenceError("No browseId provided");
}
default:
}
data.browseId = { ...data, args };
console.log(data);
@ -217,6 +222,28 @@ class Innertube {
};
}
async getEndPoint(url) {
let data = { context: this.context, url: url };
const response = await Http.post({
url: `${constants.URLS.YT_BASE_API}/navigation/resolve_url?key=${this.key}`,
data: data,
headers: { "Content-Type": "application/json" },
}).catch((error) => error);
if (response instanceof Error)
return {
success: false,
status_code: response.status,
message: response.message,
};
return {
success: true,
status_code: response.status,
data: response.data,
};
}
// WARNING: This is tracking the user's activity, but is required for recommendations to properly work
async apiStats(params, url) {
console.log(params);
@ -252,10 +279,24 @@ class Innertube {
// Simple Wrappers
async getRecommendationsAsync() {
const rec = await this.browseAsync("recommendations");
console.log(rec.data);
return rec;
}
async getChannelAsync(url) {
const channelEndpoint = await this.getEndPoint(url);
if (
channelEndpoint.success &&
channelEndpoint.data.endpoint?.browseEndpoint
) {
return await this.browseAsync(
"channel",
channelEndpoint.data.endpoint?.browseEndpoint
);
} else {
throw new ReferenceError("Cannot find channel");
}
}
async VidInfoAsync(id) {
let response = await this.getVidAsync(id);
@ -298,7 +339,9 @@ class Innertube {
isLive: details.isLiveContent,
channelName: details.author,
channelSubs: ownerData?.collapsedSubtitle?.runs[0]?.text,
channelUrl: rendererUtils.getNavigationEndpoints(ownerData),
channelUrl: rendererUtils.getNavigationEndpoints(
ownerData.navigationEndpoint
),
channelImg: ownerData?.thumbnail?.thumbnails[0].url,
availableResolutions: resolutions?.formats,
availableResolutionsAdaptive: resolutions?.adaptiveFormats,

View File

@ -1,25 +1,21 @@
// General utility functions for the renderers
class rendererUtils {
static getNavigationEndpoints(base) {
const navEndpoint = base.navigationEndpoint;
if (!navEndpoint) return;
if (navEndpoint.urlEndpoint) {
const params = new Proxy(
new URLSearchParams(navEndpoint.urlEndpoint.url),
{
get: (searchParams, prop) => searchParams.get(prop),
}
);
if (!base) return;
if (base.urlEndpoint) {
const params = new Proxy(new URLSearchParams(base.urlEndpoint.url), {
get: (searchParams, prop) => searchParams.get(prop),
});
if (params.q) return decodeURI(params.q);
else return new URL(navEndpoint.urlEndpoint.url).pathname;
} else if (navEndpoint.browseEndpoint) {
return navEndpoint.browseEndpoint.canonicalBaseUrl;
} else if (navEndpoint.watchEndpoint) {
return `/watch?v=${navEndpoint.watchEndpoint.videoId}`;
} else if (navEndpoint.navigationEndpoint) {
else return new URL(base.urlEndpoint.url).pathname;
} else if (base.browseEndpoint) {
return base.browseEndpoint.canonicalBaseUrl;
} else if (base.watchEndpoint) {
return `/watch?v=${base.watchEndpoint.videoId}`;
} else if (base.navigationEndpoint) {
return; //for now
} else if (navEndpoint.searchEndpoint) {
return `/search?q=${encodeURI(navEndpoint.searchEndpoint.query)}`;
} else if (base.searchEndpoint) {
return `/search?q=${encodeURI(base.searchEndpoint.query)}`;
}
}

View File

@ -6,6 +6,7 @@ import constants from "./constants";
import { hexToRgb, rgbToHex, parseEmoji } from "./utils";
import { Haptics, ImpactStyle } from "@capacitor/haptics";
import Vue from "vue";
import backHandler from "./classes/backHander";
Vue.directive("emoji", {
inserted: function (el) {
@ -14,6 +15,8 @@ Vue.directive("emoji", {
},
});
let backActions;
const module = {
//--- Get GitHub Commits ---//
commits: new Promise((resolve, reject) => {
@ -109,6 +112,23 @@ const module = {
rgbToHex(r, g, b) {
return rgbToHex(r, g, b);
},
async launchBackHandling() {
backActions = new backHandler();
return true;
},
resetBackActions() {
backActions.reset();
},
addBackAction(action) {
backActions.addAction(action);
},
back(listenerFunc) {
backActions.back(listenerFunc);
},
};
//--- Start ---//

View File

@ -109,6 +109,15 @@ const innertubeModule = {
else return `https://img.youtube.com/vi/${id}/mqdefault.jpg`;
},
async getChannel(url) {
try {
const response = await InnertubeAPI.getChannelAsync(url);
return response.data;
} catch (error) {
logger(constants.LOGGER_NAMES.channel, error, true);
}
},
// It just works™
// Front page recommendation
async recommend() {