SlotGrid.svelte 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <script lang="ts" module>
  2. export type Cell = {
  3. slot: number;
  4. label: string; // "A01"
  5. name: string; // sample or sequence name; empty for an empty slot
  6. meta: string; // small line under the name
  7. aria: string;
  8. warn?: boolean; // something is wrong with this slot, e.g. its sample is missing
  9. };
  10. </script>
  11. <script lang="ts">
  12. import { draggedSlot, endDrag, isPadDrag, startDrag } from "./drag";
  13. // A 4x4 grid of pads or sequence slots, used by both tabs.
  14. let {
  15. cells,
  16. selected,
  17. onpick,
  18. dropTarget = false,
  19. ondroppad,
  20. }: {
  21. cells: Cell[]; // 16 cells in grid order, top-left first
  22. selected: number;
  23. onpick: (slot: number) => void;
  24. dropTarget?: boolean; // accept WAV files dropped from the system
  25. ondroppad?: (from: number, to: number, copy: boolean) => void; // filled cells can be dragged onto others
  26. } = $props();
  27. let over: number | null = $state(null); // cell a pad is dragged over
  28. let copying = $state(false); // Alt held during the drag
  29. function dragover(e: DragEvent, slot: number) {
  30. if (!ondroppad || !isPadDrag(e)) return;
  31. e.preventDefault(); // allow the drop
  32. copying = e.altKey;
  33. if (e.dataTransfer) e.dataTransfer.dropEffect = e.altKey ? "copy" : "move";
  34. over = slot === draggedSlot() ? null : slot;
  35. }
  36. function drop(e: DragEvent, slot: number) {
  37. if (!ondroppad || !isPadDrag(e)) return;
  38. e.preventDefault();
  39. const from = draggedSlot();
  40. over = null;
  41. endDrag();
  42. if (from !== null && from !== slot) ondroppad(from, slot, e.altKey);
  43. }
  44. </script>
  45. <div class="grid">
  46. {#each cells as c (c.slot)}
  47. <button
  48. type="button"
  49. class:filled={c.name !== ""}
  50. class:warn={c.warn}
  51. class:on={c.slot === selected}
  52. class:over={c.slot === over}
  53. class:copying={c.slot === over && copying}
  54. aria-pressed={c.slot === selected}
  55. aria-label={c.aria}
  56. data-file-drop-target={dropTarget ? "" : undefined}
  57. data-slot={c.slot}
  58. onclick={() => onpick(c.slot)}
  59. draggable={!!ondroppad && c.name !== ""}
  60. ondragstart={(e) => startDrag(e, c.slot)}
  61. ondragend={() => {
  62. over = null;
  63. endDrag();
  64. }}
  65. ondragover={(e) => dragover(e, c.slot)}
  66. ondragleave={() => {
  67. if (over === c.slot) over = null;
  68. }}
  69. ondrop={(e) => drop(e, c.slot)}
  70. >
  71. <span class="num">{c.label}</span>
  72. <span class="name">{c.name}</span>
  73. <span class="meta">{c.meta}</span>
  74. </button>
  75. {/each}
  76. </div>
  77. <style>
  78. /* Fills the height its panel gives it: four equal rows. */
  79. .grid {
  80. flex: 1;
  81. min-height: 0;
  82. display: grid;
  83. grid-template-columns: repeat(4, minmax(0, 1fr));
  84. grid-template-rows: repeat(4, minmax(0, 1fr));
  85. gap: 8px;
  86. }
  87. button {
  88. min-height: 0;
  89. padding: 6px 8px;
  90. display: flex;
  91. flex-direction: column;
  92. align-items: flex-start;
  93. justify-content: space-between;
  94. gap: 4px;
  95. text-align: left;
  96. background: var(--cell-empty);
  97. color: var(--text);
  98. border: 2px solid var(--line);
  99. border-radius: 8px;
  100. font: inherit;
  101. cursor: pointer;
  102. overflow: hidden;
  103. }
  104. button.filled {
  105. background: var(--cell-filled);
  106. border-color: var(--cell-filled-edge);
  107. }
  108. button.on {
  109. border-color: var(--accent);
  110. }
  111. button.warn .meta {
  112. color: #ff8a8a;
  113. }
  114. /* A pad dragged over this cell: dashed for move or swap, solid for copy. */
  115. button.over {
  116. border-style: dashed;
  117. border-color: var(--accent);
  118. }
  119. button.over.copying {
  120. border-style: solid;
  121. }
  122. button[draggable="true"] {
  123. cursor: grab;
  124. }
  125. /* Wails adds this class while a file is dragged over a drop target. */
  126. button:global(.file-drop-target-active) {
  127. border-color: var(--accent);
  128. background: rgba(245, 165, 36, 0.12);
  129. }
  130. .num {
  131. font-family: var(--mono);
  132. font-size: 11px;
  133. font-weight: 500;
  134. color: var(--muted);
  135. }
  136. button.on .num {
  137. color: var(--accent);
  138. }
  139. .name {
  140. font-size: 12px;
  141. font-weight: 600;
  142. line-height: 1.25;
  143. max-width: 100%;
  144. overflow: hidden;
  145. text-overflow: ellipsis;
  146. white-space: nowrap;
  147. }
  148. .meta {
  149. font-family: var(--mono);
  150. font-size: 11px;
  151. color: var(--soft);
  152. min-height: 15px;
  153. white-space: nowrap;
  154. }
  155. </style>