Merge branch 'dev'

This commit is contained in:
thororen1234 2025-08-09 17:38:42 -04:00
commit 65979b31f2
No known key found for this signature in database
8 changed files with 77 additions and 206 deletions

View file

@ -261,10 +261,18 @@ export default function PluginSettings() {
.sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
const [currentPage, setCurrentPage] = React.useState(1);
const pageSize = 36;
const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
const onSearch = (query: string) => {
setSearchValue(prev => ({ ...prev, value: query }));
setCurrentPage(1);
};
const onStatusChange = (status: SearchStatus) => {
setSearchValue(prev => ({ ...prev, status }));
setCurrentPage(1);
};
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const { status } = searchValue;
@ -469,6 +477,17 @@ export default function PluginSettings() {
const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin).length;
const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin).length;
const paginatedPlugins = plugins.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
const totalPages = Math.ceil(plugins.length / pageSize);
function goToPage(page: number) {
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
}
return (
<SettingsTab title="Plugins">
@ -518,17 +537,38 @@ export default function PluginSettings() {
{plugins.length || requiredPlugins.length
? (
<div className={cl("grid")}>
{plugins.length
? plugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div>
<>
<div className={cl("grid")}>
{paginatedPlugins.length
? paginatedPlugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div>
{totalPages > 1 && (
<div className={cl("page-buttons")}>
<Button
size={Button.Sizes.SMALL}
disabled={currentPage === 1}
onClick={() => goToPage(currentPage - 1)}
>
Prev
</Button>
<Text>{`Page ${currentPage} of ${totalPages}`}</Text>
<Button
size={Button.Sizes.SMALL}
disabled={currentPage === totalPages}
onClick={() => goToPage(currentPage + 1)}
>
Next
</Button>
</div>
)}
</>
)
: <ExcludedPluginsList search={search} />
}
<Forms.FormDivider className={Margins.top20} />
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>

View file

@ -279,3 +279,11 @@
flex-direction: column;
gap: 1.25em;
}
.vc-plugins-page-buttons {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}

View file

@ -59,5 +59,21 @@ export default definePlugin({
},
predicate: () => settings.store.userPopout
},
{
find: ".SIDEBAR}),nicknameIcons",
replacement: {
match: /(?<=(\i)\.id\)\}\)\),(\i).*?,)\i\?.{0,250}\i\.card\}\)/,
replace: "$self.showAllActivitiesComponent({ activity: $2, user: $1 })"
},
predicate: () => settings.store.userPopout
},
{
find: "#{intl::STATUS_MENU_LABEL}",
replacement: {
match: /(?<=,(\i)=.{0,10}\i\.id.{0,150}userId:(\i).*?,)\i\?.{0,250}onClose:\i\}\)/,
replace: "$self.showAllActivitiesComponent({ activity: $1, user: $2 })"
},
predicate: () => settings.store.userPopout
}
],
});

View file

