Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 3 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@
"rollup-plugin-visualizer": "^6.0.3",
"tailwindcss": "^4.3.0",
"typescript": "^5.8.3",
"vite": "npm:@voidzero-dev/vite-plus-core@^0.1.11",
"vite-plus": "^0.1.11",
"vite": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1703",
"vite-plus": "https://pkg.pr.new/voidzero-dev/vite-plus@1703",
"vitepress": "^1.6.3",
"vitepress-plugin-llms": "^1.11.0",
"vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.11",
"vitest": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-test@1703",
"vue-tsc": "^3.2.5"
},
"engines": {
Expand All @@ -99,13 +99,5 @@
"prepare": "vp config"
},
"type": "module",
"pnpm": {
"overrides": {
"form-data@<2.5.4": ">=2.5.4",
"@rolldown/pluginutils": "1.0.0-rc.9",
"vite": "npm:@voidzero-dev/vite-plus-core@^0.1.11",
"vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.11"
}
},
"packageManager": "pnpm@10.32.1"
}
916 changes: 625 additions & 291 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
blockExoticSubdeps: false

minimumReleaseAge: 60
minimumReleaseAgeExclude:
- vite-plus
- "@voidzero-dev/*"
- "@oxc-project/*"
- "@oxlint/*"
- oxlint
- oxfmt
- oxlint-tsgolint

overrides:
form-data@<2.5.4: ">=2.5.4"
"@rolldown/pluginutils": 1.0.0-rc.9
"@voidzero-dev/vite-plus-core": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1703"
"@voidzero-dev/vite-plus-test": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-test@1703"
"@voidzero-dev/vite-plus-prompts": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-prompts@1703"
18 changes: 9 additions & 9 deletions resources/assets/css/app.pcss
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
@layer theme, base, components, utilities;

@import 'tailwindcss';
@import './partials/vars.pcss';
@import './partials/hack.pcss' layer(base);
@import './vendor/nprogress.pcss' layer(base);
@import './partials/skeleton.pcss' layer(base);
@import './partials/tooltip.pcss' layer(base);
@import './partials/context-menu.pcss' layer(base);
@import './partials/shared.pcss' layer(base);
@import './partials/scroll-mask.pcss';
@import "tailwindcss";
@import "./partials/vars.pcss";
@import "./partials/hack.pcss" layer(base);
@import "./vendor/nprogress.pcss" layer(base);
@import "./partials/skeleton.pcss" layer(base);
@import "./partials/tooltip.pcss" layer(base);
@import "./partials/context-menu.pcss" layer(base);
@import "./partials/shared.pcss" layer(base);
@import "./partials/scroll-mask.pcss";

@config "../../../tailwind.config.js";

Expand Down
20 changes: 13 additions & 7 deletions resources/assets/css/partials/scroll-mask.pcss
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
@property --scroll-mask-t-from {
syntax: '<length-percentage>';
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}

@property --scroll-mask-b-from {
syntax: '<length-percentage>';
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}

@property --scroll-mask-l-from {
syntax: '<length-percentage>';
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}

@property --scroll-mask-r-from {
syntax: '<length-percentage>';
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}
Expand Down Expand Up @@ -57,12 +57,18 @@
}

