feat: Added "Private vaults" as a Labs feature. Can be enabled from Preferences > General > Labs (#2562)

This commit is contained in:
Aman Harwara
2023-10-05 11:07:01 +00:00
committed by GitHub
parent a0e0567306
commit 4724b08c65
18 changed files with 45 additions and 20 deletions

View File

@@ -41,6 +41,7 @@ export class NativeFeatureIdentifier extends ValueObject<NativeFeatureIdentifier
Clipper: 'org.standardnotes.clipper', Clipper: 'org.standardnotes.clipper',
Vaults: 'org.standardnotes.vaults',
SharedVaults: 'org.standardnotes.shared-vaults', SharedVaults: 'org.standardnotes.shared-vaults',
DeprecatedMarkdownVisualEditor: 'org.standardnotes.markdown-visual-editor', DeprecatedMarkdownVisualEditor: 'org.standardnotes.markdown-visual-editor',
@@ -74,4 +75,4 @@ export class NativeFeatureIdentifier extends ValueObject<NativeFeatureIdentifier
/** /**
* Identifier for standalone filesafe instance offered as legacy installable via extensions-server * Identifier for standalone filesafe instance offered as legacy installable via extensions-server
*/ */
export const ExperimentalFeatures = [] export const ExperimentalFeatures = [NativeFeatureIdentifier.TYPES.Vaults]

View File

@@ -1,5 +1,16 @@
import { RoleName } from '@standardnotes/domain-core'
import { AnyFeatureDescription } from '../Feature/AnyFeatureDescription' import { AnyFeatureDescription } from '../Feature/AnyFeatureDescription'
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
import { PermissionName } from '../Permission/PermissionName'
export function experimentalFeatures(): AnyFeatureDescription[] { export function experimentalFeatures(): AnyFeatureDescription[] {
return [] return [
{
name: 'Private vaults',
description: 'Private vaults allow you to store notes, files and tags into separate, encrypted vaults.',
availableInRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
identifier: NativeFeatureIdentifier.TYPES.Vaults,
permission_name: PermissionName.Vaults,
},
]
} }

View File

@@ -38,5 +38,6 @@ export enum PermissionName {
SubscriptionSharing = 'server:subscription-sharing', SubscriptionSharing = 'server:subscription-sharing',
SuperEditor = 'editor:super-editor', SuperEditor = 'editor:super-editor',
Clipper = 'app:clipper', Clipper = 'app:clipper',
Vaults = 'app:vaults',
SharedVaults = 'server:shared-vaults', SharedVaults = 'server:shared-vaults',
} }

View File

