generated from JohnBra/vite-web-extension
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
16dbcf4
commit 34601c0
Showing
19 changed files
with
708 additions
and
233 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,54 @@ | ||
// TODO: Code button placement code for below player, left controls and right controls | ||
import type { GetIconType } from "@/src/icons"; | ||
import type { ButtonPlacement, FeaturesThatHaveButtons } from "@/src/types"; | ||
|
||
import { waitForSpecificMessage } from "@/src/utils/utilities"; | ||
|
||
import { addFeatureItemToMenu, removeFeatureItemFromMenu } from "../featureMenu/utils"; | ||
import { type ListenerType, makeFeatureButton, placeButton } from "./utils"; | ||
|
||
export async function addFeatureButton< | ||
Name extends FeaturesThatHaveButtons, | ||
Placement extends ButtonPlacement, | ||
Label extends string, | ||
Toggle extends boolean | ||
>(featureName: Name, placement: Placement, label: Label, icon: GetIconType<Name, Placement>, listener: ListenerType<Toggle>, isToggle: boolean) { | ||
switch (placement) { | ||
case "feature_menu": { | ||
if (icon instanceof SVGSVGElement) await addFeatureItemToMenu(featureName, label, icon, listener, isToggle); | ||
break; | ||
} | ||
case "below_player": | ||
case "player_controls_left": | ||
case "player_controls_right": { | ||
const button = makeFeatureButton(featureName, placement, label, icon, listener, isToggle); | ||
placeButton(button, placement); | ||
break; | ||
} | ||
} | ||
} | ||
export async function removeFeatureButton(featureName: FeaturesThatHaveButtons) { | ||
// Wait for the "options" message from the content script | ||
const optionsData = await waitForSpecificMessage("options", "request_data", "content"); | ||
if (!optionsData) return; | ||
const { | ||
data: { options } | ||
} = optionsData; | ||
// Extract the necessary properties from the options object | ||
const { | ||
button_placements: { [featureName]: buttonPlacement } | ||
} = options; | ||
switch (buttonPlacement) { | ||
case "feature_menu": { | ||
removeFeatureItemFromMenu(featureName); | ||
break; | ||
} | ||
case "below_player": | ||
case "player_controls_left": | ||
case "player_controls_right": { | ||
const button = document.querySelector<HTMLButtonElement>(`#yte-feature-${featureName}-button`); | ||
if (!button) return; | ||
button.remove(); | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { type GetIconType } from "@/src/icons"; | ||
import { type ButtonPlacement, type FeaturesThatHaveButtons } from "@/src/types"; | ||
import eventManager from "@/src/utils/EventManager"; | ||
import { createStyledElement, createTooltip } from "@/src/utils/utilities"; | ||
|
||
import { getFeatureIds } from "../featureMenu/utils"; | ||
// TODO: fix icon type for toggle buttons | ||
|
||
export type ListenerType<Toggle extends boolean> = Toggle extends true ? (checked?: boolean) => void : () => void; | ||
|
||
function buttonClickListener<Placement extends ButtonPlacement, Name extends FeaturesThatHaveButtons, Toggle extends boolean>( | ||
button: HTMLButtonElement, | ||
icon: GetIconType<Name, Placement>, | ||
listener: ListenerType<Toggle>, | ||
isToggle: boolean | ||
) { | ||
if (isToggle) { | ||
button.ariaChecked = button.ariaChecked ? (!JSON.parse(button.ariaChecked)).toString() : "false"; | ||
// TODO: add language strings for toggle features and update the tooltip text | ||
if (typeof icon === "object" && "off" in icon && "on" in icon) { | ||
updateFeatureButtonIcon(button, JSON.parse(button.ariaChecked) ? icon.on : icon.off); | ||
} else if (typeof icon === "object" && icon instanceof SVGSVGElement) { | ||
updateFeatureButtonIcon(button, icon); | ||
} | ||
listener(JSON.parse(button.ariaChecked) as boolean); | ||
} else { | ||
listener(); | ||
} | ||
} | ||
|
||
export function makeFeatureButton<Name extends FeaturesThatHaveButtons, Placement extends ButtonPlacement, Toggle extends boolean>( | ||
featureName: Name, | ||
placement: Placement, | ||
label: string, | ||
icon: GetIconType<Name, Placement>, | ||
listener: ListenerType<Toggle>, | ||
isToggle: boolean | ||
) { | ||
if (placement === "feature_menu") throw new Error("Cannot make a feature button for the feature menu"); | ||
const buttonExists = document.querySelector(`button#${getFeatureButtonId(featureName)}`) !== null; | ||
// TODO: fix right controls button making control buttons overflow | ||
const button = createStyledElement({ | ||
classlist: ["ytp-button"], | ||
elementId: `${getFeatureButtonId(featureName)}`, | ||
elementType: "button", | ||
styles: { | ||
alignContent: "center", | ||
display: "flex", | ||
flexWrap: "wrap", | ||
height: "48px", | ||
justifyContent: "center", | ||
padding: "0px 4px", | ||
width: "48px" | ||
} | ||
}); | ||
const { listener: tooltipListener } = createTooltip({ | ||
direction: placement === "below_player" ? "up" : "up", | ||
element: button, | ||
featureName, | ||
id: `yte-feature-${featureName}-tooltip`, | ||
text: label | ||
}); | ||
if (buttonExists) { | ||
eventManager.removeEventListener(button, "click", featureName); | ||
eventManager.addEventListener(button, "click", () => buttonClickListener<Placement, Name, Toggle>(button, icon, listener, isToggle), featureName); | ||
eventManager.removeEventListener(button, "mouseover", featureName); | ||
eventManager.addEventListener(button, "mouseover", tooltipListener, featureName); | ||
return button; | ||
} | ||
|
||
button.dataset.title = label; | ||
if (isToggle) { | ||
button.ariaChecked = "false"; | ||
if (typeof icon === "object" && "off" in icon && "on" in icon) { | ||
button.append(icon.off); | ||
} else if (typeof icon === "object" && icon instanceof SVGSVGElement) { | ||
button.append(icon); | ||
} | ||
} | ||
|
||
eventManager.addEventListener(button, "mouseover", tooltipListener, featureName); | ||
eventManager.addEventListener(button, "click", () => buttonClickListener<Placement, Name, Toggle>(button, icon, listener, isToggle), featureName); | ||
return button; | ||
} | ||
function updateFeatureButtonIcon(button: HTMLButtonElement, icon: SVGElement) { | ||
if (button.firstChild) { | ||
button.firstChild.remove(); | ||
button.append(icon); | ||
} | ||
} | ||
export function placeButton(button: HTMLButtonElement, placement: Exclude<ButtonPlacement, "feature_menu">) { | ||
switch (placement) { | ||
case "below_player": { | ||
const player = document.querySelector<HTMLDivElement>("div#primary > div#primary-inner > div#player"); | ||
if (!player) return; | ||
const buttonContainerExists = document.querySelector<HTMLDivElement>(`#${buttonContainerId}`) !== null; | ||
const buttonContainer = createStyledElement({ | ||
elementId: buttonContainerId, | ||
elementType: "div", | ||
styles: { | ||
display: "flex", | ||
justifyContent: "center" | ||
} | ||
}); | ||
buttonContainer.append(button); | ||
if (buttonContainerExists) return; | ||
player.insertAdjacentElement("afterend", buttonContainer); | ||
break; | ||
} | ||
case "player_controls_left": { | ||
const leftControls = document.querySelector<HTMLDivElement>(".ytp-left-controls"); | ||
if (!leftControls) return; | ||
leftControls.append(button); | ||
break; | ||
} | ||
case "player_controls_right": { | ||
const rightControls = document.querySelector<HTMLDivElement>(".ytp-right-controls"); | ||
if (!rightControls) return; | ||
rightControls.prepend(button); | ||
break; | ||
} | ||
} | ||
} | ||
export function checkIfFeatureButtonExists(featureName: FeaturesThatHaveButtons, placement: ButtonPlacement): boolean { | ||
switch (placement) { | ||
case "below_player": { | ||
const buttonContainer = document.querySelector<HTMLDivElement>(`#${buttonContainerId}`); | ||
if (!buttonContainer) return false; | ||
return buttonContainer.querySelector<HTMLButtonElement>(`#${getFeatureButtonId(featureName)}`) !== null; | ||
} | ||
case "player_controls_left": { | ||
const leftControls = document.querySelector<HTMLDivElement>(".ytp-left-controls"); | ||
if (!leftControls) return false; | ||
return leftControls.querySelector<HTMLButtonElement>(`#${getFeatureButtonId(featureName)}`) !== null; | ||
} | ||
case "player_controls_right": { | ||
const rightControls = document.querySelector<HTMLDivElement>(".ytp-right-controls"); | ||
if (!rightControls) return false; | ||
return rightControls.querySelector<HTMLButtonElement>(`#${getFeatureButtonId(featureName)}`) !== null; | ||
} | ||
case "feature_menu": { | ||
const featureMenu = document.querySelector<HTMLDivElement>("#yte-feature-menu"); | ||
if (!featureMenu) return false; | ||
return featureMenu.querySelector<HTMLDivElement>(`#${getFeatureIds(featureName).featureMenuItemId}`) !== null; | ||
} | ||
} | ||
} | ||
export function getFeatureButtonId(featureName: FeaturesThatHaveButtons) { | ||
return `yte-feature-${featureName}-button` as const; | ||
} | ||
export const buttonContainerId = "yte-button-container"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import type { FeaturesThatHaveButtons } from "../types"; | ||
|
||
import { addLoopButton, removeLoopButton } from "./loopButton"; | ||
import { addMaximizePlayerButton, removeMaximizePlayerButton } from "./maximizePlayerButton"; | ||
import { addOpenTranscriptButton, removeOpenTranscriptButton } from "./openTranscriptButton/utils"; | ||
import { addScreenshotButton, removeScreenshotButton } from "./screenshotButton"; | ||
import { addVolumeBoostButton, removeVolumeBoostButton } from "./volumeBoost"; | ||
|
||
export const featureButtonFunctions = { | ||
loopButton: { | ||
add: addLoopButton, | ||
remove: removeLoopButton | ||
}, | ||
maximizePlayerButton: { | ||
add: addMaximizePlayerButton, | ||
remove: removeMaximizePlayerButton | ||
}, | ||
openTranscriptButton: { | ||
add: addOpenTranscriptButton, | ||
remove: removeOpenTranscriptButton | ||
}, | ||
screenshotButton: { | ||
add: addScreenshotButton, | ||
remove: removeScreenshotButton | ||
}, | ||
volumeBoostButton: { | ||
add: addVolumeBoostButton, | ||
remove: removeVolumeBoostButton | ||
} | ||
} satisfies Record< | ||
FeaturesThatHaveButtons, | ||
{ | ||
add: (() => Promise<void>) | (() => void); | ||
remove: (() => Promise<void>) | (() => void); | ||
} | ||
>; |
Oops, something went wrong.