feat: recently play&lang

This commit is contained in:
猫头猫
2024-06-16 18:13:39 +08:00
parent 0f7bbb9926
commit 96bcba93bf
37 changed files with 479 additions and 236 deletions

View File

@@ -1,3 +1,7 @@
{
"editor.formatOnSave": true
}
"editor.formatOnSave": true,
"files.associations": {
"*.html": "html",
"map": "cpp"
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "musicfree-desktop",
"productName": "MusicFree",
"version": "0.0.3",
"version": "0.0.4",
"description": "一个插件化的音乐播放器",
"main": ".webpack/main",
"scripts": {

View File

@@ -27,7 +27,8 @@
"add": "Add",
"save": "Save",
"clear": "Clear",
"open": "Open"
"open": "Open",
"status": "Status"
},
"media": {
@@ -156,7 +157,10 @@
"create_local_sheet": "Create Playlist",
"starred_sheets": "Favorites",
"delete_sheet": "Delete Playlist"
"delete_sheet": "Delete",
"rename_sheet": "Rename",
"unstar_sheet": "Unstar",
"recently_play": "Recently Play"
},
"app_header": {
"nav_back": "Back",
@@ -165,7 +169,9 @@
"search_history": "Search History",
"settings": "Settings",
"minimize": "Minimize",
"exit": "Exit"
"minimode": "Minimode",
"exit": "Exit",
"theme": "Theme"
},
"music_bar": {
"open_music_detail_page": "Open Song Details",
@@ -193,7 +199,10 @@
"link_media_lyric": "Link Lyric",
"media_lyric_linked": "Lyric Linked: ",
"unlink_media_lyric": "Unlink Lyric",
"toast_media_lyric_unlinked": "Lyric Unlinked"
"toast_media_lyric_unlinked": "Lyric Unlinked",
"translation": "Translation",
"show_translation": "Show Translation",
"hide_translation": "Hide Translation"
},
"bottom_loading_state": {
"reached_end": "~~~ End ~~~",
@@ -232,7 +241,8 @@
},
"music_sheet_like_view": {
"play_all": "Play All",
"add_to_sheet": "Add to Playlist"
"add_to_sheet": "Add to Playlist",
"star": "Star"
},
"settings": {
"choose_path": "Choose Path",
@@ -277,7 +287,9 @@
"double_click_music_list": "When double-clicking the music list",
"add_music_to_playlist": "Add the selected song to the playback queue",
"replace_playlist_with_musiclist": "Replace playback queue with current music list",
"audio_output_device": "Audio output device"
"audio_output_device": "Audio output device",
"when_device_removed": "When device is removed",
"continue_playing": "Continue playing"
},
"download": {
"download_folder": "Download Directory",
@@ -300,18 +312,6 @@
"auto_update_plugin": "Automatically update plugins on startup",
"not_check_plugin_version": "Do not check plugin version when installing"
},
"theme": {
"example_theme_hint": "Here are some example themes:",
"example_theme_subscription_hint": "You can also follow the WeChat public account: <highlight>A Cat-headed Cat</highlight>, and reply with <highlight>MusicFree Theme Pack</highlight> to get the download link (updated periodically)",
"install_theme": "Install Theme Pack",
"uninstall_theme": "Uninstall Theme Pack",
"musicfree_theme": "MusicFree Theme",
"all_files": "All Files",
"install_theme_success": "Installed theme {{name}} successfully~",
"install_theme_fail": "Failed to install theme: {{reason}}",
"uninstall_theme_success": "Uninstalled theme {{name}} successfully~",
"uninstall_theme_fail": "Failed to uninstall theme: {{reason}}"
},
"short_cut": {
"enable_local": "Enable in-app shortcuts",
"enable_global": "Enable global shortcuts",
@@ -373,5 +373,23 @@
"unlock_desktop_lyric": "Unlock Desktop Lyrics",
"lock_desktop_lyric": "Lock Desktop Lyrics",
"no_playing_music": "No Music Playing"
},
"theme": {
"tab_local": "Local Theme",
"tab_remote": "Theme Marketplace",
"download_and_use": "Download and Use",
"use_theme": "Use Theme",
"install_theme": "Install Theme",
"update_theme": "Update Theme",
"uninstall_theme": "Uninstall Theme",
"musicfree_theme": "MusicFree Theme",
"all_files": "All Files",
"install_theme_success": "Successfully installed theme {{name}}~",
"install_theme_fail": "Failed to install theme: {{reason}}",
"uninstall_theme_success": "Successfully uninstalled theme {{name}}~",
"uninstall_theme_fail": "Failed to uninstall theme: {{reason}}",
"how_to_submit_new_theme": "💡How to submit a new theme: The themes in the theme marketplace are synchronized with the <Github>MusicFreeThemePacks</Github> repository. If you need to submit a new theme, please make a pull request directly.",
"load_remote_theme_error": "An error occurred...",
"invalid_theme": "Invalid theme: {{reason}}"
}
}

View File

