mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
add child level context menu
- more typescript
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ import AlbumArt from "./components/LeftSidebar/AlbumArt.vue";
|
||||
import NavBar from "./components/nav/NavBar.vue";
|
||||
import Tabs from "./components/RightSideBar/Tabs.vue";
|
||||
import SearchInput from "./components/RightSideBar/SearchInput.vue";
|
||||
import useContextStore from "./stores/context.js";
|
||||
import useContextStore from "./stores/context";
|
||||
import ContextMenu from "./components/contextMenu.vue";
|
||||
|
||||
const context_store = useContextStore();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.6514 13.6543C19.6426 13.3467 19.5283 13.083 19.291 12.8457L12.4531 6.15723C12.251 5.96387 12.0137 5.8584 11.7236 5.8584C11.1348 5.8584 10.6777 6.31543 10.6777 6.9043C10.6777 7.18555 10.792 7.44922 10.9941 7.65137L17.1465 13.6543L10.9941 19.6572C10.792 19.8594 10.6777 20.1143 10.6777 20.4043C10.6777 20.9932 11.1348 21.4502 11.7236 21.4502C12.0049 21.4502 12.251 21.3447 12.4531 21.1514L19.291 14.4541C19.5371 14.2256 19.6514 13.9619 19.6514 13.6543Z" fill="#F2F2F2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 585 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.63185 20.9668H11.6973C12.5322 20.9668 12.9629 20.5361 12.9629 19.7012V7.60742C12.9629 6.75488 12.5322 6.35059 11.6973 6.3418H9.63185C8.79688 6.3418 8.36622 6.77246 8.36622 7.60742V19.7012C8.35743 20.5361 8.7881 20.9668 9.63185 20.9668ZM16.3115 20.9668H18.3682C19.2031 20.9668 19.6338 20.5361 19.6338 19.7012V7.60742C19.6338 6.75488 19.2031 6.3418 18.3682 6.3418H16.3115C15.4678 6.3418 15.0459 6.77246 15.0459 7.60742V19.7012C15.0459 20.5361 15.4678 20.9668 16.3115 20.9668Z" fill="#F2F2F2"/>
|
||||
<path d="M10.9238 21.0723C11.4775 21.0723 11.9082 20.6592 11.9082 20.1143V7.21191C11.9082 6.66699 11.4775 6.25391 10.9238 6.25391C10.3701 6.25391 9.93945 6.66699 9.93945 7.21191V20.1143C9.93945 20.6592 10.3701 21.0723 10.9238 21.0723ZM17.0674 21.0723C17.6211 21.0723 18.0518 20.6592 18.0518 20.1143V7.21191C18.0518 6.66699 17.6211 6.25391 17.0674 6.25391C16.5137 6.25391 16.083 6.66699 16.083 7.21191V20.1143C16.083 20.6592 16.5137 21.0723 17.0674 21.0723Z" fill="#F2F2F2"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 607 B After Width: | Height: | Size: 578 B |
@@ -6,18 +6,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup(props, { emit }) {
|
||||
function loadMore() {
|
||||
emit("loadMore");
|
||||
}
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: "loadMore"): void;
|
||||
}>();
|
||||
|
||||
return {
|
||||
loadMore,
|
||||
};
|
||||
},
|
||||
};
|
||||
function loadMore() {
|
||||
emit("loadMore");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -42,4 +38,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
<!-- v-show="context.visible" -->
|
||||
<div
|
||||
class="context-menu rounded shadow-lg"
|
||||
:class="{ 'context-menu-visible': context.visible }"
|
||||
:class="[
|
||||
{ 'context-menu-visible': context.visible },
|
||||
{ 'context-normalizedX': context.normalizedX },
|
||||
{
|
||||
'context-normalizedY': context.normalizedY && context.hasManyChildren(),
|
||||
},
|
||||
]"
|
||||
:style="{
|
||||
left: context.x + 'px',
|
||||
top: context.y + 'px',
|
||||
@@ -17,31 +23,46 @@
|
||||
>
|
||||
<div class="icon image" :class="option.icon"></div>
|
||||
<div class="label ellip">{{ option.label }}</div>
|
||||
<div class="more image" v-if="option.children"></div>
|
||||
<div class="children rounded shadow-sm" v-if="option.children">
|
||||
<div
|
||||
class="context-item"
|
||||
v-for="child in option.children"
|
||||
:key="child"
|
||||
>
|
||||
<div class="label ellip" @click="child.action()">
|
||||
{{ child.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useContextStore from "@/stores/context.js";
|
||||
import useContextStore from "../stores/context";
|
||||
|
||||
const context = useContextStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 10rem;
|
||||
width: 12rem;
|
||||
height: min-content;
|
||||
z-index: 100000 !important;
|
||||
transform: scale(0);
|
||||
|
||||
padding: $small;
|
||||
background: $gray3;
|
||||
z-index: 100000 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: scale(0);
|
||||
transform-origin: top left;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@@ -55,6 +76,30 @@ const context = useContextStore();
|
||||
padding: 0 $small;
|
||||
border-radius: $small;
|
||||
color: rgb(255, 255, 255);
|
||||
position: relative;
|
||||
|
||||
.more {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-image: url("../assets/icons/more.svg");
|
||||
}
|
||||
|
||||
.children {
|
||||
position: absolute;
|
||||
right: -13rem;
|
||||
width: 13rem;
|
||||
|
||||
padding: $small;
|
||||
background: $gray3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: scale(0);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 1.25rem;
|
||||
@@ -92,6 +137,11 @@ const context = useContextStore();
|
||||
|
||||
&:hover {
|
||||
background: #234ece;
|
||||
|
||||
.children {
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,4 +160,22 @@ const context = useContextStore();
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.context-normalizedX {
|
||||
.more {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.context-item > .children {
|
||||
left: -13rem;
|
||||
transform-origin: center right;
|
||||
}
|
||||
}
|
||||
|
||||
.context-normalizedY {
|
||||
.context-item > .children {
|
||||
bottom: -0.5rem;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
<template>
|
||||
<div class="topnav rounded">
|
||||
<div class="left">
|
||||
<!-- <div class="btn">
|
||||
<div class="btn">
|
||||
<PlayBtn />
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">Album</div>
|
||||
<div class="title">beerbongs & bentleys</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center"></div>
|
||||
<div class="center rounded"></div>
|
||||
<div class="right"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PlayBtn from '../shared/PlayBtn.vue';
|
||||
|
||||
import PlayBtn from "../shared/PlayBtn.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topnav {
|
||||
background-color: $gray3;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
padding: 0 $small;
|
||||
@@ -28,7 +26,6 @@ import PlayBtn from '../shared/PlayBtn.vue';
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
// border: solid 1px $gray;
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
|
||||
@@ -39,5 +36,15 @@ import PlayBtn from '../shared/PlayBtn.vue';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: grid;
|
||||
|
||||
.songcard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
.play-btn {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: $red;
|
||||
background-color: $accent;
|
||||
background-image: url("../../assets/icons/play.svg");
|
||||
background-size: 1.25rem;
|
||||
background-position: 60%;
|
||||
|
||||
@@ -63,9 +63,9 @@
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks.js";
|
||||
import state from "../../composables/state";
|
||||
import useContextStore from "../../stores/context.js";
|
||||
import useContextStore from "../../stores/context";
|
||||
import { ref } from "vue";
|
||||
import trackContext from "../../composables/track_context";
|
||||
import trackContext from "../../contexts/track_context";
|
||||
import { Track } from "../../interfaces.js";
|
||||
|
||||
const contextStore = useContextStore();
|
||||
@@ -108,8 +108,6 @@ const current = state.current;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
//
|
||||
|
||||
.songlist-item {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
import { ref } from "vue";
|
||||
import perks from "../../composables/perks";
|
||||
import playAudio from "../../composables/playAudio";
|
||||
import useContextStore from "@/stores/context.js";
|
||||
import trackContext from "../../composables/track_context";
|
||||
import useContextStore from "@/stores/context";
|
||||
import trackContext from "../../contexts/track_context";
|
||||
|
||||
const contextStore = useContextStore();
|
||||
const context_on = ref(false);
|
||||
|
||||
@@ -17,20 +17,27 @@ export default (mouseX, mouseY) => {
|
||||
|
||||
const outOfBoundsOnY = scopeY + contextMenu.clientHeight > scope.clientHeight;
|
||||
|
||||
let normalizedX = mouseX;
|
||||
let normalizedY = mouseY;
|
||||
let normalX = mouseX;
|
||||
let normalY = mouseY;
|
||||
let normalizedX = false;
|
||||
let normalizedY = false;
|
||||
|
||||
if (window.innerWidth - normalX < 375) {
|
||||
normalizedX = true;
|
||||
}
|
||||
// ? normalize on X
|
||||
if (outOfBoundsOnX) {
|
||||
normalizedX = scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
|
||||
normalizedX -= 10
|
||||
normalX = scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
|
||||
normalX -= 10;
|
||||
}
|
||||
|
||||
// ? normalize on Y
|
||||
if (outOfBoundsOnY) {
|
||||
normalizedY = scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
|
||||
normalizedY -= 10
|
||||
normalY = scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
|
||||
normalY -= 10;
|
||||
|
||||
normalizedY = true;
|
||||
}
|
||||
|
||||
return { normalizedX, normalizedY };
|
||||
return { normalX, normalY, normalizedX, normalizedY };
|
||||
};
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import { Track } from './../interfaces';
|
||||
import { Track } from "../interfaces";
|
||||
import Router from "../router";
|
||||
|
||||
interface Option {
|
||||
type?: string;
|
||||
label?: string;
|
||||
action?: Function;
|
||||
icon?: string;
|
||||
critical?: Boolean;
|
||||
}
|
||||
import { Option } from "../interfaces";
|
||||
|
||||
/**
|
||||
* Returns a list of context menu items for a track.
|
||||
* @param {any} track a track object.
|
||||
* @return {Array<Option>} a list of context menu items.
|
||||
* @return {Array<Option>()} a list of context menu items.
|
||||
*/
|
||||
|
||||
export default (track: Track): Array<Option> => {
|
||||
const single_artist = track.artists.length === 1;
|
||||
|
||||
const children = () => {
|
||||
if (single_artist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return track.artists.map((artist) => {
|
||||
return <Option>{
|
||||
label: artist,
|
||||
action: () => console.log("artist"),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const option1: Option = {
|
||||
label: "Add to Playlist",
|
||||
action: () => console.log("Add to Playlist"),
|
||||
@@ -40,8 +48,19 @@ export default (track: Track): Array<Option> => {
|
||||
};
|
||||
|
||||
const option4: Option = {
|
||||
label: "Go to Artist",
|
||||
action: () => console.log("Go to Artist"),
|
||||
label: single_artist ? "Go to Artist" : "Go to Artists",
|
||||
icon: "artist",
|
||||
action: () => {
|
||||
if (single_artist) {
|
||||
console.log("Go to Artist");
|
||||
}
|
||||
},
|
||||
children: children(),
|
||||
};
|
||||
|
||||
const option7: Option = {
|
||||
label: "Go to Album Artist",
|
||||
action: () => console.log("Go to Album Artist"),
|
||||
icon: "artist",
|
||||
};
|
||||
|
||||
@@ -73,6 +92,7 @@ export default (track: Track): Array<Option> => {
|
||||
separator,
|
||||
option3,
|
||||
option4,
|
||||
option7,
|
||||
option5,
|
||||
separator,
|
||||
option6,
|
||||
+10
-1
@@ -29,4 +29,13 @@ interface Artist {
|
||||
image: string;
|
||||
}
|
||||
|
||||
export { Track, AlbumInfo, Artist };
|
||||
interface Option {
|
||||
type?: string;
|
||||
label?: string;
|
||||
action?: Function;
|
||||
children?: Option[] | false;
|
||||
icon?: string;
|
||||
critical?: Boolean;
|
||||
}
|
||||
|
||||
export { Track, AlbumInfo, Artist, Option };
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineStore } from "pinia";
|
||||
import normalize from "../composables/normalizeContextMenu";
|
||||
|
||||
export default defineStore("context-menu", {
|
||||
state: () => ({
|
||||
visible: false,
|
||||
options: [],
|
||||
x: 0,
|
||||
y: 0,
|
||||
}),
|
||||
actions: {
|
||||
showContextMenu(e, context_options) {
|
||||
if (this.visible) {
|
||||
this.visible = false;
|
||||
return
|
||||
}
|
||||
|
||||
this.options = context_options;
|
||||
const yo = normalize(e.clientX, e.clientY);
|
||||
this.x = yo.normalizedX;
|
||||
this.y = yo.normalizedY;
|
||||
this.visible = true;
|
||||
},
|
||||
hideContextMenu() {
|
||||
this.visible = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineStore } from "pinia";
|
||||
import normalize from "../composables/normalizeContextMenu";
|
||||
import { Option } from "../interfaces";
|
||||
|
||||
export default defineStore("context-menu", {
|
||||
state: () => ({
|
||||
visible: false,
|
||||
options: Array<Option>(),
|
||||
x: 500,
|
||||
y: 500,
|
||||
normalizedX: false,
|
||||
normalizedY: false,
|
||||
}),
|
||||
actions: {
|
||||
showContextMenu(e: any, context_options: Option[]) {
|
||||
if (this.visible) {
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.options = context_options;
|
||||
const yo = normalize(e.clientX, e.clientY);
|
||||
|
||||
this.x = yo.normalX;
|
||||
this.y = yo.normalY;
|
||||
|
||||
this.normalizedX = yo.normalizedX;
|
||||
this.normalizedY = yo.normalizedY;
|
||||
|
||||
this.visible = true;
|
||||
},
|
||||
hideContextMenu() {
|
||||
this.visible = false;
|
||||
},
|
||||
hasManyChildren() {
|
||||
let result = false;
|
||||
|
||||
this.options.forEach((option: Option) => {
|
||||
if (option.children && option.children.length > 7) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user