|
2 | 2 | import { onMount } from "svelte"; |
3 | 3 |
|
4 | 4 | let url = $state(""); |
5 | | - let selectedProfileId = $state(""); |
| 5 | + let selectedVideoProfileId = $state<string | null>(null); |
| 6 | + let selectedAudioProfileId = $state<string | null>(null); |
6 | 7 | let saveToLibrary = $state(false); |
7 | 8 | let profiles = $state<any[]>([]); |
8 | 9 | let loading = $state(false); |
|
14 | 15 | let newProfileName = $state(""); |
15 | 16 |
|
16 | 17 | // Basic mode stackable options |
| 18 | + let audioQuality = $state("5"); |
17 | 19 | let basicOptions = $state({ sponsorblock: false, subtitles: false, metadata: false }); |
18 | | - let audioQuality = $state("0"); |
19 | 20 |
|
20 | 21 | // Advanced flag state |
21 | 22 | let flags = $state<Record<string, { enabled: boolean; value: string }>>({}); |
|
720 | 721 | expandedCategories = next; |
721 | 722 | } |
722 | 723 |
|
| 724 | + let activeProfileId = $derived(selectedVideoProfileId ?? selectedAudioProfileId); |
| 725 | +
|
723 | 726 | function buildBasicFlags(): string[] { |
724 | 727 | const result: string[] = []; |
725 | 728 | if (basicOptions.sponsorblock) { |
|
731 | 734 | if (basicOptions.metadata) { |
732 | 735 | result.push("--embed-metadata", "--embed-chapters"); |
733 | 736 | } |
734 | | - const profile = profiles.find((p: any) => p.id === selectedProfileId); |
| 737 | + if (selectedVideoProfileId) { |
| 738 | + result.push("--audio-quality", audioQuality); |
| 739 | + } |
| 740 | + const profile = profiles.find((p: any) => p.id === activeProfileId); |
735 | 741 | if (saveToLibrary && !(profile?.audioOnly)) { |
736 | 742 | result.push("--write-thumbnail"); |
737 | 743 | } |
738 | | - if (profile && !profile.audioOnly && audioQuality !== "0") { |
739 | | - result.push("--audio-quality", audioQuality); |
740 | | - } |
741 | 744 | return result; |
742 | 745 | } |
743 | 746 |
|
|
817 | 820 | if (profilesRes.ok) { |
818 | 821 | profiles = await profilesRes.json(); |
819 | 822 | const defaultProfile = profiles.find((p: any) => p.isDefault); |
820 | | - if (defaultProfile) { |
821 | | - selectedProfileId = defaultProfile.id; |
822 | | - loadProfileFlags(defaultProfile); |
| 823 | + if (defaultProfile && !defaultProfile.audioOnly) { |
| 824 | + selectedVideoProfileId = defaultProfile.id; |
823 | 825 | } |
| 826 | + const defaultAudio = profiles.find((p: any) => p.isSystem && p.audioOnly); |
| 827 | + if (defaultAudio) { |
| 828 | + selectedAudioProfileId = defaultAudio.id; |
| 829 | + } |
| 830 | + const active = profiles.find((p: any) => p.id === activeProfileId); |
| 831 | + if (active) loadProfileFlags(active); |
824 | 832 | } |
825 | 833 | if (settingsRes.ok) { |
826 | 834 | const settings = await settingsRes.json(); |
|
832 | 840 | e.preventDefault(); |
833 | 841 | error = ""; |
834 | 842 |
|
835 | | - if (!url || !selectedProfileId) { |
| 843 | + if (!url || !activeProfileId) { |
836 | 844 | error = "Please enter a URL and select a profile"; |
837 | 845 | return; |
838 | 846 | } |
839 | 847 |
|
840 | 848 | loading = true; |
841 | 849 |
|
842 | 850 | try { |
843 | | - const body: any = { url, profileId: selectedProfileId, saveToLibrary }; |
| 851 | + const body: any = { url, profileId: activeProfileId, saveToLibrary }; |
844 | 852 | if (advancedMode) { |
845 | 853 | const cf = buildCustomFlags(); |
846 | 854 | if (cf.length > 0) body.customFlags = cf; |
|
893 | 901 | } else { |
894 | 902 | profiles = [...profiles, profile]; |
895 | 903 | } |
896 | | - selectedProfileId = profile.id; |
| 904 | + if (profile.audioOnly) { |
| 905 | + selectedAudioProfileId = profile.id; |
| 906 | + } else { |
| 907 | + selectedVideoProfileId = profile.id; |
| 908 | + } |
897 | 909 | showSaveDialog = false; |
898 | 910 | newProfileName = ""; |
899 | 911 | } catch (e: any) { |
|
905 | 917 |
|
906 | 918 | function resetToDefaults() { |
907 | 919 | const sysDefault = profiles.find((p: any) => p.isDefault); |
908 | | - if (sysDefault) { |
909 | | - selectedProfileId = sysDefault.id; |
910 | | - loadProfileFlags(sysDefault); |
| 920 | + if (sysDefault && !sysDefault.audioOnly) { |
| 921 | + selectedVideoProfileId = sysDefault.id; |
911 | 922 | } |
| 923 | + const defaultAudio = profiles.find((p: any) => p.isSystem && p.audioOnly); |
| 924 | + if (defaultAudio) { |
| 925 | + selectedAudioProfileId = defaultAudio.id; |
| 926 | + } |
| 927 | + const active = profiles.find((p: any) => p.id === activeProfileId); |
| 928 | + if (active) loadProfileFlags(active); |
912 | 929 | newProfileName = ""; |
913 | 930 | showSaveDialog = false; |
914 | 931 | } |
915 | 932 |
|
916 | | - function selectProfile(id: string) { |
917 | | - selectedProfileId = id; |
| 933 | + function selectVideoProfile(id: string) { |
| 934 | + selectedVideoProfileId = selectedVideoProfileId === id ? null : id; |
| 935 | + if (selectedVideoProfileId) selectedAudioProfileId = null; |
| 936 | + if (advancedMode) { |
| 937 | + const profile = profiles.find((p) => p.id === activeProfileId); |
| 938 | + if (profile) loadProfileFlags(profile); |
| 939 | + } |
| 940 | + } |
| 941 | +
|
| 942 | + function selectAudioProfile(id: string) { |
| 943 | + selectedAudioProfileId = id; |
| 944 | + selectedVideoProfileId = null; |
918 | 945 | if (advancedMode) { |
919 | 946 | const profile = profiles.find((p) => p.id === id); |
920 | 947 | if (profile) loadProfileFlags(profile); |
921 | 948 | } |
| 949 | + } |
| 950 | +
|
| 951 | + function selectProfile(id: string) { |
| 952 | + if (activeProfileId === id) { |
| 953 | + resetToDefaults(); |
| 954 | + return; |
| 955 | + } |
| 956 | + const profile = profiles.find((p) => p.id === id); |
| 957 | + if (profile?.audioOnly) { |
| 958 | + selectAudioProfile(id); |
| 959 | + } else { |
| 960 | + selectVideoProfile(id); |
| 961 | + } |
922 | 962 | if (showSaveDialog) { |
923 | 963 | const custom = customProfiles.find((p) => p.id === id); |
924 | 964 | newProfileName = custom ? custom.name : ""; |
925 | 965 | } |
926 | 966 | } |
927 | 967 |
|
928 | | - function handleFormClick(e: MouseEvent) { |
929 | | - if (!advancedMode) return; |
930 | | - if (!customProfiles.some((p) => p.id === selectedProfileId)) return; |
931 | | - const target = e.target as HTMLElement; |
932 | | - if (target.closest(".advanced-panel, .profile-btn, .mode-toggle")) return; |
933 | | - resetToDefaults(); |
934 | | - } |
935 | | -
|
936 | 968 | let videoProfiles = $derived( |
937 | 969 | profiles.filter((p: any) => p.isSystem && !p.audioOnly).slice(0, 4), |
938 | 970 | ); |
|
943 | 975 | </script> |
944 | 976 |
|
945 | 977 | <div class="download-form"> |
946 | | - <!-- svelte-ignore a11y_click_events_have_key_events --> |
947 | | - <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> |
948 | | - <form onsubmit={handleSubmit} onclick={handleFormClick}> |
| 978 | + <form onsubmit={handleSubmit}> |
949 | 979 | <div class="form-header"> |
950 | 980 | <label for="url">Video URL</label> |
951 | 981 | <button |
|
955 | 985 | onclick={() => { |
956 | 986 | advancedMode = !advancedMode; |
957 | 987 | if (advancedMode) { |
958 | | - const profile = profiles.find((p) => p.id === selectedProfileId); |
| 988 | + const profile = profiles.find((p) => p.id === activeProfileId); |
959 | 989 | if (profile) loadProfileFlags(profile); |
960 | 990 | } |
961 | 991 | }} |
|
1004 | 1034 | <button |
1005 | 1035 | type="button" |
1006 | 1036 | class="profile-btn" |
1007 | | - class:active={selectedProfileId === profile.id} |
1008 | | - onclick={() => selectProfile(profile.id)} |
| 1037 | + class:active={selectedVideoProfileId === profile.id} |
| 1038 | + onclick={() => selectVideoProfile(profile.id)} |
1009 | 1039 | disabled={loading} |
1010 | 1040 | > |
1011 | 1041 | {profile.name}{#if profile.isDefault} |
1012 | 1042 | *{/if} |
1013 | 1043 | </button> |
1014 | 1044 | {/each} |
1015 | 1045 | </div> |
1016 | | - <div class="audio-quality-row"> |
1017 | | - <span class="audio-quality-label">Audio Quality</span> |
1018 | | - <select bind:value={audioQuality} disabled={loading} class="audio-quality-select"> |
1019 | | - <option value="0">High</option> |
1020 | | - <option value="5">Medium</option> |
1021 | | - <option value="9">Low</option> |
1022 | | - </select> |
1023 | | - </div> |
1024 | 1046 | </div> |
1025 | 1047 |
|
| 1048 | + {#if selectedVideoProfileId} |
| 1049 | + <div class="profile-group"> |
| 1050 | + <span class="profile-group-label">Audio Quality</span> |
| 1051 | + <div class="profile-buttons"> |
| 1052 | + <button |
| 1053 | + type="button" |
| 1054 | + class="profile-btn" |
| 1055 | + class:active={audioQuality === "0"} |
| 1056 | + onclick={() => audioQuality = "0"} |
| 1057 | + disabled={loading} |
| 1058 | + > |
| 1059 | + High |
| 1060 | + </button> |
| 1061 | + <button |
| 1062 | + type="button" |
| 1063 | + class="profile-btn" |
| 1064 | + class:active={audioQuality === "5"} |
| 1065 | + onclick={() => audioQuality = "5"} |
| 1066 | + disabled={loading} |
| 1067 | + > |
| 1068 | + Medium |
| 1069 | + </button> |
| 1070 | + <button |
| 1071 | + type="button" |
| 1072 | + class="profile-btn" |
| 1073 | + class:active={audioQuality === "9"} |
| 1074 | + onclick={() => audioQuality = "9"} |
| 1075 | + disabled={loading} |
| 1076 | + > |
| 1077 | + Low |
| 1078 | + </button> |
| 1079 | + </div> |
| 1080 | + </div> |
| 1081 | + {/if} |
| 1082 | + |
1026 | 1083 | <div class="profile-group"> |
1027 | | - <span class="profile-group-label">Audio Only</span> |
| 1084 | + <span class="profile-group-label">Audio (only)</span> |
1028 | 1085 | <div class="profile-buttons"> |
1029 | 1086 | {#each audioProfiles as profile} |
1030 | 1087 | <button |
1031 | 1088 | type="button" |
1032 | 1089 | class="profile-btn" |
1033 | | - class:active={selectedProfileId === profile.id} |
1034 | | - onclick={() => selectProfile(profile.id)} |
| 1090 | + class:active={selectedAudioProfileId === profile.id} |
| 1091 | + onclick={() => selectAudioProfile(profile.id)} |
1035 | 1092 | disabled={loading} |
1036 | 1093 | > |
1037 | 1094 | {profile.name} |
|
1072 | 1129 | </button> |
1073 | 1130 | </div> |
1074 | 1131 | </div> |
1075 | | - |
1076 | 1132 | </div> |
1077 | 1133 | {:else if customProfiles.length > 0} |
1078 | 1134 | <div class="profile-quick-select"> |
|
1083 | 1139 | <button |
1084 | 1140 | type="button" |
1085 | 1141 | class="profile-btn custom" |
1086 | | - class:active={selectedProfileId === profile.id} |
| 1142 | + class:active={activeProfileId === profile.id} |
1087 | 1143 | onclick={() => selectProfile(profile.id)} |
1088 | 1144 | disabled={loading} |
1089 | 1145 | > |
|
1109 | 1165 | class="btn-save-profile" |
1110 | 1166 | onclick={() => { |
1111 | 1167 | const custom = customProfiles.find( |
1112 | | - (p) => p.id === selectedProfileId, |
| 1168 | + (p) => p.id === activeProfileId, |
1113 | 1169 | ); |
1114 | 1170 | newProfileName = custom ? custom.name : ""; |
1115 | 1171 | showSaveDialog = !showSaveDialog; |
|
1497 | 1553 | color: var(--text-primary); |
1498 | 1554 | } |
1499 | 1555 |
|
1500 | | - .audio-quality-row { |
1501 | | - display: flex; |
1502 | | - align-items: center; |
1503 | | - gap: var(--spacing-sm); |
1504 | | - margin-top: var(--spacing-sm); |
1505 | | - } |
1506 | | -
|
1507 | | - .audio-quality-label { |
1508 | | - font-size: 0.75rem; |
1509 | | - color: var(--text-tertiary); |
1510 | | - font-weight: 500; |
1511 | | - } |
1512 | | -
|
1513 | | - .audio-quality-select { |
1514 | | - flex: 1; |
1515 | | - padding: var(--spacing-sm) var(--spacing-md); |
1516 | | - background: var(--bg-tertiary); |
1517 | | - border: 1px solid var(--border); |
1518 | | - border-radius: var(--radius-md); |
1519 | | - color: var(--text-secondary); |
1520 | | - font-size: 0.875rem; |
1521 | | - cursor: pointer; |
1522 | | - transition: all var(--transition-fast); |
1523 | | - } |
1524 | | -
|
1525 | | - .audio-quality-select:focus { |
1526 | | - outline: none; |
1527 | | - border-color: var(--accent-primary); |
1528 | | - } |
1529 | | -
|
1530 | 1556 | .profile-btn.custom { |
1531 | 1557 | border-style: dashed; |
1532 | 1558 | border-color: var(--accent-dim); |
|
0 commit comments