feat: auto update strategy picker

pull/19758/head
Joel 8 months ago committed by Junyan Qin
parent 14591abb39
commit 7c32bd7e53
No known key found for this signature in database
GPG Key ID: 22FE3AFADC710CEB

@ -1,18 +1,47 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import React, { useCallback } from 'react'
import type { AutoUpdateConfig } from './types'
import Label from '../label'
import StrategyPicker from './strategy-picker'
type Props = {
payload: AutoUpdateConfig
onChange: (payload: AutoUpdateConfig) => void
}
const AutoUpdateSetting: FC<Props> = ({
payload,
onChange,
}) => {
console.log(payload)
const { strategy_setting } = payload
const handleChange = useCallback((key: keyof AutoUpdateConfig) => {
return (value: AutoUpdateConfig[keyof AutoUpdateConfig]) => {
onChange({
...payload,
[key]: value,
})
}
}, [payload, onChange])
return (
<div>
<div className='self-stretch px-6'>
<div className='my-3 flex items-center'>
<div className='system-xs-medium-uppercase text-text-tertiary'>Updates Settings</div>
<div className='ml-2 h-px grow bg-divider-subtle'></div>
</div>
<div className='space-y-4'>
<div className='flex items-center justify-between'>
<Label label='Automatic updates' />
<StrategyPicker value={strategy_setting} onChange={handleChange('strategy_setting')} />
</div>
<div className='flex items-center justify-between'>
<Label label='Update time' />
</div>
<div className='flex items-center'>
<Label label='Specify plugins to update' />
</div>
</div>
</div>
)
}

@ -0,0 +1,98 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowDownSLine,
RiCheckLine,
} from '@remixicon/react'
import { AUTO_UPDATE_STRATEGY } from './types'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
const i18nPrefix = 'plugin.autoUpdate.strategy'
type Props = {
value: AUTO_UPDATE_STRATEGY
onChange: (value: AUTO_UPDATE_STRATEGY) => void
}
const StrategyPicker = ({
value,
onChange,
}: Props) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const options = [
{
value: AUTO_UPDATE_STRATEGY.disabled,
label: t(`${i18nPrefix}.disabled.name`),
description: t(`${i18nPrefix}.disabled.description`),
},
{
value: AUTO_UPDATE_STRATEGY.fixOnly,
label: t(`${i18nPrefix}.fixOnly.name`),
description: t(`${i18nPrefix}.fixOnly.description`),
},
{
value: AUTO_UPDATE_STRATEGY.latest,
label: t(`${i18nPrefix}.latest.name`),
description: t(`${i18nPrefix}.latest.description`),
},
]
const selectedOption = options.find(option => option.value === value)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top-end'
offset={4}
>
<PortalToFollowElemTrigger onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
setOpen(v => !v)
}}>
<Button
size='small'
>
{selectedOption?.label}
<RiArrowDownSLine className='h-3.5 w-3.5' />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[99]'>
<div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
{
options.map(option => (
<div
key={option.value}
className='flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover'
onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
onChange(option.value)
setOpen(false)
}}
>
<div className='mr-1 w-4 shrink-0'>
{
value === option.value && (
<RiCheckLine className='h-4 w-4 text-text-accent' />
)
}
</div>
<div className='grow'>
<div className='system-sm-semibold mb-0.5 text-text-secondary'>{option.label}</div>
<div className='system-xs-regular text-text-tertiary'>{option.description}</div>
</div>
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default StrategyPicker

@ -0,0 +1,28 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
type Props = {
label: string
description?: string
}
const Label: FC<Props> = ({
label,
description,
}) => {
return (
<div>
<div className={cn('flex h-6 items-center', description && 'h-4')}>
<span className='system-sm-semibold text-text-secondary'>{label}</span>
</div>
{description && (
<div className='body-xs-regular mt-1 text-text-tertiary'>
{description}
</div>
)}
</div>
)
}
export default React.memo(Label)

@ -8,12 +8,16 @@ import Button from '@/app/components/base/button'
import type { Permissions } from '@/app/components/plugins/types'
import { PermissionType } from '@/app/components/plugins/types'
import type { AutoUpdateConfig } from './auto-update-setting/types'
import AutoUpdateSetting from './auto-update-setting'
import { defaultValue as autoUpdateDefaultValue } from './auto-update-setting/config'
import Label from './label'
type Payload = Permissions & { autoUpdate: AutoUpdateConfig }
const i18nPrefix = 'plugin.privilege'
type Props = {
payload: Permissions & { autoUpdate: AutoUpdateConfig }
payload: Payload
onHide: () => void
onSave: (payload: Permissions) => void
onSave: (payload: Payload) => void
}
const PluginSettingModal: FC<Props> = ({
@ -22,7 +26,9 @@ const PluginSettingModal: FC<Props> = ({
onSave,
}) => {
const { t } = useTranslation()
const [tempPrivilege, setTempPrivilege] = useState<Permissions>(payload)
const { autoUpdate: autoUpdateConfig, ...privilege } = payload || {}
const [tempPrivilege, setTempPrivilege] = useState<Permissions>(privilege)
const [tempAutoUpdateConfig, setTempAutoUpdateConfig] = useState<AutoUpdateConfig>(autoUpdateConfig || autoUpdateDefaultValue)
const handlePrivilegeChange = useCallback((key: string) => {
return (value: PermissionType) => {
setTempPrivilege({
@ -33,9 +39,12 @@ const PluginSettingModal: FC<Props> = ({
}, [tempPrivilege])
const handleSave = useCallback(async () => {
await onSave(tempPrivilege)
await onSave({
...tempPrivilege,
autoUpdate: tempAutoUpdateConfig,
})
onHide()
}, [onHide, onSave, tempPrivilege])
}, [onHide, onSave, tempAutoUpdateConfig, tempPrivilege])
return (
<Modal
@ -54,9 +63,7 @@ const PluginSettingModal: FC<Props> = ({
{ title: t(`${i18nPrefix}.whoCanDebug`), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
].map(({ title, key, value }) => (
<div key={key} className='flex flex-col items-start gap-1 self-stretch'>
<div className='flex h-6 items-center gap-0.5'>
<span className='system-sm-semibold text-text-secondary'>{title}</span>
</div>
<Label label={title} />
<div className='flex w-full items-start justify-between gap-2'>
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
<OptionCard
@ -71,6 +78,8 @@ const PluginSettingModal: FC<Props> = ({
</div>
))}
</div>
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
<div className='flex h-[76px] items-center justify-end gap-2 self-stretch p-6 pt-5'>
<Button
className='min-w-[72px]'

@ -125,6 +125,22 @@ const translation = {
admins: 'Admins',
noone: 'No one',
},
autoUpdate: {
strategy: {
disabled: {
name: 'Disabled',
description: 'Plugins will not auto-update',
},
fixOnly: {
name: 'Fix Only',
description: 'Auto-update for patch versions only (e.g., 1.0.1 → 1.0.2). Minor version changes won\'t trigger updates.',
},
latest: {
name: 'Latest',
description: 'Always update to latest version',
},
},
},
pluginInfoModal: {
title: 'Plugin info',
repository: 'Repository',

Loading…
Cancel
Save