chore: added changeset
This commit is contained in:
parent
8afa96590e
commit
cdf77523a5
5
.changeset/rude-insects-smell.md
Normal file
5
.changeset/rude-insects-smell.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'pastebar-app-ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added protected collection with PIN access
|
@ -218,7 +218,9 @@ function App() {
|
|||||||
isSingleClickToCopyPaste: settings.isSingleClickToCopyPaste?.valueBool ?? false,
|
isSingleClickToCopyPaste: settings.isSingleClickToCopyPaste?.valueBool ?? false,
|
||||||
hasPinProtectedCollections:
|
hasPinProtectedCollections:
|
||||||
settings.hasPinProtectedCollections?.valueBool ?? false,
|
settings.hasPinProtectedCollections?.valueBool ?? false,
|
||||||
protectedCollections: settings.protectedCollections?.valueText.split(','),
|
protectedCollections: settings.protectedCollections?.valueText
|
||||||
|
? settings.protectedCollections.valueText.split(',').filter(Boolean)
|
||||||
|
: [],
|
||||||
isSingleClickToCopyPasteQuickWindow:
|
isSingleClickToCopyPasteQuickWindow:
|
||||||
settings.isSingleClickToCopyPasteQuickWindow?.valueBool ?? false,
|
settings.isSingleClickToCopyPasteQuickWindow?.valueBool ?? false,
|
||||||
isKeepPinnedOnClearEnabled:
|
isKeepPinnedOnClearEnabled:
|
||||||
|
@ -42,7 +42,7 @@ type Props = {
|
|||||||
|
|
||||||
export default function ModalLockScreenConfirmationWithPasscodeOrPassword({
|
export default function ModalLockScreenConfirmationWithPasscodeOrPassword({
|
||||||
open,
|
open,
|
||||||
title = 'Unlock Application Screen',
|
title = 'Confirm Passcode',
|
||||||
isLockScreen = false,
|
isLockScreen = false,
|
||||||
showPasscode = true,
|
showPasscode = true,
|
||||||
onConfirmSuccess,
|
onConfirmSuccess,
|
||||||
@ -171,6 +171,16 @@ export default function ModalLockScreenConfirmationWithPasscodeOrPassword({
|
|||||||
setFocusField(confirmPasscodeCurrentFocus.value)
|
setFocusField(confirmPasscodeCurrentFocus.value)
|
||||||
}, [confirmPasscodeCurrentFocus.value])
|
}, [confirmPasscodeCurrentFocus.value])
|
||||||
|
|
||||||
|
// Set initial focus when modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && showPasscode && screenLockPassCode) {
|
||||||
|
setTimeout(() => {
|
||||||
|
confirmPasscodeCurrentFocus.value = 0
|
||||||
|
setFocusField(0)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}, [open, showPasscode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import { collectionsStoreAtom, settingsStoreAtom } from '~/store'
|
import { collectionsStoreAtom } from '~/store'
|
||||||
import {
|
import { useAtomValue } from 'jotai'
|
||||||
isCollectionPinModalOpenAtom,
|
|
||||||
collectionPinModalPropsAtom,
|
|
||||||
} from '~/store/uiStore'
|
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
|
||||||
|
|
||||||
import { Collection, Item, Tabs } from '~/types/menu'
|
import { Collection, Item, Tabs } from '~/types/menu'
|
||||||
|
|
||||||
@ -253,25 +249,11 @@ export function useUpdateCollectionById() {
|
|||||||
|
|
||||||
export function useSelectCollectionById() {
|
export function useSelectCollectionById() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { protectedCollections, screenLockPassCode } =
|
|
||||||
useAtomValue(settingsStoreAtom)
|
|
||||||
const { collections, updateCurrentCollectionId } = useAtomValue(collectionsStoreAtom)
|
|
||||||
|
|
||||||
const setIsCollectionPinModalOpen = useSetAtom(isCollectionPinModalOpenAtom)
|
const { mutate: selectCollectionById, isSuccess: selectCollectionByIdSuccess } =
|
||||||
const setCollectionPinModalProps = useSetAtom(collectionPinModalPropsAtom)
|
|
||||||
|
|
||||||
const { mutate: invokeSelectCollectionById, isSuccess: selectCollectionByIdSuccess } =
|
|
||||||
useInvokeMutation<Record<string, unknown>, string>('select_collection_by_id', {
|
useInvokeMutation<Record<string, unknown>, string>('select_collection_by_id', {
|
||||||
onSuccess: async (data, variables) => {
|
onSuccess: async data => {
|
||||||
if (data === 'ok') {
|
if (data === 'ok') {
|
||||||
// The actual update to currentCollectionId in collectionsStoreAtom
|
|
||||||
// might be implicitly handled by backend or needs explicit call here
|
|
||||||
// For now, assume backend handles state post 'select_collection_by_id'
|
|
||||||
// and query invalidations refresh the frontend state.
|
|
||||||
// If direct update is needed:
|
|
||||||
// const { selectCollection } = variables as { selectCollection: { collectionId: string } };
|
|
||||||
// updateCurrentCollectionId(selectCollection.collectionId);
|
|
||||||
|
|
||||||
await invoke('build_system_menu')
|
await invoke('build_system_menu')
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ['get_collections'],
|
queryKey: ['get_collections'],
|
||||||
@ -283,58 +265,14 @@ export function useSelectCollectionById() {
|
|||||||
queryKey: ['get_active_collection_with_menu_items'],
|
queryKey: ['get_active_collection_with_menu_items'],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log('select collection error', data)
|
console.log('update collection error', data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectCollectionById = (params: {
|
|
||||||
selectCollection: { collectionId: string }
|
|
||||||
}) => {
|
|
||||||
const { collectionId: targetCollectionId } = params.selectCollection // Renamed for clarity in logs
|
|
||||||
const isProtected = protectedCollections.includes(targetCollectionId)
|
|
||||||
const targetCollection = collections.find(c => c.collectionId === targetCollectionId)
|
|
||||||
|
|
||||||
if (isProtected && screenLockPassCode && targetCollection) {
|
|
||||||
console.log(`[Debug] Setting up PIN prompt for collection: ${targetCollectionId}, Title: ${targetCollection.title}`);
|
|
||||||
|
|
||||||
const onConfirmSuccessCallback = async () => { // Make callback async
|
|
||||||
console.log(`[Debug] PIN Confirmed! Attempting to switch to collection via invoke: ${targetCollectionId}`);
|
|
||||||
try {
|
|
||||||
// Instead of directly updating UI state, invoke the backend to select the collection.
|
|
||||||
// The backend will then trigger state updates through query invalidations via the mutation's onSuccess.
|
|
||||||
await invoke('select_collection_by_id', { collectionId: targetCollectionId });
|
|
||||||
console.log(`[Debug] Invoke 'select_collection_by_id' for ${targetCollectionId} successful.`);
|
|
||||||
|
|
||||||
// Explicitly call query invalidations and menu build,
|
|
||||||
// as this path is outside the direct TanStack Mutation onSuccess flow.
|
|
||||||
console.log('[Debug] Manually triggering query invalidations and menu build post-PIN success.');
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['get_collections'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['get_active_collection_with_clips'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['get_active_collection_with_menu_items'] });
|
|
||||||
await invoke('build_system_menu');
|
|
||||||
console.log('[Debug] Explicit query invalidations and menu build complete after PIN.');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[Debug] Error invoking 'select_collection_by_id' for ${targetCollectionId}:`, error);
|
|
||||||
// Handle error appropriately, e.g., show a toast message to the user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setCollectionPinModalProps({
|
|
||||||
title: `Unlock Collection: ${targetCollection.title}`,
|
|
||||||
onConfirmSuccess: onConfirmSuccessCallback,
|
|
||||||
})
|
|
||||||
setIsCollectionPinModalOpen(true)
|
|
||||||
} else {
|
|
||||||
// Not protected or no PIN set, proceed as normal
|
|
||||||
invokeSelectCollectionById(params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectCollectionByIdSuccess,
|
selectCollectionByIdSuccess,
|
||||||
selectCollectionById, // This is now our wrapped function
|
selectCollectionById,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
openOnBoardingTourName,
|
openOnBoardingTourName,
|
||||||
openOSXSystemPermissionsModal,
|
openOSXSystemPermissionsModal,
|
||||||
openProtectedContentModal,
|
openProtectedContentModal,
|
||||||
|
pendingProtectedCollectionId,
|
||||||
playerStoreAtom,
|
playerStoreAtom,
|
||||||
resetPassCodeNextDelayInSeconds,
|
resetPassCodeNextDelayInSeconds,
|
||||||
resetPassCodeNumberOfTried,
|
resetPassCodeNumberOfTried,
|
||||||
@ -57,6 +58,7 @@ import ModalLockScreenConfirmationWithPasscodeOrPassword from '~/components/orga
|
|||||||
import ModalOSXSystemPermissions from '~/components/organisms/modals/system-permissions-osx-modal'
|
import ModalOSXSystemPermissions from '~/components/organisms/modals/system-permissions-osx-modal'
|
||||||
import { Box, Button, Flex, Text } from '~/components/ui'
|
import { Box, Button, Flex, Text } from '~/components/ui'
|
||||||
|
|
||||||
|
import { useSelectCollectionById } from '~/hooks/queries/use-collections'
|
||||||
import { useClipboardPaste, useCopyPaste } from '~/hooks/use-copypaste'
|
import { useClipboardPaste, useCopyPaste } from '~/hooks/use-copypaste'
|
||||||
import { useLocalStorage } from '~/hooks/use-localstorage'
|
import { useLocalStorage } from '~/hooks/use-localstorage'
|
||||||
import { useSignal } from '~/hooks/use-signal'
|
import { useSignal } from '~/hooks/use-signal'
|
||||||
@ -90,6 +92,7 @@ const Container: React.ForwardRefRenderFunction<HTMLDivElement, MainContainerPro
|
|||||||
const { historyListSimpleBar, clipboardHistory } = useAtomValue(
|
const { historyListSimpleBar, clipboardHistory } = useAtomValue(
|
||||||
clipboardHistoryStoreAtom
|
clipboardHistoryStoreAtom
|
||||||
)
|
)
|
||||||
|
const { selectCollectionById } = useSelectCollectionById()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
appToursCompletedList,
|
appToursCompletedList,
|
||||||
@ -619,17 +622,23 @@ const Container: React.ForwardRefRenderFunction<HTMLDivElement, MainContainerPro
|
|||||||
{openProtectedContentModal.value && (
|
{openProtectedContentModal.value && (
|
||||||
<ModalLockScreenConfirmationWithPasscodeOrPassword
|
<ModalLockScreenConfirmationWithPasscodeOrPassword
|
||||||
open={openProtectedContentModal.value}
|
open={openProtectedContentModal.value}
|
||||||
title={'Test Protected Content'}
|
title={t('Enter PIN to Access Protected Collection', { ns: 'collections' })}
|
||||||
isLockScreen={false} // Important: This makes it cancellable and not the full lock screen
|
isLockScreen={false}
|
||||||
showPasscode={true} // We need PIN (passcode) input
|
showPasscode={true}
|
||||||
onConfirmSuccess={() => {
|
onConfirmSuccess={() => {
|
||||||
openProtectedContentModal.value = false
|
openProtectedContentModal.value = false
|
||||||
// modalProps.onConfirmSuccess()
|
if (pendingProtectedCollectionId.value) {
|
||||||
// setIsOpen(false)
|
selectCollectionById({
|
||||||
// setModalProps(null) // Clear props after success
|
selectCollection: {
|
||||||
|
collectionId: pendingProtectedCollectionId.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pendingProtectedCollectionId.value = null
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
openProtectedContentModal.value = false
|
openProtectedContentModal.value = false
|
||||||
|
pendingProtectedCollectionId.value = null
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -17,6 +17,8 @@ import {
|
|||||||
openAboutPasteBarModal,
|
openAboutPasteBarModal,
|
||||||
openContactUsFormModal,
|
openContactUsFormModal,
|
||||||
openOnBoardingTourName,
|
openOnBoardingTourName,
|
||||||
|
openProtectedContentModal,
|
||||||
|
pendingProtectedCollectionId,
|
||||||
playerStoreAtom,
|
playerStoreAtom,
|
||||||
settingsStoreAtom,
|
settingsStoreAtom,
|
||||||
showInvalidTrackWarningAddSong,
|
showInvalidTrackWarningAddSong,
|
||||||
@ -44,6 +46,7 @@ import {
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
FileCog,
|
FileCog,
|
||||||
LibrarySquare,
|
LibrarySquare,
|
||||||
|
LockKeyhole,
|
||||||
Maximize,
|
Maximize,
|
||||||
Minus,
|
Minus,
|
||||||
Pause,
|
Pause,
|
||||||
@ -214,6 +217,8 @@ export function NavBar() {
|
|||||||
setIsSimplifiedLayout,
|
setIsSimplifiedLayout,
|
||||||
isMainWindowOnTop,
|
isMainWindowOnTop,
|
||||||
setIsMainWindowOnTop,
|
setIsMainWindowOnTop,
|
||||||
|
hasPinProtectedCollections,
|
||||||
|
protectedCollections,
|
||||||
} = useAtomValue(settingsStoreAtom)
|
} = useAtomValue(settingsStoreAtom)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -1264,7 +1269,7 @@ export function NavBar() {
|
|||||||
height: 'auto',
|
height: 'auto',
|
||||||
maxHeight: '400px',
|
maxHeight: '400px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minWidth: '200px',
|
minWidth: '220px',
|
||||||
}}
|
}}
|
||||||
autoHide={false}
|
autoHide={false}
|
||||||
>
|
>
|
||||||
@ -1291,16 +1296,40 @@ export function NavBar() {
|
|||||||
value={collectionId}
|
value={collectionId}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
selectCollectionById({
|
const isProtectedCollection =
|
||||||
selectCollection: {
|
hasPinProtectedCollections &&
|
||||||
collectionId,
|
protectedCollections.includes(collectionId)
|
||||||
},
|
|
||||||
})
|
if (isProtectedCollection) {
|
||||||
|
pendingProtectedCollectionId.value = collectionId
|
||||||
|
openProtectedContentModal.value = true
|
||||||
|
} else {
|
||||||
|
selectCollectionById({
|
||||||
|
selectCollection: {
|
||||||
|
collectionId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className={isSelected ? 'font-semibold' : ''}>
|
<Flex
|
||||||
{title}
|
className={`${
|
||||||
</span>
|
isSelected ? 'font-semibold' : ''
|
||||||
|
} items-center justify-start gap-2`}
|
||||||
|
>
|
||||||
|
{hasPinProtectedCollections &&
|
||||||
|
protectedCollections.includes(collectionId) ? (
|
||||||
|
<>
|
||||||
|
<span className="truncate max-w-[150px]">{title}</span>
|
||||||
|
<LockKeyhole
|
||||||
|
size={12}
|
||||||
|
className="text-gray-600 dark:text-gray-500 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className="truncate max-w-[210px]">{title}</span>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</MenubarRadioItem>
|
</MenubarRadioItem>
|
||||||
))}
|
))}
|
||||||
</MenubarRadioGroup>
|
</MenubarRadioGroup>
|
||||||
|
@ -18,9 +18,18 @@ Manage Collections: Manage Collections
|
|||||||
Mark collections as protected: Mark collections as protected
|
Mark collections as protected: Mark collections as protected
|
||||||
Pin Protected Collections: Pin Protected Collections
|
Pin Protected Collections: Pin Protected Collections
|
||||||
Protect Collections with PIN: Protect Collections with PIN
|
Protect Collections with PIN: Protect Collections with PIN
|
||||||
|
Protected Collection: Protected Collection
|
||||||
Select Collections: Select Collections
|
Select Collections: Select Collections
|
||||||
Select protected collections: Select protected collections
|
Select protected collections: Select protected collections
|
||||||
Show collection name on the navbar: Show collection name on the navbar
|
Show collection name on the navbar: Show collection name on the navbar
|
||||||
Switch collections: Switch collections
|
Switch collections: Switch collections
|
||||||
You can add the selected text to your clips or menu. Please select the option below.: You can add the selected text to your clips or menu. Please select the option below.
|
You can add the selected text to your clips or menu. Please select the option below.: You can add the selected text to your clips or menu. Please select the option below.
|
||||||
You need to select a different collection before deleting the current one.: You need to select a different collection before deleting the current one.
|
You need to select a different collection before deleting the current one.: You need to select a different collection before deleting the current one.
|
||||||
|
Enable PIN Protection: Enable PIN Protection
|
||||||
|
Disable PIN Protection: Disable PIN Protection
|
||||||
|
Add To Protected Collections: Add To Protected Collections
|
||||||
|
Remove From Protected Collections: Remove From Protected Collections
|
||||||
|
Confirm Add To Protected Collections: Confirm Add To Protected Collections
|
||||||
|
Confirm Disable PIN Protection: Confirm Disable PIN Protection
|
||||||
|
Confirm Remove From Protected Collections: Confirm Remove From Protected Collections
|
||||||
|
Enter PIN to Access Protected Collection: Enter PIN to Access Protected Collection
|
@ -2,7 +2,8 @@
|
|||||||
' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.': ' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.'
|
' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.': ' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.'
|
||||||
About PasteBar: About PasteBar
|
About PasteBar: About PasteBar
|
||||||
Action Menu: Action Menu
|
Action Menu: Action Menu
|
||||||
Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template: Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
? Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||||
|
: Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||||
Add Clip: Add Clip
|
Add Clip: Add Clip
|
||||||
Add First Option: Add First Option
|
Add First Option: Add First Option
|
||||||
Add Link Card: Add Link Card
|
Add Link Card: Add Link Card
|
||||||
@ -49,11 +50,14 @@ Close History Window: Close History Window
|
|||||||
Close modal and continue in browser: Close modal and continue in browser
|
Close modal and continue in browser: Close modal and continue in browser
|
||||||
Confirm: Confirm
|
Confirm: Confirm
|
||||||
Confirm action: Confirm action
|
Confirm action: Confirm action
|
||||||
|
Confirm Add To Protected Collections: Confirm Add To Protected Collections
|
||||||
Confirm Delete: Confirm Delete
|
Confirm Delete: Confirm Delete
|
||||||
|
Confirm Disable PIN Protection: Confirm Disable PIN Protection
|
||||||
Confirm Email: Confirm Email
|
Confirm Email: Confirm Email
|
||||||
Confirm Passcode: Confirm Passcode
|
Confirm Passcode: Confirm Passcode
|
||||||
Confirm Password: Confirm Password
|
Confirm Password: Confirm Password
|
||||||
Confirm Remove: Confirm Remove
|
Confirm Remove: Confirm Remove
|
||||||
|
Confirm Remove From Protected Collections: Confirm Remove From Protected Collections
|
||||||
Confirm Your Passcode: Confirm Your Passcode
|
Confirm Your Passcode: Confirm Your Passcode
|
||||||
Confirm passcode reset: Confirm passcode reset
|
Confirm passcode reset: Confirm passcode reset
|
||||||
Confirm password reset: Confirm password reset
|
Confirm password reset: Confirm password reset
|
||||||
@ -96,6 +100,7 @@ Enabled: Enabled
|
|||||||
Enter Current Passcode: Enter Current Passcode
|
Enter Current Passcode: Enter Current Passcode
|
||||||
Enter Digits Only Passcode: Enter Digits Only Passcode
|
Enter Digits Only Passcode: Enter Digits Only Passcode
|
||||||
Enter Email: Enter Email
|
Enter Email: Enter Email
|
||||||
|
Enter PIN to Access Protected Collection: Enter PIN to Access Protected Collection
|
||||||
Enter Passcode: Enter Passcode
|
Enter Passcode: Enter Passcode
|
||||||
Enter Password: Enter Password
|
Enter Password: Enter Password
|
||||||
Enter Recovery Password: Enter Recovery Password
|
Enter Recovery Password: Enter Recovery Password
|
||||||
@ -107,7 +112,8 @@ Errors:
|
|||||||
Something went wrong! {{err}} Please try again.: Something went wrong! {{err}} Please try again.
|
Something went wrong! {{err}} Please try again.: Something went wrong! {{err}} Please try again.
|
||||||
Expand Edit: Expand Edit
|
Expand Edit: Expand Edit
|
||||||
Expires on {{proExpiresOn}}: Expires on {{proExpiresOn}}
|
Expires on {{proExpiresOn}}: Expires on {{proExpiresOn}}
|
||||||
Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template: Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
? Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||||
|
: Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||||
Field is not found in the template: Field is not found in the template
|
Field is not found in the template: Field is not found in the template
|
||||||
Find Clip: Find Clip
|
Find Clip: Find Clip
|
||||||
Find History: Find History
|
Find History: Find History
|
||||||
@ -172,7 +178,8 @@ Pasted: Pasted
|
|||||||
Path: Path
|
Path: Path
|
||||||
Pause: Pause
|
Pause: Pause
|
||||||
Pause Playing: Pause Playing
|
Pause Playing: Pause Playing
|
||||||
'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.': 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
? 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
||||||
|
: 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
||||||
Pin Selected: Pin Selected
|
Pin Selected: Pin Selected
|
||||||
Pinned: Pinned
|
Pinned: Pinned
|
||||||
Play: Play
|
Play: Play
|
||||||
@ -337,7 +344,8 @@ View Edit: View Edit
|
|||||||
Views:
|
Views:
|
||||||
Paste Menu: Paste Menu
|
Paste Menu: Paste Menu
|
||||||
We apologize but you found a bug. Please report this issue to us and try again: We apologize but you found a bug. Please report this issue to us and try again
|
We apologize but you found a bug. Please report this issue to us and try again: We apologize but you found a bug. Please report this issue to us and try again
|
||||||
We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.: We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
? We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
||||||
|
: We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
||||||
Yes: Yes
|
Yes: Yes
|
||||||
Yes, activate: Yes, activate
|
Yes, activate: Yes, activate
|
||||||
chars: chars
|
chars: chars
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import createMenuTree from '~/libs/create-menu-tree'
|
import createMenuTree from '~/libs/create-menu-tree'
|
||||||
import { collectionsStoreAtom, settingsStoreAtom, uiStoreAtom } from '~/store'
|
|
||||||
import { useAtom, useAtomValue } from 'jotai'
|
|
||||||
import {
|
import {
|
||||||
CheckSquare,
|
ACTION_TYPE_COMFIRMATION_MODAL,
|
||||||
ChevronDown,
|
actionNameForConfirmModal,
|
||||||
ListFilter,
|
actionTypeConfirmed,
|
||||||
LockKeyhole,
|
actionTypeForConfirmModal,
|
||||||
Trash,
|
collectionsStoreAtom,
|
||||||
Trash2,
|
openActionConfirmModal,
|
||||||
} from 'lucide-react'
|
openProtectedContentModal,
|
||||||
|
pendingProtectedCollectionId,
|
||||||
|
settingsStoreAtom,
|
||||||
|
uiStoreAtom,
|
||||||
|
} from '~/store'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
import { CheckSquare, ChevronDown, LockKeyhole, Trash, Trash2 } from 'lucide-react'
|
||||||
// Added ChevronDown, ListFilter
|
// Added ChevronDown, ListFilter
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
@ -102,6 +106,17 @@ export default function ManageCollectionsSection({
|
|||||||
const [confirmDeleteCollectionId, setConfirmDeleteCollectionId] = useState<
|
const [confirmDeleteCollectionId, setConfirmDeleteCollectionId] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null)
|
>(null)
|
||||||
|
const [pendingProtectionToggle, setPendingProtectionToggle] = useState(false)
|
||||||
|
const [pendingProtectedCollectionChange, setPendingProtectedCollectionChange] =
|
||||||
|
useState<{
|
||||||
|
collectionId: string
|
||||||
|
checked: boolean
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
// Use action types from constants
|
||||||
|
const ACTION_TOGGLE_PROTECTION = ACTION_TYPE_COMFIRMATION_MODAL.toggleProtection
|
||||||
|
const ACTION_CHANGE_PROTECTED_COLLECTIONS =
|
||||||
|
ACTION_TYPE_COMFIRMATION_MODAL.changeProtectedCollections
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collectionCardEditId) {
|
if (collectionCardEditId) {
|
||||||
@ -123,6 +138,38 @@ export default function ManageCollectionsSection({
|
|||||||
setData(menuItems.length > 0 ? createMenuTree(menuItems) : [])
|
setData(menuItems.length > 0 ? createMenuTree(menuItems) : [])
|
||||||
}, [menuItems, menuItems.length])
|
}, [menuItems, menuItems.length])
|
||||||
|
|
||||||
|
// Handle confirmed actions after PIN verification
|
||||||
|
useEffect(() => {
|
||||||
|
if (actionTypeConfirmed.value === ACTION_TOGGLE_PROTECTION) {
|
||||||
|
setHasPinProtectedCollections(pendingProtectionToggle).then(() => {
|
||||||
|
setPendingProtectionToggle(false)
|
||||||
|
actionTypeConfirmed.value = null
|
||||||
|
})
|
||||||
|
} else if (
|
||||||
|
actionTypeConfirmed.value === ACTION_CHANGE_PROTECTED_COLLECTIONS &&
|
||||||
|
pendingProtectedCollectionChange
|
||||||
|
) {
|
||||||
|
const currentProtectedIds = [...protectedCollections]
|
||||||
|
if (pendingProtectedCollectionChange.checked) {
|
||||||
|
if (
|
||||||
|
!currentProtectedIds.includes(pendingProtectedCollectionChange.collectionId)
|
||||||
|
) {
|
||||||
|
currentProtectedIds.push(pendingProtectedCollectionChange.collectionId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = currentProtectedIds.indexOf(
|
||||||
|
pendingProtectedCollectionChange.collectionId
|
||||||
|
)
|
||||||
|
if (index > -1) {
|
||||||
|
currentProtectedIds.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setProtectedCollections(currentProtectedIds)
|
||||||
|
setPendingProtectedCollectionChange(null)
|
||||||
|
actionTypeConfirmed.value = null
|
||||||
|
}
|
||||||
|
}, [actionTypeConfirmed.value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoSize disableWidth>
|
<AutoSize disableWidth>
|
||||||
{({ height }) => {
|
{({ height }) => {
|
||||||
@ -205,7 +252,7 @@ export default function ManageCollectionsSection({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text
|
<Flex
|
||||||
className={`${
|
className={`${
|
||||||
isSelected
|
isSelected
|
||||||
? isEnabled
|
? isEnabled
|
||||||
@ -214,19 +261,45 @@ export default function ManageCollectionsSection({
|
|||||||
: 'hover:text-slate-500'
|
: 'hover:text-slate-500'
|
||||||
} !font-medium ${
|
} !font-medium ${
|
||||||
isEnabled ? 'cursor-pointer' : 'text-muted-foreground'
|
isEnabled ? 'cursor-pointer' : 'text-muted-foreground'
|
||||||
}`}
|
} items-center gap-2`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isEnabled && !isSelected) {
|
if (isEnabled && !isSelected) {
|
||||||
selectCollectionById({
|
// Check if collection is protected
|
||||||
selectCollection: {
|
const isProtectedCollection =
|
||||||
collectionId,
|
hasPinProtectedCollections &&
|
||||||
},
|
protectedCollections.includes(collectionId)
|
||||||
})
|
|
||||||
|
if (isProtectedCollection) {
|
||||||
|
// Store pending collection and show PIN modal
|
||||||
|
pendingProtectedCollectionId.value = collectionId
|
||||||
|
openProtectedContentModal.value = true
|
||||||
|
} else {
|
||||||
|
// Switch directly
|
||||||
|
selectCollectionById({
|
||||||
|
selectCollection: {
|
||||||
|
collectionId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
<Text className="font-medium truncate">{title}</Text>
|
||||||
</Text>
|
{hasPinProtectedCollections &&
|
||||||
|
protectedCollections.includes(collectionId) && (
|
||||||
|
<ToolTip
|
||||||
|
text={t('Protected Collection', {
|
||||||
|
ns: 'collections',
|
||||||
|
})}
|
||||||
|
isCompact
|
||||||
|
>
|
||||||
|
<LockKeyhole
|
||||||
|
size={15}
|
||||||
|
className="text-gray-600 dark:text-gray-500 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
{!isCardEdit && isEnabled && (
|
{!isCardEdit && isEnabled && (
|
||||||
@ -244,11 +317,21 @@ export default function ManageCollectionsSection({
|
|||||||
size="xs"
|
size="xs"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
selectCollectionById({
|
// Check if collection is protected
|
||||||
selectCollection: {
|
const isProtectedCollection =
|
||||||
collectionId,
|
hasPinProtectedCollections &&
|
||||||
},
|
protectedCollections.includes(collectionId)
|
||||||
})
|
|
||||||
|
if (isProtectedCollection) {
|
||||||
|
pendingProtectedCollectionId.value = collectionId
|
||||||
|
openProtectedContentModal.value = true
|
||||||
|
} else {
|
||||||
|
selectCollectionById({
|
||||||
|
selectCollection: {
|
||||||
|
collectionId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Select', { ns: 'common' })}
|
{t('Select', { ns: 'common' })}
|
||||||
@ -445,7 +528,12 @@ export default function ManageCollectionsSection({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{screenLockPassCode && (
|
{screenLockPassCode && (
|
||||||
<Card>
|
<Card
|
||||||
|
className={`${
|
||||||
|
!hasPinProtectedCollections &&
|
||||||
|
'opacity-80 bg-gray-100 dark:bg-gray-900/80'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
||||||
<CardTitle className="animate-in fade-in text-md font-medium border-red-300 border-1 w-full">
|
<CardTitle className="animate-in fade-in text-md font-medium border-red-300 border-1 w-full">
|
||||||
<Flex className="flex flex-row items-center justify-start space-y-0 pb-1 gap-2">
|
<Flex className="flex flex-row items-center justify-start space-y-0 pb-1 gap-2">
|
||||||
@ -454,10 +542,19 @@ export default function ManageCollectionsSection({
|
|||||||
</Flex>
|
</Flex>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Switch
|
<Switch
|
||||||
checked={isShowCollectionNameOnNavBar}
|
checked={hasPinProtectedCollections}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onCheckedChange={() => {
|
onCheckedChange={checked => {
|
||||||
setHasPinProtectedCollections(!hasPinProtectedCollections)
|
if (hasPinProtectedCollections) {
|
||||||
|
setPendingProtectionToggle(checked)
|
||||||
|
actionNameForConfirmModal.value = checked
|
||||||
|
? t('Enable PIN Protection', { ns: 'collections' })
|
||||||
|
: t('Disable PIN Protection', { ns: 'collections' })
|
||||||
|
actionTypeForConfirmModal.value = ACTION_TOGGLE_PROTECTION
|
||||||
|
openActionConfirmModal.value = true
|
||||||
|
} else {
|
||||||
|
setHasPinProtectedCollections(checked)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@ -482,28 +579,48 @@ export default function ManageCollectionsSection({
|
|||||||
{collections.map(collection => (
|
{collections.map(collection => (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
key={collection.collectionId}
|
key={collection.collectionId}
|
||||||
|
disabled={!hasPinProtectedCollections}
|
||||||
checked={protectedCollections.includes(
|
checked={protectedCollections.includes(
|
||||||
collection.collectionId
|
collection.collectionId
|
||||||
)}
|
)}
|
||||||
onCheckedChange={checked => {
|
onCheckedChange={checked => {
|
||||||
const currentProtectedIds = [...protectedCollections]
|
// If PIN protection is enabled, require PIN to change protected collections
|
||||||
if (checked) {
|
if (hasPinProtectedCollections) {
|
||||||
if (
|
setPendingProtectedCollectionChange({
|
||||||
!currentProtectedIds.includes(
|
collectionId: collection.collectionId,
|
||||||
|
checked,
|
||||||
|
})
|
||||||
|
actionNameForConfirmModal.value = checked
|
||||||
|
? t('Add To Protected Collections', {
|
||||||
|
ns: 'collections',
|
||||||
|
})
|
||||||
|
: t('Remove From Protected Collections', {
|
||||||
|
ns: 'collections',
|
||||||
|
})
|
||||||
|
actionTypeForConfirmModal.value =
|
||||||
|
ACTION_CHANGE_PROTECTED_COLLECTIONS
|
||||||
|
openActionConfirmModal.value = true
|
||||||
|
} else {
|
||||||
|
// If PIN protection is not enabled, allow changes without PIN
|
||||||
|
const currentProtectedIds = [...protectedCollections]
|
||||||
|
if (checked) {
|
||||||
|
if (
|
||||||
|
!currentProtectedIds.includes(
|
||||||
|
collection.collectionId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
currentProtectedIds.push(collection.collectionId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = currentProtectedIds.indexOf(
|
||||||
collection.collectionId
|
collection.collectionId
|
||||||
)
|
)
|
||||||
) {
|
if (index > -1) {
|
||||||
currentProtectedIds.push(collection.collectionId)
|
currentProtectedIds.splice(index, 1)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const index = currentProtectedIds.indexOf(
|
|
||||||
collection.collectionId
|
|
||||||
)
|
|
||||||
if (index > -1) {
|
|
||||||
currentProtectedIds.splice(index, 1)
|
|
||||||
}
|
}
|
||||||
|
setProtectedCollections(currentProtectedIds)
|
||||||
}
|
}
|
||||||
setProtectedCollections(currentProtectedIds)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{collection.title}
|
{collection.title}
|
||||||
@ -512,11 +629,11 @@ export default function ManageCollectionsSection({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Box className="mt-4">
|
<Box className="mt-4">
|
||||||
<Text className="text-sm font-medium mb-1">
|
<Text className="text-sm font-medium my-2">
|
||||||
{t('Pin Protected Collections', { ns: 'collections' })}:
|
{t('Pin Protected Collections', { ns: 'collections' })}:
|
||||||
</Text>
|
</Text>
|
||||||
{protectedCollections.length > 0 ? (
|
{protectedCollections.length > 0 ? (
|
||||||
<Flex className="flex-wrap gap-2">
|
<Flex className="flex-wrap gap-2 justify-start">
|
||||||
{protectedCollections.map(id => {
|
{protectedCollections.map(id => {
|
||||||
const collection = collections.find(
|
const collection = collections.find(
|
||||||
c => c.collectionId === id
|
c => c.collectionId === id
|
||||||
@ -524,7 +641,7 @@ export default function ManageCollectionsSection({
|
|||||||
return collection ? (
|
return collection ? (
|
||||||
<Badge
|
<Badge
|
||||||
key={id}
|
key={id}
|
||||||
variant="secondary"
|
variant="graySecondary"
|
||||||
className="font-normal"
|
className="font-normal"
|
||||||
>
|
>
|
||||||
{collection.title}
|
{collection.title}
|
||||||
|
@ -29,6 +29,8 @@ export const CONTENT_TYPE_LANGUAGE = {
|
|||||||
export const ACTION_TYPE_COMFIRMATION_MODAL = {
|
export const ACTION_TYPE_COMFIRMATION_MODAL = {
|
||||||
resetPassword: 'RESET_PASSWORD',
|
resetPassword: 'RESET_PASSWORD',
|
||||||
resetPasscode: 'RESET_PASSCODE',
|
resetPasscode: 'RESET_PASSCODE',
|
||||||
|
toggleProtection: 'TOGGLE_PROTECTION',
|
||||||
|
changeProtectedCollections: 'CHANGE_PROTECTED_COLLECTIONS',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const APP_TOURS = {
|
export const APP_TOURS = {
|
||||||
|
@ -214,7 +214,7 @@ export interface SettingsStoreState {
|
|||||||
setClipTextMinLength: (width: number) => void
|
setClipTextMinLength: (width: number) => void
|
||||||
setClipTextMaxLength: (height: number) => void
|
setClipTextMaxLength: (height: number) => void
|
||||||
setProtectedCollections: (ids: string[]) => void
|
setProtectedCollections: (ids: string[]) => void
|
||||||
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => void
|
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsStoreState & Settings = {
|
const initialState: SettingsStoreState & Settings = {
|
||||||
@ -296,7 +296,7 @@ const initialState: SettingsStoreState & Settings = {
|
|||||||
isKeepStarredOnClearEnabled: false,
|
isKeepStarredOnClearEnabled: false,
|
||||||
protectedCollections: [],
|
protectedCollections: [],
|
||||||
hasPinProtectedCollections: false,
|
hasPinProtectedCollections: false,
|
||||||
setHasPinProtectedCollections: () => {},
|
setHasPinProtectedCollections: async () => {},
|
||||||
CONST: {
|
CONST: {
|
||||||
APP_DETECT_LANGUAGES_SUPPORTED: [],
|
APP_DETECT_LANGUAGES_SUPPORTED: [],
|
||||||
},
|
},
|
||||||
@ -776,8 +776,8 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
|
|||||||
setProtectedCollections: async (ids: string[]) => {
|
setProtectedCollections: async (ids: string[]) => {
|
||||||
return get().updateSetting('protectedCollections', ids.join(','))
|
return get().updateSetting('protectedCollections', ids.join(','))
|
||||||
},
|
},
|
||||||
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => {
|
setHasPinProtectedCollections: async (hasPinProtectedCollections: boolean) => {
|
||||||
return set(() => ({ hasPinProtectedCollections }))
|
return get().updateSetting('hasPinProtectedCollections', hasPinProtectedCollections)
|
||||||
},
|
},
|
||||||
isNotTourCompletedOrSkipped: (tourName: string) => {
|
isNotTourCompletedOrSkipped: (tourName: string) => {
|
||||||
const { appToursCompletedList, appToursSkippedList } = get()
|
const { appToursCompletedList, appToursSkippedList } = get()
|
||||||
|
@ -11,7 +11,7 @@ import { ACTION_TYPE_COMFIRMATION_MODAL, APP_TOURS } from './constants'
|
|||||||
import { Song, SongSourceType } from './playerStore'
|
import { Song, SongSourceType } from './playerStore'
|
||||||
|
|
||||||
type ValueOf<T> = T[keyof T]
|
type ValueOf<T> = T[keyof T]
|
||||||
type ActionType = ValueOf<typeof ACTION_TYPE_COMFIRMATION_MODAL>
|
export type ActionType = ValueOf<typeof ACTION_TYPE_COMFIRMATION_MODAL>
|
||||||
export type AppTourType = ValueOf<typeof APP_TOURS>
|
export type AppTourType = ValueOf<typeof APP_TOURS>
|
||||||
|
|
||||||
export const visibilityCopyPopup = signal(false)
|
export const visibilityCopyPopup = signal(false)
|
||||||
@ -27,6 +27,7 @@ export const openAboutPasteBarModal = signal(false)
|
|||||||
export const openContactUsFormModal = signal(false)
|
export const openContactUsFormModal = signal(false)
|
||||||
export const openOnBoardingTourName = signal<AppTourType | null>(null)
|
export const openOnBoardingTourName = signal<AppTourType | null>(null)
|
||||||
export const openProtectedContentModal = signal(false)
|
export const openProtectedContentModal = signal(false)
|
||||||
|
export const pendingProtectedCollectionId = signal<string | null>(null)
|
||||||
export const onBoardingTourSingleElements = signal<string | string[] | null>(null)
|
export const onBoardingTourSingleElements = signal<string | string[] | null>(null)
|
||||||
export const openOSXSystemPermissionsModal = signal(false)
|
export const openOSXSystemPermissionsModal = signal(false)
|
||||||
export const actionNameForConfirmModal = signal<string | null>(null)
|
export const actionNameForConfirmModal = signal<string | null>(null)
|
||||||
|
@ -252,10 +252,3 @@ export const uiStore = createStore<UIStoreState>()(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const uiStoreAtom = atomWithStore(uiStore)
|
export const uiStoreAtom = atomWithStore(uiStore)
|
||||||
|
|
||||||
// Atoms for Collection PIN Prompt Modal (using LockScreenConfirmationModal)
|
|
||||||
export const isCollectionPinModalOpenAtom = atom(false)
|
|
||||||
export const collectionPinModalPropsAtom = atom<{
|
|
||||||
title: string
|
|
||||||
onConfirmSuccess: () => void
|
|
||||||
} | null>(null)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user