source: KernelRecordsMVC.Web/Views/Admin/CreateRelease.cshtml@ d2eccb3

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

Added major improvements.

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