@@ -27,7 +27,8 @@
"add": "Añadir",
"save": "Guardar",
"clear": "Limpiar",
"open": "Abrir"
"open": "Abrir",
"status": "Estado"
},
"media": {
@@ -156,7 +157,10 @@
"create_local_sheet": "Crear lista de canciones",
"starred_sheets": "Mis favoritos",
"delete_sheet": "Eliminar lista de canciones"
"delete_sheet": "Eliminar lista de canciones",
"rename_sheet": "Cambiar nombre",
"unstar_sheet": "Desmarcar",
"recently_play": "Reproducido recientemente"
},
"app_header": {
"nav_back": "Atrás",
@@ -165,7 +169,9 @@
"search_history": "Historial de búsqueda",
"settings": "Configuración",
"minimize": "Minimizar",
"exit": "Salir"
"minimode": "Modo minimalista",
"exit": "Salir",
"theme": "tema"
},
"music_bar": {
"open_music_detail_page": "Abrir detalles de la canción",
@@ -193,7 +199,10 @@
"link_media_lyric": "Vincular letra",
"media_lyric_linked": "Letra vinculada: ",
"unlink_media_lyric": "Desvincular letra",
"toast_media_lyric_unlinked": "Letra desvinculada"
"toast_media_lyric_unlinked": "Letra desvinculada",
"translation": "Traducción",
"show_translation": "Mostrar traducción",
"hide_translation": "Ocultar traducción"
},
"bottom_loading_state": {
"reached_end": "~~~ Fin ~~~",
@@ -232,7 +241,8 @@
},
"music_sheet_like_view": {
"play_all": "Reproducir todo",
"add_to_sheet": "Añadir a la lista de canciones"
"add_to_sheet": "Añadir a la lista de canciones",
"star": "favorito"
},
"settings": {
"choose_path": "Elegir ruta",
@@ -277,7 +287,9 @@
"double_click_music_list": "Al hacer doble clic en la lista de canciones",
"add_music_to_playlist": "Añadir canción a la lista de reproducción",
"replace_playlist_with_musiclist": "Reemplazar la lista de reproducción con la lista de canciones actual",
"audio_output_device": "Dispositivo de salida de audio"
"audio_output_device": "Dispositivo de salida de audio",
"when_device_removed": "Cuando se elimina el dispositivo de audio",
"continue_playing": "Continuar reproduciendo"
},
"download": {
"download_folder": "Carpeta de descargas",
@@ -300,18 +312,6 @@
"auto_update_plugin": "Actualizar complementos automáticamente al iniciar la aplicación",
"not_check_plugin_version": "No verificar la versión al instalar complementos"
},
"theme": {
"example_theme_hint": "Aquí hay algunos temas de ejemplo:",
"example_theme_subscription_hint": "También puedes seguir nuestro WeChat: <highlight>Una GatoGato</highlight>, responde con <highlight>Paquete de temas MusicFree</highlight> para obtener el enlace de descarga (actualizado periódicamente)",
"install_theme": "Instalar paquete de temas",
"uninstall_theme": "Desinstalar paquete de temas",
"musicfree_theme": "Tema MusicFree",
"all_files": "Todos los archivos",
"install_theme_success": "Tema {{name}} instalado con éxito~",
"install_theme_fail": "Error al instalar el tema: {{reason}}",
"uninstall_theme_success": "Tema {{name}} desinstalado con éxito~",
"uninstall_theme_fail": "Error al desinstalar el tema: {{reason}}"
},
"short_cut": {
"enable_local": "Habilitar atajos en la aplicación",
"enable_global": "Habilitar atajos globales",
@@ -373,5 +373,23 @@
"unlock_desktop_lyric": "Desbloquear letra en pantalla",
"lock_desktop_lyric": "Bloquear letra en pantalla",
"no_playing_music": "No hay música reproduciéndose actualmente"
},
"theme": {
"tab_local": "Tema local",
"tab_remote": "Mercado de temas",
"download_and_use": "Descargar y usar",
"use_theme": "Usar tema",
"install_theme": "Instalar tema",
"update_theme": "Actualizar tema",
"uninstall_theme": "Desinstalar tema",
"musicfree_theme": "Tema de MusicFree",
"all_files": "Todos los archivos",
"install_theme_success": "Tema {{name}} instalado con éxito~",
"install_theme_fail": "Error al instalar el tema: {{reason}}",
"uninstall_theme_success": "Tema {{name}} desinstalado con éxito~",
"uninstall_theme_fail": "Error al desinstalar el tema: {{reason}}",
"how_to_submit_new_theme": "💡Cómo enviar un nuevo tema: Los temas en el mercado de temas están sincronizados con el repositorio <Github>MusicFreeThemePacks</Github>. Si necesita enviar un nuevo tema, envíe una solicitud de extracción directamente.",
"load_remote_theme_error": "Ha ocurrido un error...",
"invalid_theme": "Tema inválido: {{reason}}"
}
}

View File

