Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8a65888e8 |
@@ -103,7 +103,6 @@ const emit = defineEmits<{
|
|||||||
delete: [path: string]
|
delete: [path: string]
|
||||||
share: [path: string, name: string, isDir: boolean]
|
share: [path: string, name: string, isDir: boolean]
|
||||||
preview: [path: string]
|
preview: [path: string]
|
||||||
play: [path: string, name: string]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const cloudStore = useCloudStore()
|
const cloudStore = useCloudStore()
|
||||||
@@ -123,10 +122,8 @@ const downloadHref = computed(() => cloudStore.downloadUrl(props.item.path))
|
|||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (props.item.isDir) {
|
if (props.item.isDir) {
|
||||||
emit('navigate', props.item.path)
|
emit('navigate', props.item.path)
|
||||||
} else if (isAudio.value) {
|
} else if (isImage.value || isVideo.value || isAudio.value) {
|
||||||
// Music goes to the global bottom-bar player, not the lightbox.
|
// MediaLightbox handles all three media kinds.
|
||||||
emit('play', props.item.path, props.item.name)
|
|
||||||
} else if (isImage.value || isVideo.value) {
|
|
||||||
emit('preview', props.item.path)
|
emit('preview', props.item.path)
|
||||||
} else {
|
} else {
|
||||||
// Non-media files open by downloading — a click should always act on the file.
|
// Non-media files open by downloading — a click should always act on the file.
|
||||||
|
|||||||
@@ -53,7 +53,6 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
@navigate="$emit('navigate', $event)"
|
@navigate="$emit('navigate', $event)"
|
||||||
@delete="$emit('delete', $event)"
|
@delete="$emit('delete', $event)"
|
||||||
@play="(path, name) => $emit('play', path, name)"
|
|
||||||
@share="(path, name, isDir) => $emit('share', path, name, isDir)"
|
@share="(path, name, isDir) => $emit('share', path, name, isDir)"
|
||||||
@preview="$emit('preview', $event)"
|
@preview="$emit('preview', $event)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -93,7 +93,6 @@
|
|||||||
:item="r.item"
|
:item="r.item"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@share="handleShare"
|
@share="handleShare"
|
||||||
@play="handlePlay"
|
|
||||||
@preview="(p: string) => handlePreview(p, searchMineItems)"
|
@preview="(p: string) => handlePreview(p, searchMineItems)"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -142,7 +141,6 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@share="handleShare"
|
@share="handleShare"
|
||||||
@play="handlePlay"
|
|
||||||
@preview="(p: string) => handlePreview(p, filteredMyFiles)"
|
@preview="(p: string) => handlePreview(p, filteredMyFiles)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -361,7 +359,6 @@ import { useCloudStore } from '../stores/cloud'
|
|||||||
import { fileBrowserClient, type FileBrowserItem } from '@/api/filebrowser-client'
|
import { fileBrowserClient, type FileBrowserItem } from '@/api/filebrowser-client'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
import { getFileCategory } from '../composables/useFileType'
|
import { getFileCategory } from '../composables/useFileType'
|
||||||
import { useAudioPlayer } from '../composables/useAudioPlayer'
|
|
||||||
import FileCard from '../components/cloud/FileCard.vue'
|
import FileCard from '../components/cloud/FileCard.vue'
|
||||||
import ShareModal from '../components/cloud/ShareModal.vue'
|
import ShareModal from '../components/cloud/ShareModal.vue'
|
||||||
import MediaLightbox from '../components/cloud/MediaLightbox.vue'
|
import MediaLightbox from '../components/cloud/MediaLightbox.vue'
|
||||||
@@ -369,7 +366,6 @@ import MediaLightbox from '../components/cloud/MediaLightbox.vue'
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
const cloudStore = useCloudStore()
|
const cloudStore = useCloudStore()
|
||||||
const audioPlayer = useAudioPlayer()
|
|
||||||
const sectionCounts = ref<Record<string, number>>({})
|
const sectionCounts = ref<Record<string, number>>({})
|
||||||
const countsLoading = ref(false)
|
const countsLoading = ref(false)
|
||||||
|
|
||||||
@@ -569,12 +565,6 @@ function handleShare(path: string, name: string, isDir: boolean) {
|
|||||||
shareTarget.value = { path, name, isDir }
|
shareTarget.value = { path, name, isDir }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Music opens in the global bottom-bar player (GlobalAudioPlayer in App.vue). */
|
|
||||||
async function handlePlay(path: string, name: string) {
|
|
||||||
const url = await cloudStore.streamUrl(path)
|
|
||||||
audioPlayer.play(url, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePreview(path: string, context: FileBrowserItem[]) {
|
function handlePreview(path: string, context: FileBrowserItem[]) {
|
||||||
// MediaLightbox filters to media internally; index within that filtered list.
|
// MediaLightbox filters to media internally; index within that filtered list.
|
||||||
const mediaItems = context.filter(item => {
|
const mediaItems = context.filter(item => {
|
||||||
|
|||||||
@@ -426,8 +426,6 @@ async function handleSetup() {
|
|||||||
playLoginSuccessWhoosh()
|
playLoginSuccessWhoosh()
|
||||||
loginTransition.setJustCompletedOnboarding(true)
|
loginTransition.setJustCompletedOnboarding(true)
|
||||||
loginTransition.setJustLoggedIn(true)
|
loginTransition.setJustLoggedIn(true)
|
||||||
// First password setup counts as the first login (video → static rotation).
|
|
||||||
try { localStorage.setItem('neode_first_login_done', '1') } catch { /* ignore */ }
|
|
||||||
await new Promise(r => setTimeout(r, 520))
|
await new Promise(r => setTimeout(r, 520))
|
||||||
await router.replace(loginRedirectTo.value).catch(() => {
|
await router.replace(loginRedirectTo.value).catch(() => {
|
||||||
window.location.href = loginRedirectTo.value
|
window.location.href = loginRedirectTo.value
|
||||||
@@ -540,17 +538,13 @@ async function handleTotpVerify() {
|
|||||||
/** A login right after the onboarding wizard (flag set by OnboardingDone)
|
/** A login right after the onboarding wizard (flag set by OnboardingDone)
|
||||||
* gets the FULL dashboard entrance — zoom + oomph — even though it's a
|
* gets the FULL dashboard entrance — zoom + oomph — even though it's a
|
||||||
* regular password login (e.g. the demo). Subsequent logins stay
|
* regular password login (e.g. the demo). Subsequent logins stay
|
||||||
* deliberately low-key (justLoggedIn only).
|
* deliberately low-key (justLoggedIn only). */
|
||||||
* Also stamps neode_first_login_done: the login page keeps the intro VIDEO
|
|
||||||
* background until someone has logged in once (OnboardingWrapper reads it
|
|
||||||
* to pick video vs the rotating static backgrounds). */
|
|
||||||
function consumeOnboardingFinale() {
|
function consumeOnboardingFinale() {
|
||||||
try {
|
try {
|
||||||
if (sessionStorage.getItem('archy_onboarding_finale') === '1') {
|
if (sessionStorage.getItem('archy_onboarding_finale') === '1') {
|
||||||
sessionStorage.removeItem('archy_onboarding_finale')
|
sessionStorage.removeItem('archy_onboarding_finale')
|
||||||
loginTransition.setJustCompletedOnboarding(true)
|
loginTransition.setJustCompletedOnboarding(true)
|
||||||
}
|
}
|
||||||
localStorage.setItem('neode_first_login_done', '1')
|
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,22 +86,22 @@ const videoBackgroundRoutes = ['/onboarding/intro', '/login']
|
|||||||
// Login uses video when coming from splash, or static + glitch when direct
|
// Login uses video when coming from splash, or static + glitch when direct
|
||||||
const isLoginRoute = computed(() => route.path === '/login')
|
const isLoginRoute = computed(() => route.path === '/login')
|
||||||
|
|
||||||
|
// True once onboarding is complete. Used to skip the intro video on
|
||||||
// Check if current route should use video background.
|
// the /login route so that returning (logged-out) users go straight
|
||||||
// The FIRST login (nobody has logged in / set a password yet) keeps the VIDEO
|
// to the screensaver-style static + glitch background instead of
|
||||||
// running so the splash → login handoff is one continuous shot; only after a
|
// replaying the full intro every time.
|
||||||
// successful login does /login switch to the rotating static backgrounds.
|
const onboardingDone = computed(() => {
|
||||||
// Login.vue sets neode_first_login_done on every successful login.
|
|
||||||
const hasLoggedInBefore = () => {
|
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem('neode_first_login_done') === '1'
|
return localStorage.getItem('neode_onboarding_complete') === '1'
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
// Check if current route should use video background
|
||||||
const useVideoBackground = computed(() => {
|
const useVideoBackground = computed(() => {
|
||||||
if (!videoBackgroundRoutes.includes(route.path)) return false
|
if (!videoBackgroundRoutes.includes(route.path)) return false
|
||||||
if (route.path === '/login' && hasLoggedInBefore()) return false
|
if (route.path === '/login' && onboardingDone.value) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,12 +122,9 @@ const routeBackgrounds: Record<string, string> = {
|
|||||||
'/login': 'bg-intro.jpg' // Video loops from splash (same as intro)
|
'/login': 'bg-intro.jpg' // Video loops from splash (same as intro)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Until the user has logged in (or set their password) once, the login
|
// Rotate the login background so the lock screen doesn't look
|
||||||
// background stays PINNED to the same still the intro video ends on — the
|
// identical on every logout. Cycles through bg-intro-1..6 using a
|
||||||
// video → login handoff only reads as one continuous shot when both show the
|
// counter persisted to localStorage so subsequent visits advance.
|
||||||
// identical frame. From the second login onward the lock screen rotates
|
|
||||||
// through bg-intro-1..6 for variety (counter persisted in localStorage).
|
|
||||||
// Login.vue sets neode_first_login_done on every successful login.
|
|
||||||
const LOGIN_BACKGROUNDS = [
|
const LOGIN_BACKGROUNDS = [
|
||||||
'bg-intro-1.webp',
|
'bg-intro-1.webp',
|
||||||
'bg-intro-2.jpg',
|
'bg-intro-2.jpg',
|
||||||
@@ -138,14 +135,13 @@ const LOGIN_BACKGROUNDS = [
|
|||||||
]
|
]
|
||||||
function pickNextLoginBackground(): string {
|
function pickNextLoginBackground(): string {
|
||||||
try {
|
try {
|
||||||
if (localStorage.getItem('neode_first_login_done') !== '1') return 'bg-intro.jpg'
|
|
||||||
const raw = localStorage.getItem('neode_login_bg_idx')
|
const raw = localStorage.getItem('neode_login_bg_idx')
|
||||||
const prev = raw !== null ? parseInt(raw, 10) : -1
|
const prev = raw !== null ? parseInt(raw, 10) : -1
|
||||||
const next = (Number.isFinite(prev) ? prev + 1 : 0) % LOGIN_BACKGROUNDS.length
|
const next = (Number.isFinite(prev) ? prev + 1 : 0) % LOGIN_BACKGROUNDS.length
|
||||||
localStorage.setItem('neode_login_bg_idx', String(next))
|
localStorage.setItem('neode_login_bg_idx', String(next))
|
||||||
return LOGIN_BACKGROUNDS[next]!
|
return LOGIN_BACKGROUNDS[next]!
|
||||||
} catch {
|
} catch {
|
||||||
return 'bg-intro.jpg'
|
return LOGIN_BACKGROUNDS[Math.floor(Math.random() * LOGIN_BACKGROUNDS.length)]!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loginBackground = ref(pickNextLoginBackground())
|
const loginBackground = ref(pickNextLoginBackground())
|
||||||
|
|||||||
Reference in New Issue
Block a user