@supports (animation-timeline: scroll()) {
:where([class^='scroll-mask-'], [class*=' scroll-mask-']) {
:where([class^="scroll-mask-"], [class*=" scroll-mask-"]) {
--scroll-mask-t: linear-gradient(to top, black, black var(--scroll-mask-t-from), transparent);
--scroll-mask-b: linear-gradient(to bottom, black, black var(--scroll-mask-b-from), transparent);
--scroll-mask-b: linear-gradient(
to bottom,
black,
black var(--scroll-mask-b-from),
transparent
);
--scroll-mask-l: linear-gradient(to left, black, black var(--scroll-mask-l-from), transparent);
--scroll-mask-r: linear-gradient(to right, black, black var(--scroll-mask-r-from), transparent);
mask-image: var(--scroll-mask-t), var(--scroll-mask-b), var(--scroll-mask-l), var(--scroll-mask-r);
mask-image:
var(--scroll-mask-t), var(--scroll-mask-b), var(--scroll-mask-l), var(--scroll-mask-r);
mask-composite: intersect;
-webkit-mask-composite: source-in;
animation:
Expand Down
2 changes: 1 addition & 1 deletion resources/assets/css/partials/shared.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ textarea,
}

button,
[role='button'] {
[role="button"] {
@apply touch-manipulation select-none cursor-pointer focus-visible:outline-1 focus-visible:outline-k-highlight;
}

Expand Down
4 changes: 2 additions & 2 deletions resources/assets/css/partials/vars.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
--bg-size: cover;

--font-family:
system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue',
sans-serif;
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu,
Cantarell, "Helvetica Neue", sans-serif;
--font-size: 13px;

--header-height: auto;
Expand Down
6 changes: 3 additions & 3 deletions resources/assets/css/remote.pcss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@layer theme, base, components, utilities;

@import 'tailwindcss';
@import './partials/vars.pcss';
@import './partials/shared.pcss' layer(base);
@import "tailwindcss";
@import "./partials/vars.pcss";
@import "./partials/shared.pcss" layer(base);

@config "../../../tailwind.config.js";

Expand Down
159 changes: 84 additions & 75 deletions resources/assets/js/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,154 +33,163 @@
</template>

<script lang="ts" setup>
import { defineAsyncComponent } from '@/utils/helpers'
import { computed, onMounted, provide, ref, shallowRef, watch } from 'vue'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { queueStore } from '@/stores/queueStore'
import { authService } from '@/services/authService'
import { radioStationStore } from '@/stores/radioStationStore'
import { defineAsyncComponent } from "@/utils/helpers";
import { computed, onMounted, provide, ref, shallowRef, watch } from "vue";
import { useNetworkStatus } from "@/composables/useNetworkStatus";
import { queueStore } from "@/stores/queueStore";
import { authService } from "@/services/authService";
import { radioStationStore } from "@/stores/radioStationStore";
import {
ContextMenuKey,
CurrentStreamableKey,
DialogBoxKey,
MessageToasterKey,
ModalKey,
OverlayKey,
} from '@/config/symbols'
import { useRouter } from '@/composables/useRouter'
import { commonStore } from '@/stores/commonStore'
import type { Route } from '@/router'
} from "@/config/symbols";
import { useRouter } from "@/composables/useRouter";
import { commonStore } from "@/stores/commonStore";
import type { Route } from "@/router";

import DialogBox from '@/components/ui/DialogBox.vue'
import MessageToaster from '@/components/ui/message-toaster/MessageToaster.vue'
import Overlay from '@/components/ui/Overlay.vue'
import OfflineNotification from '@/components/ui/OfflineNotification.vue'
import DialogBox from "@/components/ui/DialogBox.vue";
import MessageToaster from "@/components/ui/message-toaster/MessageToaster.vue";
import Overlay from "@/components/ui/Overlay.vue";
import OfflineNotification from "@/components/ui/OfflineNotification.vue";

// Do not dynamic-import app footer, as it contains the <audio> element
// that is necessary to properly initialize the playService and equalizer.
import AppFooter from '@/components/layout/app-footer/index.vue'
import AppFooter from "@/components/layout/app-footer/index.vue";

// GlobalEventListener must NOT be lazy-loaded, so that it can handle LOG_OUT event properly.
import GlobalEventListeners from '@/components/utils/GlobalEventListeners.vue'
import AppInitializer from '@/components/utils/AppInitializer.vue'
import ContextMenu from '@/components/ui/context-menu/ContextMenu.vue'

const HotkeyListener = defineAsyncComponent(() => import('@/components/utils/HotkeyListener.vue'))
const LoginForm = defineAsyncComponent(() => import('@/components/auth/LoginForm.vue'))
const MainWrapper = defineAsyncComponent(() => import('@/components/layout/main-wrapper/index.vue'))
const SupportKoel = defineAsyncComponent(() => import('@/components/meta/SupportKoel.vue'))
const AiAssistantScreen = defineAsyncComponent(() => import('@/components/ai/AiAssistantScreen.vue'))
const DropZone = defineAsyncComponent(() => import('@/components/ui/upload/DropZone.vue'))
const AcceptInvitation = defineAsyncComponent(() => import('@/components/invitation/AcceptInvitation.vue'))
const ResetPasswordForm = defineAsyncComponent(() => import('@/components/auth/ResetPasswordForm.vue'))
const Embed = defineAsyncComponent(() => import('@/components/embed/widget/EmbedWidget.vue'))

const overlay = ref<InstanceType<typeof Overlay>>()
const dialog = ref<InstanceType<typeof DialogBox>>()
const toaster = ref<InstanceType<typeof MessageToaster>>()
const currentStreamable = ref<Streamable>()
const showDropZone = ref(false)

const { isCurrentScreen, resolveRoute, triggerNotFound, onRouteChanged } = useRouter()
const { online } = useNetworkStatus()

const authenticated = ref(false)
const initialized = ref(false)
const currentRoute = ref<Route | null>(null)

const triggerAppInitialization = () => (authenticated.value = true)
const onInitError = () => (authenticated.value = false)
import GlobalEventListeners from "@/components/utils/GlobalEventListeners.vue";
import AppInitializer from "@/components/utils/AppInitializer.vue";
import ContextMenu from "@/components/ui/context-menu/ContextMenu.vue";

const HotkeyListener = defineAsyncComponent(() => import("@/components/utils/HotkeyListener.vue"));
const LoginForm = defineAsyncComponent(() => import("@/components/auth/LoginForm.vue"));
const MainWrapper = defineAsyncComponent(
() => import("@/components/layout/main-wrapper/index.vue"),
);
const SupportKoel = defineAsyncComponent(() => import("@/components/meta/SupportKoel.vue"));
const AiAssistantScreen = defineAsyncComponent(
() => import("@/components/ai/AiAssistantScreen.vue"),
);
const DropZone = defineAsyncComponent(() => import("@/components/ui/upload/DropZone.vue"));
const AcceptInvitation = defineAsyncComponent(
() => import("@/components/invitation/AcceptInvitation.vue"),
);
const ResetPasswordForm = defineAsyncComponent(
() => import("@/components/auth/ResetPasswordForm.vue"),
);
const Embed = defineAsyncComponent(() => import("@/components/embed/widget/EmbedWidget.vue"));

const overlay = ref<InstanceType<typeof Overlay>>();
const dialog = ref<InstanceType<typeof DialogBox>>();
const toaster = ref<InstanceType<typeof MessageToaster>>();
const currentStreamable = ref<Streamable>();
const showDropZone = ref(false);

const { isCurrentScreen, resolveRoute, triggerNotFound, onRouteChanged } = useRouter();
const { online } = useNetworkStatus();

const authenticated = ref(false);
const initialized = ref(false);
const currentRoute = ref<Route | null>(null);

const triggerAppInitialization = () => (authenticated.value = true);
const onInitError = () => (authenticated.value = false);

const onInitSuccess = async () => {
initialized.value = true
initialized.value = true;

if (currentRoute.value && currentRoute.value.meta?.guard?.() === false) {
triggerNotFound()
triggerNotFound();
}
}
};

const layout = computed(() => {
if (currentRoute.value?.meta?.layout) {
return currentRoute.value.meta.layout
return currentRoute.value.meta.layout;
}

return authenticated.value ? 'default' : 'login'
})
return authenticated.value ? "default" : "login";
});

