Skip to content

Commit

Permalink
Add sidebar-tooltip stream previews (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
Melonify authored Apr 7, 2023
1 parent 2278182 commit 0f52a39
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG-nightly.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added hot-patching functionality to the extension
- Added chat rich embeds which allows twitch clips to preview in chat
- Added a "Copy Message" button
- Added an option to show thumbnail previews of streams when hovering over channels on the sidebar
- Fixed an issue which caused flickering when hovering on a deleted message

### Version 3.0.3.1000
Expand Down Expand Up @@ -55,6 +56,7 @@
- Fixed an issue which caused duplicate colon-complete matches with FFZ
- Fixed an issue that caused tab-completion to break with FFZ
- Fixed an issue where emotes in colon-complete wouldn't load on Mozilla Firefox
- Added visual settings for highlighted messages, allows changing opacity and over-all style

### Version 3.0.0.19000 (Beta 19 RC)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Improve your viewing experience on Twitch & YouTube with new features, emotes, vanity and performance.",
"private": true,
"version": "3.0.4",
"dev_version": "5.0",
"dev_version": "6.0",
"scripts": {
"start": "NODE_ENV=dev yarn build:dev && NODE_ENV=dev vite --mode dev",
"build:section:app": "vite build --config vite.config.ts",
Expand Down
112 changes: 112 additions & 0 deletions src/site/twitch.tv/modules/sidebar-previews/SidebarCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template />

<script setup lang="ts">
import { onUnmounted, ref, toRaw, watch } from "vue";
import { REACT_TYPEOF_TOKEN } from "@/common/Constant";
import { HookedInstance } from "@/common/ReactHooks";
import { defineFunctionHook, definePropertyHook, unsetPropertyHook } from "@/common/Reflection";
import { useConfig } from "@/composable/useSettings";
const props = defineProps<{
instance: HookedInstance<Twitch.SidebarCardComponent>;
}>();
const showPreviews = useConfig<boolean>("ui.sidebar_previews");
const tooltipContent = ref<ReactExtended.ReactRuntimeElement>();
definePropertyHook(props.instance.component, "props", {
value: (v: Twitch.SidebarCardComponent["props"]) => {
definePropertyHook(v, "tooltipContent", {
value: (v) => {
if (typeof v == "object") {
tooltipContent.value = v;
}
},
});
},
});
watch(
tooltipContent,
(tooltip, old) => {
if (tooltip != old) {
if (old && typeof old.type == "function") unsetPropertyHook(old.type.prototype, "render");
if (tooltip && typeof tooltip.type == "function") {
defineFunctionHook(tooltip.type.prototype, "render", function (old, ...args: unknown[]) {
const vnode = old?.apply(this, args);
return patchTooltip(this, vnode);
});
rerenderCard();
}
}
},
{ immediate: true },
);
watch(showPreviews, () => rerenderCard());
function rerenderCard() {
toRaw(props.instance.component).forceUpdate();
}
function patchTooltip(tooltip: ReactExtended.ReactRuntimeElement, vnode: ReactExtended.ReactRuntimeElement) {
if (!showPreviews.value) return vnode;
const body = vnode?.props?.children;
if (!body || !body.props?.children) return vnode;
body.props.style = { width: "20rem" };
body.props.children.splice(2, 0, {
[REACT_TYPEOF_TOKEN]: Symbol.for("react.element"),
ref: null,
key: null,
type: "div",
props: {
className: "seventv-sidebar-tooltip-preview",
style: {
backgroundImage: getThumbnail(tooltip.props.channelDisplayName.toLowerCase()),
},
},
});
return vnode;
}
function getThumbnail(channel: string) {
let url = `https://static-cdn.jtvnw.net/previews-ttv/live_user_${channel}-190x107.jpg`;
url += `?${Math.floor(Date.now() / 300000)}`;
return `url("${url}")`;
}
onUnmounted(() => {
unsetPropertyHook(props.instance.component, "props");
if (props.instance.component?.props) {
unsetPropertyHook(props.instance.component.props, "tooltipContent");
}
if (typeof tooltipContent.value?.type == "function") {
unsetPropertyHook(tooltipContent.value.type.prototype, "type");
}
});
</script>

<style lang="scss">
.seventv-sidebar-tooltip-preview {
margin: 2px 0;
border-radius: 4px;
width: 100%;
padding-bottom: 56.25%;
background-color: var(--color-background-placeholder);
background-size: contain;
background-position: center;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<template v-for="inst in sidebarCard.instances" :key="inst.identifier">
<SidebarCard :instance="inst" />
</template>
</template>

<script setup lang="ts">
import { declareModule } from "@/composable/useModule";
import { declareConfig } from "@/composable/useSettings";
import { useComponentHook } from "@/common/ReactHooks";
import SidebarCard from "./SidebarCard.vue";
const { markAsReady } = declareModule("sidebar-previews", {
name: "Sidebar Previews",
depends_on: ["settings"],
});
const sidebarCard = useComponentHook<Twitch.SidebarCardComponent>({
parentSelector: ".side-nav-section",
predicate: (n) => n.props && n.props.title && n.props.isWatchParty !== null && n.props.linkTo,
});
markAsReady();
</script>

<script lang="ts">
export const config = [
declareConfig("ui.sidebar_previews", "TOGGLE", {
path: ["Appearance", "Interface"],
label: "Sidebar Stream Thumbnails",
hint: "Show stream thumbnails when hovering over streams on the sidebar.",
defaultValue: false,
}),
];
</script>
2 changes: 2 additions & 0 deletions src/types/tw.module.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type ChatModuleVue from "@/site/twitch.tv/modules/chat/ChatModule.vue";
import type EmoteMenuModuleVue from "@/site/twitch.tv/modules/emote-menu/EmoteMenuModule.vue";
import type ModLogsModule from "@/site/twitch.tv/modules/mod-logs/ModLogsModule.vue";
import type SettingsModuleVue from "@/site/twitch.tv/modules/settings/SettingsModule.vue";
import type SidebarPreviewsModuleVue from "@/site/twitch.tv/modules/sidebar-previews/SidebarPreviewsModule.vue";

declare type TwModuleID = keyof TwModuleComponentMap;

Expand All @@ -16,6 +17,7 @@ declare type TwModuleComponentMap = {
"chat-vod": typeof ChatVodModuleVue;
"emote-menu": typeof EmoteMenuModuleVue;
"mod-logs": typeof ModLogsModule;
"sidebar-previews": typeof SidebarPreviewsModuleVue;
autoclaim: typeof AutoclaimModuleVue;
avatars: typeof AvatarsModuleVue;
chat: typeof ChatModuleVue;
Expand Down
38 changes: 38 additions & 0 deletions src/types/twitch.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,44 @@ declare module Twitch {
}[T];
};

export type SidebarCardComponent = ReactExtended.WritableComponent<{
avatarAlt: string;
avatarSrc: string;
collapsed: boolean;
"data-a-id": string;
"data-test-selector": string;
dispatch: () => unknown;
isInspecting: boolean;
isPromoted: boolean;
isWatchParty: boolean;
latencyTracking: {};
latencyTrackingConfig: { isNonCritical: boolean };
linkTo: {
pathname: string;
state: {
channelView: string;
content: string;
content_index: number;
item_tracking_id: string;
medium: string;
};
};
metadataLeft: string | ReactExtended.ReactRuntimeElement;
metadataRight: string | ReactExtended.ReactRuntimeElement;
offline: boolean;
onClick: () => void;
onContextMenu: () => void;
onMiddleClick: () => void;
primaryColorHex: string;
register: () => void;
rootTrackerExists: boolean;
title: string;
titleElement: string | ReactExtended.ReactRuntimeElement;
tooltipContent: string | ReactExtended.ReactRuntimeElement;
unregister: () => void;
userLogin: string;
}>;

export enum Theme {
"Light",
"Dark",
Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const ignoreHMR = [
"ChatInputModule.vue",
"EmoteMenuModule.vue",
"SettingsModule.vue",
"SidebarPreviewsModule.vue",
];

const alwaysHot = ["src/background/background.ts", "src/content/content.ts", "src/content/emoji.ts"];
Expand Down

0 comments on commit 0f52a39

Please sign in to comment.