@@ -159,7 +159,8 @@
"delete_sheet": "删除歌单",
"rename_sheet": "重命名歌单",
"unstar_sheet": "取消收藏"
"unstar_sheet": "取消收藏",
"recently_play": "最近播放"
},
"app_header": {
"nav_back": "后退",
@@ -311,7 +312,6 @@
"auto_update_plugin": "打开软件时自动更新插件",
"not_check_plugin_version": "安装插件时不校验版本"
},
"short_cut": {
"enable_local": "启用软件内快捷键",
"enable_global": "启用全局快捷键",

View File

@@ -27,7 +27,8 @@
"add": "新增",
"save": "保存",
"clear": "清除",
"open": "打開"
"open": "打開",
"status": "狀態"
},
"media": {
@@ -156,7 +157,10 @@
"create_local_sheet": "創建歌單",
"starred_sheets": "我的收藏",
"delete_sheet": "刪除歌單"
"delete_sheet": "刪除歌單",
"rename_sheet": "重歌單",
"unstar_sheet": "取消收藏",
"recently_play": "最近播放"
},
"app_header": {
"nav_back": "返回",
@@ -165,7 +169,9 @@
"search_history": "搜尋歷史",
"settings": "設置",
"minimize": "最小化",
"exit": "退出"
"minimode": "迷你模式",
"exit": "退出",
"theme": "主題"
},
"music_bar": {
"open_music_detail_page": "打開歌曲詳情",
@@ -193,7 +199,10 @@
"link_media_lyric": "鏈接歌詞",
"media_lyric_linked": "已鏈接歌詞:",
"unlink_media_lyric": "取消鏈接歌詞",
"toast_media_lyric_unlinked": "已取消鏈接歌詞"
"toast_media_lyric_unlinked": "已取消鏈接歌詞",
"translation": "翻譯",
"show_translation": "顯示翻譯",
"hide_translation": "隱藏翻譯"
},
"bottom_loading_state": {
"reached_end": "~~~ 沒有更多了 ~~~",
@@ -232,7 +241,8 @@
},
"music_sheet_like_view": {
"play_all": "播放全部",
"add_to_sheet": "添加到歌單"
"add_to_sheet": "添加到歌單",
"star": "收藏"
},
"settings": {
"choose_path": "選擇路徑",
@@ -277,7 +287,9 @@
"double_click_music_list": "雙擊歌曲列表時",
"add_music_to_playlist": "添加歌曲到播放清單",
"replace_playlist_with_musiclist": "用當前歌單替換播放清單",
"audio_output_device": "音頻輸出設備"
"audio_output_device": "音頻輸出設備",
"when_device_removed": "當音頻裝置被移除時",
"continue_playing": "繼續播放"
},
"download": {
"download_folder": "下載文件夾",
@@ -300,18 +312,6 @@
"auto_update_plugin": "啟動應用時自動更新插件",
"not_check_plugin_version": "安裝插件時不檢查版本"
},
"theme": {
"example_theme_hint": "這裡有一些示例主題:",
"example_theme_subscription_hint": "你也可以關注我們的微信:<highlight>一隻貓貓</highlight>,回復 <highlight>MusicFree 主題包</highlight> 獲取下載連結(定期更新)",
"install_theme": "安裝主題包",
"uninstall_theme": "卸載主題包",
"musicfree_theme": "MusicFree 主題",
"all_files": "所有文件",
"install_theme_success": "主題 {{name}} 安裝成功~",
"install_theme_fail": "安裝主題失敗:{{reason}}",
"uninstall_theme_success": "主題 {{name}} 卸載成功~",
"uninstall_theme_fail": "卸載主題失敗:{{reason}}"
},
"short_cut": {
"enable_local": "啟用應用內快捷鍵",
"enable_global": "啟用全局快捷鍵",
@@ -373,5 +373,23 @@
"unlock_desktop_lyric": "解鎖桌面歌詞",
"lock_desktop_lyric": "鎖定桌面歌詞",
"no_playing_music": "目前無正在播放的音樂"
},
"theme": {
"tab_local": "本地主題",
"tab_remote": "主題市場",
"download_and_use": "下載並使用",
"use_theme": "使用主題",
"install_theme": "安裝主題",
"update_theme": "更新主題",
"uninstall_theme": "卸載主題",
"musicfree_theme": "MusicFree 主題",
"all_files": "全部檔案",
"install_theme_success": "安裝主題{{name}}成功~",
"install_theme_fail": "安裝主題失敗: {{reason}}",
"uninstall_theme_success": "卸載主題{{name}}成功~",
"uninstall_theme_fail": "卸載主題失敗: {{reason}}",
"how_to_submit_new_theme": "💡如何提交新主題: 主題市場中的主題與 <Github>MusicFreeThemePacks</Github> 倉庫同步如果需要提交新主題請直接提PR。",
"load_remote_theme_error": "出錯啦...",
"invalid_theme": "主題無效: {{reason}}"
}
}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 247 B

View File