@ -159,16 +159,6 @@ export const settings = definePluginSettings({
default: 0,
...sliderOptions
},
SyncMode: {
description: "Lyrics sync mode",
type: OptionType.SELECT,
options: [
{ value: "line", label: "Line" },
{ value: "word", label: "Word", default: true },
{ value: "character", label: "Character" },
],
default: "word",
},
PurgeLyricsCache: {
description: "Purge the lyrics cache",
type: OptionType.COMPONENT,

View file

@ -158,90 +158,4 @@
.eq-tidal-lyrics-modal-no-lyrics {
text-align: center;
padding: 1rem;
}
/* Character sync styles */
.eq-tidal-lyrics-char {
color: var(--text-muted);
padding: 0 1px;
display: inline-block;
transition: color .15s ease, opacity .15s ease, transform .15s ease, text-shadow .15s ease;
will-change: transform, color, opacity, text-shadow;
}
.eq-tidal-lyrics-char-active {
color: var(--brand-experiment);
font-weight: 600;
text-shadow: 0 0 .25em currentcolor;
}
.eq-tidal-lyrics-word {
color: var(--text-muted);
border-radius: 4px;
padding: 0 1px;
margin-right: 2px;
border: 1px solid transparent;
display: inline-block;
transition: color .15s ease, opacity .15s ease, background-color .15s ease, box-shadow .15s ease, transform .15s ease;
will-change: transform, color, opacity, background-color, box-shadow;
}
.eq-tidal-lyrics-word:last-child {
margin-right: 0;
}
.eq-tidal-lyrics-word-active {
color: var(--brand-experiment);
background: none;
box-shadow: none;
text-shadow: 0 0 .25em currentcolor;
}
.eq-tidal-lyrics-current,
.eq-tidal-lyrics-next,
.eq-tidal-lyrics-prev {
transition: color .2s ease, opacity .2s ease, transform .2s ease, text-shadow .2s ease;
}
.eq-tidal-lyrics-current {
font-weight: 600;
text-shadow: 0 0 .35em rgb(0 0 0 / 15%);
}
.eq-tidal-lyrics-next {
opacity: .85;
}
.eq-tidal-lyrics-prev {
opacity: .65;
}
.eq-tidal-lyrics-current .eq-tidal-lyrics-char,
.eq-tidal-lyrics-current .eq-tidal-lyrics-word {
color: var(--text-normal);
}
.eq-tidal-lyrics-next .eq-tidal-lyrics-char,
.eq-tidal-lyrics-prev .eq-tidal-lyrics-char,
.eq-tidal-lyrics-next .eq-tidal-lyrics-word,
.eq-tidal-lyrics-prev .eq-tidal-lyrics-word {
color: var(--text-muted);
}
@media (prefers-reduced-motion: reduce) {
.eq-tidal-lyrics-current,
.eq-tidal-lyrics-next,
.eq-tidal-lyrics-prev,
.eq-tidal-lyrics-char,
.eq-tidal-lyrics-word {
transition: none;
text-shadow: none;
}
.eq-tidal-lyrics-char-active,
.eq-tidal-lyrics-word-active {
transform: none;
}
}

View file

@ -149,7 +149,7 @@ export const TidalStore = proxyLazyWebpack(() => {
public repeat: Repeat = 0;
public shuffle = false;
public volume = 100;
private playerElement: HTMLElement | null = null;
public playerElement: HTMLElement | null = null;
public socket = new TidalSocket((message: Message) => {
if (message.type === "update" && message.all && message.fields) {
const apiData = message.fields;

View file

@ -15,11 +15,10 @@ import { LyricsModal } from "./modal";
import { cl, NoteSvg, useLyrics } from "./util";
function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) {
const { ShowMusicNoteOnNoLyrics, SyncMode } = settings.use(["ShowMusicNoteOnNoLyrics", "SyncMode"]);
const { ShowMusicNoteOnNoLyrics } = settings.use(["ShowMusicNoteOnNoLyrics"]);
const { lyrics, lyricRefs, currLrcIndex, nextLyric } = useLyrics({ scroll });
const currentLyrics = lyrics || null;
const NoteElement = NoteSvg(cl("music-note"));
const position = useStateFromStores([TidalStore], () => TidalStore.mPosition);
const makeClassName = (index: number): string => {
if (currLrcIndex === null) return "";
@ -52,53 +51,7 @@ function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) {
variant={currLrcIndex === i ? "text-sm/normal" : "text-xs/normal"}
className={makeClassName(i)}
>
{SyncMode === "character" && line.characters && line.characters.length > 0 && line.words && line.words.length > 0 ? (
line.words.map((word, wIdx) => (
<span key={wIdx}>
{word.characters.map((char, j) => {
const charActive = position / 1000 >= char.time && position / 1000 < char.endTime;
return (
<span
key={j}
className={charActive ? "eq-tidal-lyrics-char-active" : "eq-tidal-lyrics-char"}
style={{ opacity: charActive ? 1 : 0.5 }}
>
{char.char}
</span>
);
})}
{wIdx < line.words.length - 1 && <span> </span>}
</span>
))
) : SyncMode === "character" && line.characters && line.characters.length > 0 ? (
line.characters.map((char, j) => {
const charActive = position / 1000 >= char.time && position / 1000 < char.endTime;
return (
<span
key={j}
className={charActive ? "eq-tidal-lyrics-char-active" : "eq-tidal-lyrics-char"}
style={{ opacity: charActive ? 1 : 0.5 }}
>
{char.char}
</span>
);
})
) : SyncMode === "word" && line.words && line.words.length > 0 ? (
line.words.map((word, j) => {
const wordActive = position / 1000 >= word.time && position / 1000 < word.endTime;
return (
<span
key={j}
className={wordActive ? "eq-tidal-lyrics-word-active" : "eq-tidal-lyrics-word"}
style={{ opacity: wordActive ? 1 : 0.5 }}
>
{word.word}
</span>
);
})
) : (
line.text || NoteElement
)}
{line.text || NoteElement}
</Text>
</div>
))}

View file

@ -7,7 +7,6 @@
import { ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Text } from "@webpack/common";
import { settings } from "../../../settings";
import { TidalStore, Track } from "../../TidalStore";
import { cl, NoteSvg, scrollClasses, useLyrics } from "./util";
@ -41,8 +40,6 @@ function ModalHeaderContent({ track }: { track: Track; }) {
export function LyricsModal({ rootProps }: { rootProps: ModalProps; }) {
const { track, lyrics, currLrcIndex } = useLyrics({ scroll: false });
const currentLyrics = lyrics || null;
const position = track ? TidalStore.mPosition : 0;
const { SyncMode } = settings.use(["SyncMode"]);
return (
<ModalRoot {...rootProps}>
@ -61,54 +58,7 @@ export function LyricsModal({ rootProps }: { rootProps: ModalProps; }) {
onClick={() => TidalStore.seek(line.time * 1000)}
>
{formatTime(line.time)}
</span>
{SyncMode === "character" && line.characters && line.characters.length > 0 && line.words && line.words.length > 0 ? (
line.words.map((word, wIdx) => (
<span key={wIdx}>
{word.characters.map((char, j) => {
const charActive = position / 1000 >= char.time && position / 1000 < char.endTime;
return (
<span
key={j}
className={charActive ? cl("char-active") : cl("char")}
style={{ opacity: charActive ? 1 : 0.5 }}
>
{char.char}
</span>
);
})}
{wIdx < line.words.length - 1 && <span> </span>}
</span>
))
) : SyncMode === "character" && line.characters && line.characters.length > 0 ? (
line.characters.map((char, j) => {
const charActive = position / 1000 >= char.time && position / 1000 < char.endTime;
return (
<span
key={j}
className={charActive ? cl("char-active") : cl("char")}
style={{ opacity: charActive ? 1 : 0.5 }}
>
{char.char}
</span>
);
})
) : SyncMode === "word" && line.words && line.words.length > 0 ? (
line.words.map((word, j) => {
const wordActive = position / 1000 >= word.time && position / 1000 < word.endTime;
return (
<span
key={j}
className={wordActive ? cl("word-active") : cl("word")}
style={{ opacity: wordActive ? 1 : 0.5, marginRight: 2 }}
>
{word.word}
</span>
);
})
) : (
line.text || NoteSvg(cl("modal-note"))
)}
</span>{line.text || NoteSvg(cl("modal-note"))}
</Text>
))
) : (