@@ -102,7 +102,7 @@ const FileMenuOptions: FunctionComponent<Props> = ({
) : null} ) : null}
</> </>
)} )}
{application.featuresController.isEntitledToVaults() && ( {application.featuresController.isVaultsEnabled() && (
<AddToVaultMenuOption <AddToVaultMenuOption
iconClassName={iconClass} iconClassName={iconClass}
items={selectedFiles} items={selectedFiles}

View File

@@ -16,7 +16,7 @@ const VaultSelectionButton = ({ isMobileNavigation = false }: { isMobileNavigati
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const toggleMenu = () => setIsOpen(!isOpen) const toggleMenu = () => setIsOpen(!isOpen)
if (!application.featuresController.isEntitledToVaults()) { if (!application.featuresController.isVaultsEnabled()) {
return null return null
} }

View File

@@ -286,7 +286,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
)} )}
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
{application.featuresController.isEntitledToVaults() && ( {application.featuresController.isVaultsEnabled() && (
<AddToVaultMenuOption iconClassName={iconClass} items={notes} disabled={!hasAdminPermissionForAllSharedNotes} /> <AddToVaultMenuOption iconClassName={iconClass} items={notes} disabled={!hasAdminPermissionForAllSharedNotes} />
)} )}

View File

@@ -49,7 +49,7 @@ class PasswordWizard extends AbstractComponent<Props, State> {
continueTitle: DEFAULT_CONTINUE_TITLE, continueTitle: DEFAULT_CONTINUE_TITLE,
} }
if (props.application.featuresController.isEntitledToVaults()) { if (props.application.featuresController.isVaultsEnabled()) {
this.state = { this.state = {
...baseState, ...baseState,
lockContinue: true, lockContinue: true,

View File

@@ -26,7 +26,7 @@ export class PreferencesSessionController {
? PREFERENCES_MENU_ITEMS.slice() ? PREFERENCES_MENU_ITEMS.slice()
: READY_PREFERENCES_MENU_ITEMS.slice() : READY_PREFERENCES_MENU_ITEMS.slice()
if (application.featuresController.isEntitledToVaults()) { if (application.featuresController.isVaultsEnabled()) {
menuItems.push({ id: 'vaults', label: 'Vaults', icon: 'safe-square', order: 5 }) menuItems.push({ id: 'vaults', label: 'Vaults', icon: 'safe-square', order: 5 })
} }

View File

@@ -31,6 +31,7 @@ const Vaults = observer(() => {
const application = useApplication() const application = useApplication()
const hasAccount = application.hasAccount() const hasAccount = application.hasAccount()
const isSharedVaultsEnabled = application.featuresController.isEntitledToSharedVaults()
const [vaults, setVaults] = useState<VaultListingInterface[]>([]) const [vaults, setVaults] = useState<VaultListingInterface[]>([])
const [canCreateMoreVaults, setCanCreateMoreVaults] = useState(true) const [canCreateMoreVaults, setCanCreateMoreVaults] = useState(true)
@@ -163,7 +164,7 @@ const Vaults = observer(() => {
</PreferencesSegment> </PreferencesSegment>
</PreferencesGroup> </PreferencesGroup>
)} )}
{hasAccount && ( {hasAccount && isSharedVaultsEnabled && (
<PreferencesGroup> <PreferencesGroup>
<PreferencesSegment> <PreferencesSegment>
<Title>Contacts</Title> <Title>Contacts</Title>
@@ -180,7 +181,7 @@ const Vaults = observer(() => {
</PreferencesSegment> </PreferencesSegment>
</PreferencesGroup> </PreferencesGroup>
)} )}
{hasAccount && ( {hasAccount && isSharedVaultsEnabled && (
<PreferencesGroup> <PreferencesGroup>
<PreferencesSegment> <PreferencesSegment>
<Title>CollaborationID</Title> <Title>CollaborationID</Title>
@@ -235,13 +236,15 @@ const Vaults = observer(() => {
{canCreateMoreVaults ? ( {canCreateMoreVaults ? (
<div className="mt-2.5 flex gap-3"> <div className="mt-2.5 flex gap-3">
<Button label="Create Vault" onClick={createNewVault} /> <Button label="Create Vault" onClick={createNewVault} />
{hasAccount && <Button label="Create Shared Vault" onClick={createNewSharedVault} />} {hasAccount && isSharedVaultsEnabled && (
<Button label="Create Shared Vault" onClick={createNewSharedVault} />
)}
</div> </div>
) : ( ) : (
<div className="mt-3.5"> <div className="mt-3.5">
<NoProSubscription <NoProSubscription
application={application} application={application}
text={<span>Please upgrade in order to increase your shared vault limit.</span>} text={<span>Please upgrade in order to increase your vault limit.</span>}
/> />
</div> </div>
)} )}

View File

@@ -17,6 +17,8 @@ type Props = {
const VaultItem = ({ vault }: Props) => { const VaultItem = ({ vault }: Props) => {
const application = useApplication() const application = useApplication()
const canEnableCollaboration = application.hasAccount() && application.featuresController.isEntitledToSharedVaults()
const [isInviteModalOpen, setIsAddContactModalOpen] = useState(false) const [isInviteModalOpen, setIsAddContactModalOpen] = useState(false)
const closeInviteModal = () => setIsAddContactModalOpen(false) const closeInviteModal = () => setIsAddContactModalOpen(false)
@@ -128,7 +130,7 @@ const VaultItem = ({ vault }: Props) => {
{isCurrentUserAdmin ? ( {isCurrentUserAdmin ? (
vault.isSharedVaultListing() ? ( vault.isSharedVaultListing() ? (
<Button colorStyle="info" label="Invite Contacts" onClick={openInviteModal} /> <Button colorStyle="info" label="Invite Contacts" onClick={openInviteModal} />
) : application.hasAccount() ? ( ) : canEnableCollaboration ? (
<Button colorStyle="info" label="Enable Collaboration" onClick={convertToSharedVault} /> <Button colorStyle="info" label="Enable Collaboration" onClick={convertToSharedVault} />
) : null ) : null
) : null} ) : null}

View File

@@ -124,7 +124,7 @@ const Navigation = forwardRef<HTMLDivElement, Props>(({ application, className,
)} )}
<PreferencesButton openPreferences={() => application.preferencesController.openPreferences()} /> <PreferencesButton openPreferences={() => application.preferencesController.openPreferences()} />
<QuickSettingsButton application={application} isMobileNavigation /> <QuickSettingsButton application={application} isMobileNavigation />
{application.featuresController.isEntitledToVaults() && <VaultSelectionButton isMobileNavigation />} {application.featuresController.isVaultsEnabled() && <VaultSelectionButton isMobileNavigation />}
</div> </div>
{children} {children}
</div> </div>

View File

@@ -81,7 +81,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
iconGridClassName="max-h-30" iconGridClassName="max-h-30"
/> />
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
{application.featuresController.isEntitledToVaults() && ( {application.featuresController.isVaultsEnabled() && (
<AddToVaultMenuOption iconClassName="mr-2 text-neutral" items={[selectedTag]} /> <AddToVaultMenuOption iconClassName="mr-2 text-neutral" items={[selectedTag]} />
)} )}
<MenuItem className={'justify-between py-1.5'} onClick={onClickStar}> <MenuItem className={'justify-between py-1.5'} onClick={onClickStar}>

View File

@@ -120,7 +120,7 @@ const AddToVaultMenuOption = ({
setIsSubMenuOpen((isOpen) => !isOpen) setIsSubMenuOpen((isOpen) => !isOpen)
}, []) }, [])
if (!application.featuresController.isEntitledToVaults()) { if (!application.featuresController.isVaultsEnabled()) {
return null return null
} }

View File

@@ -130,7 +130,12 @@ export class FeaturesController extends AbstractViewController implements Intern
return status === FeatureStatus.Entitled return status === FeatureStatus.Entitled
} }
isEntitledToVaults(): boolean { isVaultsEnabled(): boolean {
const enabled = this.features.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.Vaults)
return featureTrunkVaultsEnabled() || enabled
}
isEntitledToSharedVaults(): boolean {
const status = this.features.getFeatureStatus( const status = this.features.getFeatureStatus(
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SharedVaults).getValue(), NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SharedVaults).getValue(),
) )

View File

@@ -71,7 +71,8 @@ describe('LinkingController', () => {
application.getPreference = jest.fn() application.getPreference = jest.fn()
application.addSingleEventObserver = jest.fn() application.addSingleEventObserver = jest.fn()
application.sync.sync = jest.fn() application.sync.sync = jest.fn()
application.featuresController.isEntitledToVaults = jest.fn().mockReturnValue(true) application.featuresController.isVaultsEnabled = jest.fn().mockReturnValue(true)
application.featuresController.isEntitledToSharedVaults = jest.fn().mockReturnValue(true)
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemManagerInterface> }) Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemManagerInterface> })

View File

@@ -208,7 +208,7 @@ export class LinkingController extends AbstractViewController implements Interna
const linkNoteAndFile = async (note: SNNote, file: FileItem) => { const linkNoteAndFile = async (note: SNNote, file: FileItem) => {
const updatedFile = await this.mutator.associateFileWithNote(file, note) const updatedFile = await this.mutator.associateFileWithNote(file, note)
if (this.featuresController.isEntitledToVaults()) { if (this.featuresController.isVaultsEnabled()) {
if (updatedFile) { if (updatedFile) {
const noteVault = this.vaults.getItemVault(note) const noteVault = this.vaults.getItemVault(note)
const fileVault = this.vaults.getItemVault(updatedFile) const fileVault = this.vaults.getItemVault(updatedFile)

View File

@@ -9,7 +9,8 @@ export function featureTrunkEnabled(trunk: FeatureTrunkName): boolean {
} }
export function featureTrunkVaultsEnabled(): boolean { export function featureTrunkVaultsEnabled(): boolean {
return InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults) // return InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)
return false
} }
export function featureTrunkHomeServerEnabled(): boolean { export function featureTrunkHomeServerEnabled(): boolean {

View File

@@ -23,7 +23,7 @@ export const useItemVaultInfo = (item: DecryptedItemInterface): ItemVaultInfo =>
const [sharedByContact, setSharedByContact] = useState<TrustedContactInterface>() const [sharedByContact, setSharedByContact] = useState<TrustedContactInterface>()
const updateInfo = useCallback(() => { const updateInfo = useCallback(() => {
if (!application.featuresController.isEntitledToVaults()) { if (!application.featuresController.isVaultsEnabled()) {
return return
} }