@@ -74,6 +74,8 @@ interface IMusicListProps {
enableDrag?: boolean;
/** 拖拽结束 */
onDragEnd?: (newMusicList: IMusic.IMusicItem[]) => void;
/** context */
contextMenu?: IContextMenuItem[];
}
const columnHelper = createColumnHelper<IMusic.IMusicItem>();

View File

@@ -18,10 +18,6 @@
padding-left: 0.8em;
padding-right: 0.8em;
&:not([data-type="primaryButton"]) {
background-color: color-mix(in srgb, currentColor 15%, transparent);
}
& svg {
width: 1.3em;
height: 1.3em;

View File

@@ -26,11 +26,14 @@
flex: 1;
font-size: 1.4rem;
font-weight: 600;
margin-left: 8px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
&:not(:first-child) {
margin-left: 8px;
}
}
}

View File

@@ -12,12 +12,13 @@ import { useTranslation } from "react-i18next";
interface IProps {
musicSheet: IMusic.IMusicSheetItem;
musicList: IMusic.IMusicItem[];
hidePlatform?: boolean;
}
export default function Header(props: IProps) {
const { musicSheet, musicList } = props;
const { musicSheet, musicList, hidePlatform } = props;
const containerRef = useRef<HTMLDivElement>();
const {t} = useTranslation();
const { t } = useTranslation();
return (
<div className="music-sheetlike-view--header-container" ref={containerRef}>
@@ -28,28 +29,39 @@ export default function Header(props: IProps) {
></img>
<div className="sheet-info">
<div className="title-container">
<Condition condition={musicSheet?.platform}>
{musicSheet?.platform && !hidePlatform ? (
<Tag>{musicSheet?.platform}</Tag>
</Condition>
<div className="title">{musicSheet?.title ?? t("media.unknown_title")}</div>
) : null}
<div className="title">
{musicSheet?.title ?? t("media.unknown_title")}
</div>
</div>
<Condition condition={musicSheet?.createAt || musicSheet?.artist}>
<div className="info-container">
<Condition condition={musicSheet?.createAt}>
<span>
{t("media.media_create_at")}: {dayjs(musicSheet?.createAt).format("YYYY-MM-DD")}
{t("media.media_create_at")}:{" "}
{dayjs(musicSheet?.createAt).format("YYYY-MM-DD")}
</span>
</Condition>
<Condition condition={musicSheet?.artist}>
<span>{t("media.media_type_artist")}: {musicSheet?.artist}</span>
<span>
{t("media.media_type_artist")}: {musicSheet?.artist}
</span>
</Condition>
</div>
</Condition>
<div className="info-container">
<Condition condition={musicSheet?.playCount}>
<span>{t("media.media_play_count")}: {musicSheet?.playCount}</span>
<span>
{t("media.media_play_count")}: {musicSheet?.playCount}
</span>
</Condition>
<span>{t("media.media_music_count")}: {musicSheet?.worksNum ?? musicList?.length ?? 0}</span>
<span>
{t("media.media_music_count")}:{" "}
{musicSheet?.worksNum ?? musicList?.length ?? 0}
</span>
</div>
<Condition condition={musicSheet?.description}>

View File

@@ -1,10 +1,9 @@
import { ReactNode, useEffect } from "react";
import { RequestStateCode } from "@/common/constant";
import Body from "./components/Body";
import Header from "./components/Header";
import "./index.scss";
import { ReactNode, useEffect } from "react";
import { initValue, offsetHeightStore } from "./store";
import "./index.scss";
interface IMusicSheetlikeViewProps {
scrollElement?: HTMLElement;
@@ -13,6 +12,8 @@ interface IMusicSheetlikeViewProps {
state?: RequestStateCode;
onLoadMore?: () => void;
options?: ReactNode;
/** 是否展示来源tag */
hidePlatform?: boolean;
}
export default function MusicSheetlikeView(props: IMusicSheetlikeViewProps) {
@@ -22,6 +23,7 @@ export default function MusicSheetlikeView(props: IMusicSheetlikeViewProps) {
state = RequestStateCode.IDLE,
onLoadMore,
options,
hidePlatform,
} = props;
useEffect(() => {
@@ -32,7 +34,11 @@ export default function MusicSheetlikeView(props: IMusicSheetlikeViewProps) {
return (
<div className="music-sheetlike-view--container">
<Header musicSheet={musicSheet} musicList={musicList ?? []}></Header>
<Header
hidePlatform={hidePlatform}
musicSheet={musicSheet}
musicList={musicList ?? []}
></Header>
<Body
musicList={musicList ?? []}
musicSheet={musicSheet}

View File

@@ -12,6 +12,7 @@ export type SvgAssetIconNames =
| "chevron-down"
| "chevron-left"
| "chevron-right"
| "clock"
| "code-bracket-square"
| "cog-8-tooth"
| "dashboard-speed"

View File

@@ -0,0 +1,80 @@
import { isSameMedia } from "@/common/media-util";
import Store from "@/common/store";
import {
getUserPreferenceIDB,
setUserPreferenceIDB,
} from "@/renderer/utils/user-perference";
import { Immer } from "immer";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
const recentlyPlayListStore = new Store<IMusic.IMusicItem[]>([]);
const immer = new Immer({
autoFreeze: false,
});
const HARD_LIMIT = 500;
async function fetchRecentlyPlaylist() {
return (await getUserPreferenceIDB("recentlyPlayList")) || [];
}
async function setRecentlyPlaylist(musicItems: IMusic.IMusicItem[]) {
recentlyPlayListStore.setValue(musicItems);
return await setUserPreferenceIDB("recentlyPlayList", musicItems);
}
export async function setupRecentlyPlaylist() {
const playList = await fetchRecentlyPlaylist();
recentlyPlayListStore.setValue(playList);
}
export async function addToRecentlyPlaylist(musicItem: IMusic.IMusicItem) {
const playList = recentlyPlayListStore.getValue();
const existId = playList.findIndex((it) => isSameMedia(musicItem, it));
let newPlayList = playList;
if (existId !== -1) {
newPlayList = immer.produce(playList, (draft) => {
draft.splice(existId, 1);
});
}
newPlayList = [musicItem].concat(newPlayList).slice(0, HARD_LIMIT);
setRecentlyPlaylist(newPlayList);
}
export async function removeRecentlyPlayList(musicItem: IMusic.IMusicItem) {
const playList = recentlyPlayListStore.getValue();
const existId = playList.findIndex((it) => isSameMedia(musicItem, it));
let newPlayList = playList;
if (existId !== -1) {
newPlayList = immer.produce(playList, (draft) => {
draft.splice(existId, 1);
});
setRecentlyPlaylist(newPlayList);
}
}
export async function clearRecentlyPlaylist() {
setRecentlyPlaylist([]);
}
export function useRecentlyPlaylistSheet() {
const recentlyPlayList = recentlyPlayListStore.useValue();
const { t } = useTranslation();
const musicSheet: IMusic.IMusicSheetItem = useMemo(() => {
return {
id: "recently-play",
title: t("side_bar.recently_play"),
platform: "recently-play",
playCount: recentlyPlayList?.length || 0,
artwork: recentlyPlayList?.[0]?.artwork,
musicList: recentlyPlayList || [],
};
}, [recentlyPlayList, t]);
return musicSheet;
}

View File

@@ -23,6 +23,11 @@ import {
setupPlayerSyncHandler,
} from "../core/command-handler";
import ThemePack from "@/shared/themepack/renderer";
import {
addToRecentlyPlaylist,
setupRecentlyPlaylist,
} from "../core/recently-playlist";
import { TrackPlayerEvent } from "../core/track-player/enum";
setAutoFreeze(false);
@@ -43,6 +48,7 @@ export default async function () {
setupDeviceChange();
localMusic.setupLocalMusic();
await Downloader.setupDownloader();
setupRecentlyPlaylist();
// 自动更新插件
if (getAppConfigPath("plugin.autoUpdatePlugin")) {
@@ -152,6 +158,11 @@ function setupEvents() {
MusicSheet.frontend.addMusicToFavorite(realItem);
}
});
// 最近播放
trackPlayer.on(TrackPlayerEvent.MusicChanged, (musicItem) => {
addToRecentlyPlaylist(musicItem);
});
}
async function setupDeviceChange() {

View File

@@ -87,6 +87,7 @@ input {
}
}
// 按钮样式
div[role="button"] {
cursor: pointer;
user-select: none;
@@ -114,6 +115,7 @@ div[role="button"] {
border: 1px solid currentColor;
width: fit-content;
line-height: 1em;
background-color: color-mix(in srgb, currentColor 15%, transparent);
}
&[data-type="dangerButton"] {

View File

@@ -8,7 +8,6 @@ import MainPage from "../pages/main-page";
import { ContextMenuComponent } from "../components/ContextMenu";
import { ToastContainer } from "react-toastify";
import "rc-slider/assets/index.css";
import "react-toastify/dist/ReactToastify.css";
import "./index.css"; // 全局样式
@@ -48,7 +47,6 @@ function Root() {
}
function BootstrapComponent(): null {
useBootstrap();
return null;

View File

@@ -36,6 +36,11 @@ export default function () {
title: t("side_bar.plugin_management"),
route: "plugin-manager-view",
},
{
iconName: "clock",
title: t("side_bar.recently_play"),
route: "recently_play",
},
] as const;
return (

View File

@@ -1,14 +1,20 @@
.page-container {
flex: auto;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
padding-left: 1.5rem;
padding-right: 1.5rem;
position: relative;
flex: auto;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
padding-left: 1.5rem;
padding-right: 1.5rem;
position: relative;
}
.page-container-full-width {
padding-left: 0;
padding-right: 0;
}
padding-left: 0;
padding-right: 0;
}
.page-container-fw {
@extend .page-container;
padding-left: 0;
padding-right: 0;
}

View File

@@ -13,58 +13,55 @@ import SettingView from "./views/setting-view";
import LocalMusicView from "./views/local-music-view";
import Empty from "@/renderer/components/Empty";
import DownloadView from "./views/download-view";
import ThemeView from "./views/theme-view";
import RecentlyPlayView from "./views/recently-play-view";
import "./index.scss";
import ThemeView from "./views/theme-view";
export default function MainPage() {
return (
<>
<SideBar></SideBar>
<div className="page-container" id="page-container">
<Routes>
<Route
path="search/:query"
element={<SearchView></SearchView>}
></Route>
<Route
path="plugin-manager-view"
element={<PluginManagerView></PluginManagerView>}
></Route>
<Route
path="musicsheet/:platform/:id"
element={<MusicSheetView></MusicSheetView>}
></Route>
<Route
path="album/:platform/:id"
element={<AlbumView></AlbumView>}
></Route>
<Route
path="artist/:platform/:id"
element={<ArtistView></ArtistView>}
></Route>
<Route path="toplist" element={<ToplistView></ToplistView>}></Route>
<Route
path="toplist-detail/:platform"
element={<TopListDetailView></TopListDetailView>}
></Route>
<Route
path="recommend-sheets"
element={<RecommendSheetsView></RecommendSheetsView>}
></Route>
<Route
path="local-music"
element={<LocalMusicView></LocalMusicView>}
></Route>
<Route
path="download"
element={<DownloadView></DownloadView>}
></Route>
<Route path="setting" element={<SettingView></SettingView>}></Route>
<Route path="theme" element={<ThemeView></ThemeView>}></Route>
<Route path="*" element={<Empty></Empty>}></Route>
</Routes>
</div>
<Routes>
<Route path="search/:query" element={<SearchView></SearchView>}></Route>
<Route
path="plugin-manager-view"
element={<PluginManagerView></PluginManagerView>}
></Route>
<Route
path="musicsheet/:platform/:id"
element={<MusicSheetView></MusicSheetView>}
></Route>
<Route
path="album/:platform/:id"
element={<AlbumView></AlbumView>}
></Route>
<Route
path="artist/:platform/:id"
element={<ArtistView></ArtistView>}
></Route>
<Route path="toplist" element={<ToplistView></ToplistView>}></Route>
<Route
path="toplist-detail/:platform"
element={<TopListDetailView></TopListDetailView>}
></Route>
<Route
path="recommend-sheets"
element={<RecommendSheetsView></RecommendSheetsView>}
></Route>
<Route
path="local-music"
element={<LocalMusicView></LocalMusicView>}
></Route>
<Route path="download" element={<DownloadView></DownloadView>}></Route>
<Route path="setting" element={<SettingView></SettingView>}></Route>
<Route path="theme" element={<ThemeView></ThemeView>}></Route>
<Route
path="recently_play"
element={<RecentlyPlayView></RecentlyPlayView>}
></Route>
<Route path="*" element={<Empty></Empty>}></Route>
</Routes>
<MusicDetail></MusicDetail>
</>
);

View File

@@ -20,11 +20,13 @@ export default function AlbumView() {
useAlbumDetail(originalAlbumItem);
return (
<MusicSheetlikeView
musicSheet={albumItem}
musicList={musicList}
onLoadMore={getAlbumDetail}
state={requestState}
></MusicSheetlikeView>
<div id="page-container" className="page-container">
<MusicSheetlikeView
musicSheet={albumItem}
musicList={musicList}
onLoadMore={getAlbumDetail}
state={requestState}
></MusicSheetlikeView>
</div>
);
}

View File

@@ -12,11 +12,9 @@ interface IBodyProps {
const supportedMediaType = ["music", "album"];
export default function Body(props: IBodyProps) {
const {artistItem} = props;
const { artistItem } = props;
const [currentMediaType, setCurrentMediaType] = useState("music");
const { t } = useTranslation();
return (
<div className="artist-view--body-container">
@@ -28,21 +26,21 @@ export default function Body(props: IBodyProps) {
<Tab.List className="tab-list-container">
{supportedMediaType.map((type) => (
<Tab key={type} as="div" className="tab-list-item">
{t(type)}
{t(`media.media_type_${type}`)}
</Tab>
))}
</Tab.List>
<Tab.Panels className={"tab-panels-container"}>
{supportedMediaType.map((type) => (
<Tab.Panel className="tab-panel-container" key={type}>
<SwitchCase.Switch switch={type}>
<SwitchCase.Case case={"music"}>
<MusicResult artistItem={artistItem}></MusicResult>
</SwitchCase.Case>
<SwitchCase.Case case={"album"}>
<AlbumResult artistItem={artistItem}></AlbumResult>
</SwitchCase.Case>
</SwitchCase.Switch>
<SwitchCase.Switch switch={type}>
<SwitchCase.Case case={"music"}>
<MusicResult artistItem={artistItem}></MusicResult>
</SwitchCase.Case>
<SwitchCase.Case case={"album"}>
<AlbumResult artistItem={artistItem}></AlbumResult>
</SwitchCase.Case>
</SwitchCase.Switch>
</Tab.Panel>
))}
</Tab.Panels>

View File

@@ -11,7 +11,7 @@ interface IProps {
export default function Header(props: IProps) {
const { artistItem } = props;
const {t} = useTranslation();
const { t } = useTranslation();
return (
<div className="artist-view--header-container">
@@ -23,7 +23,9 @@ export default function Header(props: IProps) {
<div className="artist-info">
<div className="title-container">
<Tag>{artistItem?.platform}</Tag>
<div className="title">{artistItem?.name ?? t("media.unknown_artist")}</div>
<div className="title">
{artistItem?.name ?? t("media.unknown_artist")}
</div>
</div>
<Condition condition={artistItem?.description}>
@@ -35,7 +37,7 @@ export default function Header(props: IProps) {
dataset.fold = dataset.fold === "true" ? "false" : "true";
}}
>
{artistItem?.description}
{artistItem?.description}
</div>
</Condition>
</div>

View File

@@ -22,12 +22,12 @@ export default function ArtistView() {
return () => {
queryResultStore.setValue(initQueryResult);
};
})
});
return (
<div className="artist-view--container">
<div id="page-container" className="page-container artist-view--container">
<Header artistItem={artistItem}></Header>
<Body artistItem = {artistItem}></Body>
<Body artistItem={artistItem}></Body>
</div>
);
}

View File

@@ -8,7 +8,10 @@ export default function DownloadView() {
const { t } = useTranslation();
return (
<div className="download-view--container">
<div
id="page-container"
className="page-container download-view--container"
>
<Tab.Group>
<Tab.List className="tab-list-container">
<Tab as="div" className="tab-list-item">

View File

@@ -74,7 +74,8 @@ export default function LocalMusicView() {
return (
<div
className="local-music-view--container"
id="page-container"
className="page-container local-music-view--container"
data-full-page={displayView !== DisplayView.LIST}
>
<div className="header">{t("local_music_page.local_music")}</div>

View File

@@ -1,10 +1,10 @@
import { useParams } from "react-router-dom";
import "./index.scss";
import Condition from "@/renderer/components/Condition";
import { localPluginName } from "@/common/constant";
import LocalSheet from "./local-sheet";
import RemoteSheet from "./remote-sheet";
import "./index.scss";
/**
* path: /main/musicsheet/platform/id
*
@@ -17,11 +17,12 @@ export default function MusicSheetView() {
const { platform } = useParams() ?? {};
return (
<Condition
condition={platform === localPluginName}
falsy={<RemoteSheet></RemoteSheet>}
>
<LocalSheet></LocalSheet>
</Condition>
<div id="page-container" className="page-container">
{platform === localPluginName ? (
<LocalSheet></LocalSheet>
) : (
<RemoteSheet></RemoteSheet>
)}
</div>
);
}

View File

@@ -19,6 +19,7 @@ export default function LocalSheet() {
return (
<MusicSheetlikeView
hidePlatform
musicSheet={_musicSheet}
state={loading}
musicList={musicSheet?.musicList ?? []}

View File

@@ -11,7 +11,10 @@ export default function PluginManagerView() {
const { t } = useTranslation();
return (
<div className="plugin-manager-view-container">
<div
id="page-container"
className="page-container plugin-manager-view-container"
>
<div className="header">
{t("plugin_management_page.plugin_management")}
</div>

View File

@@ -0,0 +1,40 @@
import MusicSheetlikeView from "@/renderer/components/MusicSheetlikeView";
import SvgAsset from "@/renderer/components/SvgAsset";
import {
clearRecentlyPlaylist,
useRecentlyPlaylistSheet,
} from "@/renderer/core/recently-playlist";
import { useTranslation } from "react-i18next";
export default function RecentlyPlayView() {
const recentlyPlaylistSheet = useRecentlyPlaylistSheet();
const { t } = useTranslation();
const options = (
<>
<div
role="button"
className="option-button"
data-type="normalButton"
data-disabled={!recentlyPlaylistSheet.playCount}
onClick={() => {
clearRecentlyPlaylist();
}}
>
<SvgAsset iconName={"trash"}></SvgAsset>
<span>{t("common.clear")}</span>
</div>
</>
);
return (
<div id="page-container" className="page-container">
<MusicSheetlikeView
hidePlatform
musicSheet={recentlyPlaylistSheet}
musicList={recentlyPlaylistSheet.musicList}
options={options}
></MusicSheetlikeView>
</div>
);
}

View File

@@ -10,41 +10,43 @@ export default function RecommendSheetsView() {
const navigate = useNavigate();
return (
<Condition
condition={availablePlugins.length}
falsy={<NoPlugin supportMethod="热门歌单" height={"100%"}></NoPlugin>}
>
<Tab.Group
defaultIndex={history.state?.usr?.pluginIndex}
onChange={(index) => {
const usr = history.state.usr ?? {};
navigate("", {
replace: true,
state: {
...usr,
pluginHash: availablePlugins[index].hash,
pluginIndex: index,
tag: null
},
});
}}
<div id="page-container" className="page-container">
<Condition
condition={availablePlugins.length}
falsy={<NoPlugin supportMethod="热门歌单" height={"100%"}></NoPlugin>}
>
<Tab.List className="tab-list-container">
{availablePlugins.map((plugin) => (
<Tab key={plugin.hash} as="div" className="tab-list-item">
{plugin.platform}
</Tab>
))}
</Tab.List>
<Tab.Panels className={"tab-panels-container"}>
{availablePlugins.map((plugin) => (
<Tab.Panel className="tab-panel-container" key={plugin.hash}>
<Body plugin={plugin}></Body>
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
</Condition>
<Tab.Group
defaultIndex={history.state?.usr?.pluginIndex}
onChange={(index) => {
const usr = history.state.usr ?? {};
navigate("", {
replace: true,
state: {
...usr,
pluginHash: availablePlugins[index].hash,
pluginIndex: index,
tag: null,
},
});
}}
>
<Tab.List className="tab-list-container">
{availablePlugins.map((plugin) => (
<Tab key={plugin.hash} as="div" className="tab-list-item">
{plugin.platform}
</Tab>
))}
</Tab.List>
<Tab.Panels className={"tab-panels-container"}>
{availablePlugins.map((plugin) => (
<Tab.Panel className="tab-panel-container" key={plugin.hash}>
<Body plugin={plugin}></Body>
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
</Condition>
</div>
);
}

View File

@@ -39,7 +39,7 @@ export default function SearchView() {
}, []);
return (
<div className="search-view-container">
<div id="page-container" className="page-container search-view-container">
<div className="search-header">
<span className="highlight">{decodeURIComponent(query)}</span>
{t("search_result_page.search_result_title")}

View File

@@ -18,9 +18,6 @@ export default function SettingView() {
const intersectionRatioRef = useRef<Map<string, number>>(new Map());
useEffect(() => {
document
.getElementById("page-container")
?.classList?.add("page-container-full-width");
intersectionObserverRef.current = new IntersectionObserver(
(targets) => {
const ratio = intersectionRatioRef.current;
@@ -62,7 +59,10 @@ export default function SettingView() {
}, []);
return (
<div className="setting-view--container">
<div
id="page-container"
className="page-container-fw setting-view--container"
>
<div className="setting-view--header">
<div className="tab-list-container">
{routers.map((setting) => (

View File

@@ -10,22 +10,24 @@ export default function ThemeView() {
const { t } = useTranslation();
return (
<Tab.Group>
<Tab.List className="tab-list-container">
{routes.map((it) => (
<Tab key={it} as="div" className="tab-list-item">
{t(`theme.tab_${it}`)}
</Tab>
))}
</Tab.List>
<Tab.Panels className={"tab-panels-container"}>
<Tab.Panel>
<LocalThemes></LocalThemes>
</Tab.Panel>
<Tab.Panel>
<RemoteThemes></RemoteThemes>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<div id="page-container" className="page-container">
<Tab.Group>
<Tab.List className="tab-list-container">
{routes.map((it) => (
<Tab key={it} as="div" className="tab-list-item">
{t(`theme.tab_${it}`)}
</Tab>
))}
</Tab.List>
<Tab.Panels className={"tab-panels-container"}>
<Tab.Panel>
<LocalThemes></LocalThemes>
</Tab.Panel>
<Tab.Panel>
<RemoteThemes></RemoteThemes>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div>
);
}

View File

@@ -10,11 +10,13 @@ export default function TopListDetailView() {
);
return (
<MusicSheetlikeView
musicSheet={topListDetail}
musicList={topListDetail?.musicList ?? []}
state={state}
onLoadMore={loadMore}
/>
<div id="page-container" className="page-container">
<MusicSheetlikeView
musicSheet={topListDetail}
musicList={topListDetail?.musicList ?? []}
state={state}
onLoadMore={loadMore}
/>
</div>
);
}

View File

@@ -14,17 +14,21 @@ import { useTranslation } from "react-i18next";
import "./index.scss";
export default function ToplistView() {
const availablePlugins = getSortedSupportedPlugin("getTopLists");
const navigate = useNavigate();
const {t} = useTranslation();
const { t } = useTranslation();
return (
<div className="toplist-view--container">
<div id="page-container" className="page-container toplist-view--container">
<Condition
condition={availablePlugins.length}
falsy={<NoPlugin supportMethod={t("plugin.method_get_top_lists")} height={"100%"}></NoPlugin>}
falsy={
<NoPlugin
supportMethod={t("plugin.method_get_top_lists")}
height={"100%"}
></NoPlugin>
}
>
<Tab.Group
defaultIndex={history.state?.usr?.pluginIndex}

View File

@@ -24,6 +24,8 @@ declare namespace IUserPreference {
interface IDBType {
/** 当前播放队列 */
playList: IMusic.IMusicItem[];
/** 最近播放队列 */
recentlyPlayList: IMusic.IMusicItem[];
/** 已下载列表 */
downloadedList: IMedia.IMediaBase[];
/** 本地音乐监听列表 */