onMounted(() => {
// Add an ugly mac/non-mac class for OS-targeting styles.
document.documentElement.classList.add(navigator.userAgent.includes('Mac') ? 'mac' : 'non-mac')
document.documentElement.classList.add(navigator.userAgent.includes("Mac") ? "mac" : "non-mac");

currentRoute.value = resolveRoute()
currentRoute.value = resolveRoute();

if (currentRoute.value?.meta?.public) {
// If the route is public (embed, login, reset password etc.) we don't need to check for authentication.
return
return;
}

// If the user is authenticated via a proxy, we have the token in the window object.
// Simply forward it to the authService and continue with the normal flow.
if (window.KOEL.auth_token) {
authService.setTokensUsingCompositeToken(window.KOEL.auth_token)
authService.setTokensUsingCompositeToken(window.KOEL.auth_token);
}

// The app has just been initialized, check if we can get the user data with an already existing token
if (authService.hasApiToken()) {
triggerAppInitialization()
triggerAppInitialization();
}
})
});

const onDragOver = (e: DragEvent) => {
showDropZone.value = Boolean(e.dataTransfer?.types.includes('Files')) && !isCurrentScreen('Upload')
}
showDropZone.value =
Boolean(e.dataTransfer?.types.includes("Files")) && !isCurrentScreen("Upload");
};

watch(
() => queueStore.current,
song => (currentStreamable.value = song),
)
(song) => (currentStreamable.value = song),
);

watch(
() => radioStationStore.current,
station => {
(station) => {
if (station) {
currentStreamable.value = station
currentStreamable.value = station;
}
},
)
);

onRouteChanged(route => (currentRoute.value = route))
onRouteChanged((route) => (currentRoute.value = route));

const onDragEnd = () => (showDropZone.value = false)
const onDragEnd = () => (showDropZone.value = false);

const onDragLeave = (e: MouseEvent) => {
if ((e.currentTarget as Node)?.contains?.(e.relatedTarget as Node)) {
return
return;
}

showDropZone.value = false
}
showDropZone.value = false;
};

const onDrop = () => (showDropZone.value = false)
const onDrop = () => (showDropZone.value = false);

provide(OverlayKey, overlay)
provide(DialogBoxKey, dialog)
provide(MessageToasterKey, toaster)
provide(CurrentStreamableKey, currentStreamable)
provide(OverlayKey, overlay);
provide(DialogBoxKey, dialog);
provide(MessageToasterKey, toaster);
provide(CurrentStreamableKey, currentStreamable);

provide(
ContextMenuKey,
shallowRef({
component: null,
position: { top: 0, left: 0 },
}),
)
);

provide(
ModalKey,
shallowRef({
component: null,
}),
)
);
</script>

<style lang="postcss">
Expand Down
Loading
Loading