source: KernelRecordsMVC.Web/Views/Admin/CreateRelease.cshtml@ 595d2e5

main
Last change on this file since 595d2e5 was 595d2e5, checked in by mmilevski <markomilevski3@…>, 4 weeks ago

Various UI bugfixes.

  • Property mode set to 100644
File size: 25.5 KB
Line 
1@model KernelRecordsMVC.Application.ViewModels.CreateReleaseViewModel
2
3@{
4ViewData["Title"] = "Create Release";
5}
6
7<div class="admin-page admin-form-page">
8
9 <!-- =====================================================
10 HEADER
11 ===================================================== -->
12
13 <div class="admin-header">
14
15 <div>
16
17 <a asp-action="Index"
18 class="admin-back-link">
19 ← Dashboard
20 </a>
21
22 <span class="admin-eyebrow">
23 CATALOG MANAGEMENT
24 </span>
25
26 <h1>Create Release</h1>
27
28 <p>
29 Add an album or single, assign artists
30 and build its track listing.
31 </p>
32
33 </div>
34
35 </div>
36
37
38 <form asp-action="CreateRelease"
39 method="post"
40 id="createReleaseForm">
41
42 @Html.AntiForgeryToken()
43
44
45 <div asp-validation-summary="All"
46 class="admin-validation">
47 </div>
48
49 <!-- =================================================
50 STEP 1 - RELEASE TYPE
51 ================================================= -->
52
53 <section class="admin-form-card release-create-section">
54
55 <div class="admin-form-card-header">
56
57 <span class="admin-eyebrow">
58 STEP 01
59 </span>
60
61 <h2>Release Type</h2>
62
63 <p class="admin-section-description">
64 Choose whether this release is an album
65 or a single.
66 </p>
67
68 </div>
69
70
71 <div class="admin-modification-options">
72
73 <!-- ALBUM -->
74
75 <label class="admin-modification-option">
76
77 <input asp-for="ReleaseType"
78 type="radio"
79 value="ALBUM"
80 id="releaseTypeAlbum" />
81
82 <span>
83
84 <strong>
85 Album
86 </strong>
87
88 <small>
89 Create a release with multiple tracks.
90 </small>
91
92 </span>
93
94 </label>
95
96
97 <!-- SINGLE -->
98
99 <label class="admin-modification-option">
100
101 <input asp-for="ReleaseType"
102 type="radio"
103 value="SINGLE"
104 id="releaseTypeSingle" />
105
106 <span>
107
108 <strong>
109 Single
110 </strong>
111
112 <small>
113 Create an individual single release.
114 </small>
115
116 </span>
117
118 </label>
119
120 </div>
121
122
123 <span asp-validation-for="ReleaseType"
124 class="admin-field-error">
125 </span>
126
127 </section>
128
129
130 <!-- =================================================
131 STEP 2 - RELEASE INFORMATION
132 ================================================= -->
133
134 <section class="admin-form-card release-create-section">
135
136 <div class="admin-form-card-header">
137
138 <span class="admin-eyebrow">
139 STEP 02
140 </span>
141
142 <h2>Release Information</h2>
143
144 <p class="admin-section-description">
145 Enter the main catalog information for this release.
146 </p>
147
148 </div>
149
150
151 <!-- TITLE -->
152
153 <div class="admin-form-field">
154
155 <label asp-for="Title">
156 Title
157 </label>
158
159 <input asp-for="Title"
160 placeholder="Enter release title" />
161
162 <span asp-validation-for="Title"
163 class="admin-field-error">
164 </span>
165
166 </div>
167
168
169 <!-- GENRE + DATE -->
170
171 <div class="admin-form-row">
172
173 <div class="admin-form-field">
174
175 <label asp-for="Genre">
176 Genre
177 </label>
178
179 <input asp-for="Genre"
180 placeholder="Rock, Pop, Jazz..." />
181
182 <span asp-validation-for="Genre"
183 class="admin-field-error">
184 </span>
185
186 </div>
187
188
189 <div class="admin-form-field">
190
191 <label asp-for="ReleaseDate">
192 Release Date
193 </label>
194
195 <input asp-for="ReleaseDate"
196 type="date" />
197
198 <span asp-validation-for="ReleaseDate"
199 class="admin-field-error">
200 </span>
201
202 </div>
203
204 </div>
205
206
207 <!-- RECORD LABEL -->
208
209 <div class="admin-form-field">
210
211 <label asp-for="RecordLabel">
212 Record Label
213 </label>
214
215 <input asp-for="RecordLabel"
216 placeholder="Record label (optional)" />
217
218 <span asp-validation-for="RecordLabel"
219 class="admin-field-error">
220 </span>
221
222 </div>
223
224
225 <!-- COVER PHOTO -->
226
227 <div class="admin-form-field">
228
229 <label asp-for="CoverPhoto">
230 Cover Photo URL
231 </label>
232
233 <input asp-for="CoverPhoto"
234 id="coverPhotoInput"
235 placeholder="https://..." />
236
237 <span asp-validation-for="CoverPhoto"
238 class="admin-field-error">
239 </span>
240
241 <small>
242 Paste a direct image URL for the release artwork.
243 </small>
244
245 </div>
246
247
248 <!-- COVER PREVIEW -->
249
250 <div class="admin-cover-preview">
251
252 <span class="admin-eyebrow">
253 COVER PREVIEW
254 </span>
255
256 <div class="admin-cover-preview-image"
257 id="coverPreview">
258
259 <span>♪</span>
260
261 </div>
262
263 </div>
264
265 </section>
266
267
268 <!-- =================================================
269 STEP 3 - ARTISTS
270 ================================================= -->
271
272 <section class="admin-form-card release-create-section">
273
274 <div class="admin-form-card-header">
275
276 <span class="admin-eyebrow">
277 STEP 03
278 </span>
279
280 <h2>Artists</h2>
281
282 <p class="admin-section-description">
283 Select the main artist and any featured artists
284 for this release.
285 </p>
286
287 </div>
288
289
290 <!-- MAIN ARTIST -->
291
292 <div class="admin-form-field">
293
294 <label asp-for="MainArtistId">
295 Main Artist
296 </label>
297
298 <select asp-for="MainArtistId">
299
300 <option value="">
301 Select main artist
302 </option>
303
304 @foreach (var artist in ViewBag.Artists)
305 {
306 <option value="@artist.ArtistId">
307 @artist.ArtistName
308 </option>
309 }
310
311 </select>
312
313 <span asp-validation-for="MainArtistId"
314 class="admin-field-error">
315 </span>
316
317 <small>
318 The primary artist associated with this release.
319 </small>
320
321 </div>
322
323
324 <!-- FEATURED ARTISTS -->
325
326 <div class="admin-form-field">
327
328 <label asp-for="FeaturedArtistIds">
329 Featured Artists
330 </label>
331
332 <select asp-for="FeaturedArtistIds"
333 multiple
334 class="admin-multi-select">
335
336 @foreach (var artist in ViewBag.Artists)
337 {
338 <option value="@artist.ArtistId">
339 @artist.ArtistName
340 </option>
341 }
342
343 </select>
344
345 <small>
346 Hold Ctrl on Windows or Cmd on macOS
347 to select multiple artists.
348 </small>
349
350 <span asp-validation-for="FeaturedArtistIds"
351 class="admin-field-error">
352 </span>
353
354 </div>
355
356 </section>
357
358
359 <!-- =================================================
360 SINGLE SETTINGS
361 ================================================= -->
362
363 <section class="admin-form-card release-create-section"
364 id="singleSection">
365
366 <div class="admin-form-card-header">
367
368 <span class="admin-eyebrow">
369 STEP 04
370 </span>
371
372 <h2>Single Information</h2>
373
374 <p class="admin-section-description">
375 Enter the duration of the single.
376 </p>
377
378 </div>
379
380
381 <div class="admin-form-field">
382
383 <label asp-for="SingleDuration">
384 Duration
385 </label>
386
387 <input asp-for="SingleDuration"
388 placeholder="03:45" />
389
390 <span asp-validation-for="SingleDuration"
391 class="admin-field-error">
392 </span>
393
394 <small>
395 Example: 03:45
396 </small>
397
398 </div>
399
400 </section>
401
402
403 <!-- =================================================
404 ALBUM TRACKS
405 ================================================= -->
406
407 <section class="admin-form-card release-create-section"
408 id="albumSection">
409
410 <div class="admin-form-card-header track-header">
411
412 <div>
413
414 <span class="admin-eyebrow">
415 STEP 04
416 </span>
417
418 <h2>Track List</h2>
419
420 <p class="admin-section-description">
421 Add tracks in the order they appear
422 on the album.
423 </p>
424
425 </div>
426
427
428 <button type="button"
429 class="admin-button secondary"
430 id="addTrackButton">
431
432 + Add Track
433
434 </button>
435
436 </div>
437
438
439 <div id="tracksContainer">
440
441 @for (var i = 0;
442 i < Model.Tracks.Count;
443 i++)
444 {
445 <div class="admin-track-card"
446 data-track-row>
447
448 <!-- TRACK NUMBER -->
449
450 <div class="admin-track-number">
451 @(i + 1)
452 </div>
453
454
455 <!-- TRACK CONTENT -->
456
457 <div class="admin-track-content">
458
459 <div class="admin-form-row">
460
461 <!-- TRACK NAME -->
462
463 <div class="admin-form-field">
464
465 <label>
466 Track Name
467 </label>
468
469 <input asp-for="Tracks[i].SongName"
470 data-field="SongName"
471 placeholder="Track title" />
472
473 <span asp-validation-for="Tracks[i].SongName"
474 class="admin-field-error">
475 </span>
476
477 </div>
478
479
480 <!-- DURATION -->
481
482 <div class="admin-form-field">
483
484 <label>
485 Duration
486 </label>
487
488 <input asp-for="Tracks[i].SongDuration"
489 data-field="SongDuration"
490 placeholder="03:42"
491 inputmode="numeric"
492 maxlength="5" />
493
494 <span asp-validation-for="Tracks[i].SongDuration"
495 class="admin-field-error">
496 </span>
497
498 </div>
499
500 </div>
501
502
503 <!-- TRACK ARTISTS -->
504
505 <div class="admin-form-field">
506
507 <label>
508 Track Artists
509 </label>
510
511 <select asp-for="Tracks[i].ArtistIds"
512 multiple
513 data-field="ArtistIds"
514 class="admin-multi-select">
515
516 @foreach (var artist in ViewBag.Artists)
517 {
518 <option value="@artist.ArtistId">
519 @artist.ArtistName
520 </option>
521 }
522
523 </select>
524
525 <small>
526 If no artist is selected,
527 the release's main artist will
528 automatically be used.
529 </small>
530
531 </div>
532
533 </div>
534
535
536 <!-- REMOVE TRACK -->
537
538 <button type="button"
539 class="admin-track-remove"
540 data-remove-track
541 title="Remove track">
542
543 ×
544
545 </button>
546
547 </div>
548 }
549
550 </div>
551
552 </section>
553
554
555 <!-- =================================================
556 SUBMIT
557 ================================================= -->
558
559 <div class="admin-release-submit">
560
561 <div>
562
563 <span class="admin-eyebrow">
564 READY TO CREATE?
565 </span>
566
567 <p>
568 The release, artists and tracks
569 will be created together.
570 </p>
571
572 </div>
573
574
575 <div class="admin-form-actions release-actions">
576
577 <a asp-action="Index"
578 class="admin-button secondary">
579
580 Cancel
581
582 </a>
583
584
585 <button type="submit"
586 class="admin-button primary">
587
588 Create Release
589
590 </button>
591
592 </div>
593
594 </div>
595
596 </form>
597
598</div>
599
600
601<!-- =========================================================
602 TRACK TEMPLATE
603 ========================================================= -->
604
605<template id="trackTemplate">
606
607 <div class="admin-track-card"
608 data-track-row>
609
610 <!-- TRACK NUMBER -->
611
612 <div class="admin-track-number">
613 1
614 </div>
615
616
617 <!-- TRACK CONTENT -->
618
619 <div class="admin-track-content">
620
621 <div class="admin-form-row">
622
623 <!-- NAME -->
624
625 <div class="admin-form-field">
626
627 <label>
628 Track Name
629 </label>
630
631 <input type="text"
632 data-field="SongName"
633 placeholder="Track title" />
634
635 </div>
636
637
638 <!-- DURATION -->
639
640 <div class="admin-form-field">
641
642 <label>
643 Duration
644 </label>
645
646 <input type="text"
647 data-field="SongDuration"
648 placeholder="03:42"
649 inputmode="numeric"
650 maxlength="5"
651 pattern="\d{1,2}:[0-5]\d"
652 title="Enter a duration like 03:42" />
653
654 </div>
655
656 </div>
657
658
659 <!-- ARTISTS -->
660
661 <div class="admin-form-field">
662
663 <label>
664 Track Artists
665 </label>
666
667 <select multiple
668 data-field="ArtistIds"
669 class="admin-multi-select">
670
671 @foreach (var artist in ViewBag.Artists)
672 {
673 <option value="@artist.ArtistId">
674 @artist.ArtistName
675 </option>
676 }
677
678 </select>
679
680 <small>
681 If left empty, the release's main artist
682 will automatically be assigned.
683 </small>
684
685 </div>
686
687 </div>
688
689
690 <!-- REMOVE -->
691
692 <button type="button"
693 class="admin-track-remove"
694 data-remove-track
695 title="Remove track">
696
697 ×
698
699 </button>
700
701 </div>
702
703</template>
704
705
706@section Scripts
707{
708<partial name="_ValidationScriptsPartial" />
709
710<script>
711
712 document.addEventListener(
713 "DOMContentLoaded",
714 function ()
715 {
716 // ==========================================
717 // ELEMENTS
718 // ==========================================
719
720 const albumRadio =
721 document.getElementById(
722 "releaseTypeAlbum");
723
724 const singleRadio =
725 document.getElementById(
726 "releaseTypeSingle");
727
728 const albumSection =
729 document.getElementById(
730 "albumSection");
731
732 const singleSection =
733 document.getElementById(
734 "singleSection");
735
736 const tracksContainer =
737 document.getElementById(
738 "tracksContainer");
739
740 const addTrackButton =
741 document.getElementById(
742 "addTrackButton");
743
744 const trackTemplate =
745 document.getElementById(
746 "trackTemplate");
747
748 const form =
749 document.getElementById(
750 "createReleaseForm");
751
752
753 // ==========================================
754 // RELEASE TYPE
755 // ==========================================
756
757 function updateReleaseType()
758 {
759 const isAlbum =
760 albumRadio.checked;
761
762
763 // Show / hide sections.
764 albumSection.style.display =
765 isAlbum
766 ? "block"
767 : "none";
768
769
770 singleSection.style.display =
771 isAlbum
772 ? "none"
773 : "block";
774
775
776 // Disable fields belonging to
777 // the hidden release type.
778 const albumInputs =
779 albumSection.querySelectorAll(
780 "input, select, textarea");
781
782
783 const singleInputs =
784 singleSection.querySelectorAll(
785 "input, select, textarea");
786
787
788 albumInputs.forEach(
789 function (input)
790 {
791 input.disabled =
792 !isAlbum;
793 });
794
795
796 singleInputs.forEach(
797 function (input)
798 {
799 input.disabled =
800 isAlbum;
801 });
802 }
803
804
805 albumRadio.addEventListener(
806 "change",
807 updateReleaseType);
808
809
810 singleRadio.addEventListener(
811 "change",
812 updateReleaseType);
813
814
815 // ==========================================
816 // TRACK INDEXING
817 // ==========================================
818
819 function reindexTracks()
820 {
821 const rows =
822 tracksContainer.querySelectorAll(
823 "[data-track-row]");
824
825
826 rows.forEach(
827 function (row, index)
828 {
829 const number =
830 row.querySelector(
831 ".admin-track-number");
832
833
834 const songName =
835 row.querySelector(
836 '[data-field="SongName"]');
837
838
839 const duration =
840 row.querySelector(
841 '[data-field="SongDuration"]');
842
843
844 const artists =
845 row.querySelector(
846 '[data-field="ArtistIds"]');
847
848
849 // Display number.
850 number.textContent =
851 index + 1;
852
853
854 // Song name.
855 songName.name =
856 `Tracks[${index}].SongName`;
857
858 songName.id =
859 `Tracks_${index}__SongName`;
860
861
862 // Duration.
863 duration.name =
864 `Tracks[${index}].SongDuration`;
865
866 duration.id =
867 `Tracks_${index}__SongDuration`;
868
869
870 // Artists.
871 artists.name =
872 `Tracks[${index}].ArtistIds`;
873
874 artists.id =
875 `Tracks_${index}__ArtistIds`;
876 });
877 }
878
879
880 // ==========================================
881 // ADD TRACK
882 // ==========================================
883
884 addTrackButton.addEventListener(
885 "click",
886 function ()
887 {
888 const fragment =
889 trackTemplate
890 .content
891 .cloneNode(true);
892
893
894 tracksContainer.appendChild(
895 fragment);
896
897
898 reindexTracks();
899 });
900
901
902 // ==========================================
903 // REMOVE TRACK
904 // ==========================================
905
906 tracksContainer.addEventListener(
907 "click",
908 function (event)
909 {
910 const button =
911 event.target.closest(
912 "[data-remove-track]");
913
914
915 if (!button)
916 return;
917
918
919 const rows =
920 tracksContainer.querySelectorAll(
921 "[data-track-row]");
922
923
924 // Album must always keep
925 // at least one track row.
926 if (rows.length <= 1)
927 {
928 alert(
929 "An album must have at least one track.");
930
931 return;
932 }
933
934
935 const row =
936 button.closest(
937 "[data-track-row]");
938
939
940 row.remove();
941
942
943 reindexTracks();
944 });
945
946
947 // ==========================================
948 // BEFORE SUBMIT
949 // ==========================================
950
951 form.addEventListener(
952 "submit",
953 function ()
954 {
955 // Make sure indexes are always
956 // sequential before ASP.NET binding.
957 reindexTracks();
958 });
959
960
961 // ==========================================
962 // COVER PREVIEW
963 // ==========================================
964
965 const coverInput =
966 document.getElementById(
967 "coverPhotoInput");
968
969
970 const coverPreview =
971 document.getElementById(
972 "coverPreview");
973
974
975 function showCoverPlaceholder()
976 {
977 coverPreview.innerHTML =
978 "<span>♪</span>";
979 }
980
981
982 function updateCoverPreview()
983 {
984 const url =
985 coverInput.value.trim();
986
987
988 if (!url)
989 {
990 showCoverPlaceholder();
991
992 return;
993 }
994
995
996 coverPreview.innerHTML = "";
997
998
999 const image =
1000 document.createElement(
1001 "img");
1002
1003
1004 image.src =
1005 url;
1006
1007 image.alt =
1008 "Cover preview";
1009
1010
1011 image.addEventListener(
1012 "error",
1013 function ()
1014 {
1015 showCoverPlaceholder();
1016 });
1017
1018
1019 coverPreview.appendChild(
1020 image);
1021 }
1022
1023
1024 coverInput.addEventListener(
1025 "input",
1026 updateCoverPreview);
1027
1028
1029 // ==========================================
1030 // INITIAL STATE
1031 // ==========================================
1032
1033 updateReleaseType();
1034
1035 reindexTracks();
1036
1037 updateCoverPreview();
1038 });
1039
1040</script>
1041}
Note: See TracBrowser for help on using the repository browser.