Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Trubbel’s Utilities 1.0.0 #259

Merged
merged 3 commits into from
Feb 14, 2025
Merged

Conversation

Trubbel
Copy link
Contributor

@Trubbel Trubbel commented Jan 7, 2025

  • Show custom timestamp for clips and videos
  • Video preview for thumbnails in the directory
  • Automatically reset player on error
  • Collapse drops in the inventory page
    …etc

// Load collapsed state from localStorage
function loadCollapsedState() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use FFZ's settings provider for storing values rather than localStorage.

this.settings.provider.get(key);
this.settings.provider.set(key, value);
this.settings.provider.has(key);
this.settings.provider.delete(key);

});

// Start observing
observer.observe(document.body, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This observer does not need to run if you aren't on twitch.tv/drops/inventory.

You can use, and in fact I see you are using, the site.router:route event to detect navigation changes within the React app and use that to check the current URL and decide to enable or disable your logic, rather than just leave it running all the time. But you don't have any code that stops the observer when the user leaves that page.

@@ -0,0 +1,5 @@
query FFZ_GetClipDate($slug: ID!) {
clip(slug: $slug) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should always include the id column when fetching something like a clip model to that Apollo's caching layer stays happy.

@@ -0,0 +1,5 @@
query FFZ_GetVideoDate($id: ID!) {
video(id: $id) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should always include the id column when fetching something like a video model to that Apollo's caching layer stays happy. Yes, even though the query includes the id.

});

try {
if (result?.data?.clip && result?.data?.clip.createdAt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just do if (result?.data?.clip?.createdAt)

defaultDateTimeFormat: this.settings.get("addon.trubbel.format.datetime")
});

this.on("addon.trubbel.format.datetime-clip", () => this.setClipOrVideoTimestamp());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is supposed to be settings:changed:addon.trubbel.format.datetime-clip and not just addon.trubbel.format.datetime-clip? Similar issue with the line below this.

});

this.on("addon.trubbel.format.datetime-clip", () => this.setClipOrVideoTimestamp());
this.on("addon.trubbel.format.datetime-video", () => this.setClipOrVideoTimestamp());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of these event listeners have unnecessary lambdas. You can just do this.on(some_event, this.handler) and it will work. If you don't supply a context as the third argument, the context (aka what this is when the handler is called) will be set to the this from this.on() by default.

if (metadataElement) {
const hasError = errorCodes.some(code => metadataElement.textContent.includes(code));
if (hasError) {
this.site.children.player.resetPlayer(this.site.children.player.current);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main issue with this code is that there isn't really a good safety check against it potentially breaking and causing a stampede. There should be a limit so that it can only try resetting the player so many times within a certain time window.

While reducing frustration for the user is good, we also want to ensure we can't accidentally create excessive load on Twitch's servers.



onDisable() {
if (this.handleMouseEnterListener) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of event listeners and potentially long running code that isn't disabled in this onDisable method. It may as well not be here in its current state since a user will need to refresh the page to fully clean out this add-on's side effects.

}

handleMouseLeave(event) {
handleMouseLeave(event);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the point of this is, to be honest. If you really want handleMouseLeave to be accessible under your module you could just do this.handleMouseLeave = handleMouseLeave; in your constructor or even assign it to the prototype. This is just an extra layer of function when you're already using an arrow function elsewhere where actually binding this method?

@Trubbel
Copy link
Contributor Author

Trubbel commented Feb 12, 2025

Had made a few changes beforehand. Hopefully this is better.

@Trubbel Trubbel requested a review from SirStendec February 14, 2025 13:05
Copy link
Collaborator

@SirStendec SirStendec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking better! Just a few more things to bring up. I should've gone over the GQL better the first time, so sorry about that.

The GQL requests are the only thing I consider a real blocker here, though I'd suggest also addressing the change to settings (removing an unnecessary JSON serialization) since changing that in the future, once this is in user hands, would be more annoying.


removeCommandHandlers() {
for (const handler of this.commandHandlers) {
ffz.off("chat:get-tab-commands", handler);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using the window global ffz to register/unregister events? You're already working within a Module so you can just use this.on(...) and this.off(...)


saveCollapsedState(state) {
try {
this.settings.provider.set(STORAGE_KEY_CONFIG, JSON.stringify(state));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, FFZ's setting providers can store JSON safe objects, you don't need to run stringify / parse yourself.

updateCSS() {
// Remove/Hide Things - Stream - Remove the Gradient from Player
if (this.settings.get("addon.trubbel.remove-things.player-gradient")) {
this.css_tweaks.set("hide-player-gradient", ".video-player .top-bar, .video-player .player-controls { background: transparent !important; }");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, is there any reason you're using css_tweaks to store your css when you have a custom ManagedStyle instance you could be setting these in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little bit of being stupid, and a little bit of starting something and not finishing it. And going back and forth.
(Just like with the translation core)

@@ -0,0 +1,10 @@
query FFZ_GetUserFollowAge($login: String!) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query is redundant. FFZ's twitch_data module has a getUserFollowed(id, login) method that returns followedAt.

@@ -0,0 +1,6 @@
query FFZ_GetUserAccountAge($login: String!) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to mention it before, but please set the unique prefix of your gql query names to something other than FFZ, like Trubbel perhaps. This follows the pattern established by the Deck add-on and helps if we need to identify the source of a specific query.

}

onEnable() {
this._ = new NewTransCore({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through the code again and I don't think you're ever actually using this for anything? Just assigning a format to it. You should probably drop creating your own translation core and instead access i18n._ in that one specific place where you want to iterate the available formats.


async waitForElements() {
return new Promise((resolve) => {
const checkElements = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it doesn't end up finding what it wants, this code could end up running once per frame until the page is closed/reloaded. I would suggest adding a timeout.

@Trubbel Trubbel requested a review from SirStendec February 14, 2025 21:57
@SirStendec SirStendec merged commit 5008a39 into FrankerFaceZ:master Feb 14, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants