Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
294b4bcb
Commit
294b4bcb
authored
Apr 05, 2026
by
erio
Browse files
fix: remove Sora S3 profile code from DataManagementView, keep backup functionality
parent
67008b5d
Changes
1
Hide whitespace changes
Inline
Side-by-side
frontend/src/views/admin/DataManagementView.vue
View file @
294b4bcb
<
template
>
<
template
>
<div
class=
"space-y-6"
>
<div
class=
"space-y-6"
>
<div
class=
"card p-6"
>
<!-- Placeholder: no data management content configured -->
<div
class=
"mb-4 flex flex-wrap items-center justify-between gap-3"
>
</div>
<div>
<h3
class=
"text-base font-semibold text-gray-900 dark:text-white"
>
{{
t
(
'
admin.settings.soraS3.title
'
)
}}
</h3>
<p
class=
"mt-1 text-sm text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.settings.soraS3.description
'
)
}}
</p>
</div>
<div
class=
"flex flex-wrap gap-2"
>
<button
type=
"button"
class=
"btn btn-secondary btn-sm"
@
click=
"startCreateSoraProfile"
>
{{
t
(
'
admin.settings.soraS3.newProfile
'
)
}}
</button>
<button
type=
"button"
class=
"btn btn-secondary btn-sm"
:disabled=
"loadingSoraProfiles"
@
click=
"loadSoraS3Profiles"
>
{{
loadingSoraProfiles
?
t
(
'
common.loading
'
)
:
t
(
'
admin.settings.soraS3.reloadProfiles
'
)
}}
</button>
</div>
</div>
<div
class=
"overflow-x-auto"
>
<table
class=
"w-full min-w-[1000px] text-sm"
>
<thead>
<tr
class=
"border-b border-gray-200 text-left text-xs uppercase tracking-wide text-gray-500 dark:border-dark-700 dark:text-gray-400"
>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.profile
'
)
}}
</th>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.active
'
)
}}
</th>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.endpoint
'
)
}}
</th>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.bucket
'
)
}}
</th>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.quota
'
)
}}
</th>
<th
class=
"py-2 pr-4"
>
{{
t
(
'
admin.settings.soraS3.columns.updatedAt
'
)
}}
</th>
<th
class=
"py-2"
>
{{
t
(
'
admin.settings.soraS3.columns.actions
'
)
}}
</th>
</tr>
</thead>
<tbody>
<tr
v-for=
"profile in soraS3Profiles"
:key=
"profile.profile_id"
class=
"border-b border-gray-100 align-top dark:border-dark-800"
>
<td
class=
"py-3 pr-4"
>
<div
class=
"font-mono text-xs"
>
{{
profile
.
profile_id
}}
</div>
<div
class=
"mt-1 text-xs text-gray-600 dark:text-gray-400"
>
{{
profile
.
name
}}
</div>
</td>
<td
class=
"py-3 pr-4"
>
<span
class=
"rounded px-2 py-0.5 text-xs"
:class=
"profile.is_active ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300' : 'bg-gray-100 text-gray-700 dark:bg-dark-800 dark:text-gray-300'"
>
{{
profile
.
is_active
?
t
(
'
common.enabled
'
)
:
t
(
'
common.disabled
'
)
}}
</span>
</td>
<td
class=
"py-3 pr-4 text-xs"
>
<div>
{{
profile
.
endpoint
||
'
-
'
}}
</div>
<div
class=
"mt-1 text-gray-500 dark:text-gray-400"
>
{{
profile
.
region
||
'
-
'
}}
</div>
</td>
<td
class=
"py-3 pr-4 text-xs"
>
{{
profile
.
bucket
||
'
-
'
}}
</td>
<td
class=
"py-3 pr-4 text-xs"
>
{{
formatStorageQuotaGB
(
profile
.
default_storage_quota_bytes
)
}}
</td>
<td
class=
"py-3 pr-4 text-xs"
>
{{
formatDate
(
profile
.
updated_at
)
}}
</td>
<td
class=
"py-3 text-xs"
>
<div
class=
"flex flex-wrap gap-2"
>
<button
type=
"button"
class=
"btn btn-secondary btn-xs"
@
click=
"editSoraProfile(profile.profile_id)"
>
{{
t
(
'
common.edit
'
)
}}
</button>
<button
v-if=
"!profile.is_active"
type=
"button"
class=
"btn btn-secondary btn-xs"
:disabled=
"activatingSoraProfile"
@
click=
"activateSoraProfile(profile.profile_id)"
>
{{
t
(
'
admin.settings.soraS3.activateProfile
'
)
}}
</button>
<button
type=
"button"
class=
"btn btn-danger btn-xs"
:disabled=
"deletingSoraProfile"
@
click=
"removeSoraProfile(profile.profile_id)"
>
{{
t
(
'
common.delete
'
)
}}
</button>
</div>
</td>
</tr>
<tr
v-if=
"soraS3Profiles.length === 0"
>
<td
colspan=
"7"
class=
"py-6 text-center text-sm text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.settings.soraS3.empty
'
)
}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<Teleport
to=
"body"
>
<Transition
name=
"dm-drawer-mask"
>
<div
v-if=
"soraProfileDrawerOpen"
class=
"fixed inset-0 z-[54] bg-black/40 backdrop-blur-sm"
@
click=
"closeSoraProfileDrawer"
></div>
</Transition>
<Transition
name=
"dm-drawer-panel"
>
<div
v-if=
"soraProfileDrawerOpen"
class=
"fixed inset-y-0 right-0 z-[55] flex h-full w-full max-w-2xl flex-col border-l border-gray-200 bg-white shadow-2xl dark:border-dark-700 dark:bg-dark-900"
>
<div
class=
"flex items-center justify-between border-b border-gray-200 px-4 py-3 dark:border-dark-700"
>
<h4
class=
"text-sm font-semibold text-gray-900 dark:text-white"
>
{{
creatingSoraProfile
?
t
(
'
admin.settings.soraS3.createTitle
'
)
:
t
(
'
admin.settings.soraS3.editTitle
'
)
}}
</h4>
<button
type=
"button"
class=
"rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-dark-800 dark:hover:text-gray-200"
@
click=
"closeSoraProfileDrawer"
>
✕
</button>
</div>
<div
class=
"flex-1 overflow-y-auto p-4"
>
<div
class=
"grid grid-cols-1 gap-3 md:grid-cols-2"
>
<input
v-model=
"soraProfileForm.profile_id"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.profileID')"
:disabled=
"!creatingSoraProfile"
/>
<input
v-model=
"soraProfileForm.name"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.profileName')"
/>
<label
class=
"inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 md:col-span-2"
>
<input
v-model=
"soraProfileForm.enabled"
type=
"checkbox"
/>
<span>
{{
t
(
'
admin.settings.soraS3.enabled
'
)
}}
</span>
</label>
<input
v-model=
"soraProfileForm.endpoint"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.endpoint')"
/>
<input
v-model=
"soraProfileForm.region"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.region')"
/>
<input
v-model=
"soraProfileForm.bucket"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.bucket')"
/>
<input
v-model=
"soraProfileForm.prefix"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.prefix')"
/>
<input
v-model=
"soraProfileForm.access_key_id"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.accessKeyId')"
/>
<input
v-model=
"soraProfileForm.secret_access_key"
type=
"password"
class=
"input w-full"
:placeholder=
"soraProfileForm.secret_access_key_configured ? t('admin.settings.soraS3.secretConfigured') : t('admin.settings.soraS3.secretAccessKey')"
/>
<input
v-model=
"soraProfileForm.cdn_url"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.cdnUrl')"
/>
<div>
<input
v-model.number=
"soraProfileForm.default_storage_quota_gb"
type=
"number"
min=
"0"
step=
"0.1"
class=
"input w-full"
:placeholder=
"t('admin.settings.soraS3.defaultQuota')"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.settings.soraS3.defaultQuotaHint
'
)
}}
</p>
</div>
<label
class=
"inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300"
>
<input
v-model=
"soraProfileForm.force_path_style"
type=
"checkbox"
/>
<span>
{{
t
(
'
admin.settings.soraS3.forcePathStyle
'
)
}}
</span>
</label>
<label
v-if=
"creatingSoraProfile"
class=
"inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 md:col-span-2"
>
<input
v-model=
"soraProfileForm.set_active"
type=
"checkbox"
/>
<span>
{{
t
(
'
admin.settings.soraS3.setActive
'
)
}}
</span>
</label>
</div>
</div>
<div
class=
"flex flex-wrap justify-end gap-2 border-t border-gray-200 p-4 dark:border-dark-700"
>
<button
type=
"button"
class=
"btn btn-secondary btn-sm"
@
click=
"closeSoraProfileDrawer"
>
{{
t
(
'
common.cancel
'
)
}}
</button>
<button
type=
"button"
class=
"btn btn-secondary btn-sm"
:disabled=
"testingSoraProfile || !soraProfileForm.enabled"
@
click=
"testSoraProfileConnection"
>
{{
testingSoraProfile
?
t
(
'
common.loading
'
)
:
t
(
'
admin.settings.soraS3.testConnection
'
)
}}
</button>
<button
type=
"button"
class=
"btn btn-primary btn-sm"
:disabled=
"savingSoraProfile"
@
click=
"saveSoraProfile"
>
{{
savingSoraProfile
?
t
(
'
common.loading
'
)
:
t
(
'
admin.settings.soraS3.saveProfile
'
)
}}
</button>
</div>
</div>
</Transition>
</Teleport>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
ref
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
type
{
SoraS3Profile
}
from
'
@/api/admin/settings
'
import
{
adminAPI
}
from
'
@/api
'
import
{
useAppStore
}
from
'
@/stores
'
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
const
loadingSoraProfiles
=
ref
(
false
)
const
savingSoraProfile
=
ref
(
false
)
const
testingSoraProfile
=
ref
(
false
)
const
activatingSoraProfile
=
ref
(
false
)
const
deletingSoraProfile
=
ref
(
false
)
const
creatingSoraProfile
=
ref
(
false
)
const
soraProfileDrawerOpen
=
ref
(
false
)
const
soraS3Profiles
=
ref
<
SoraS3Profile
[]
>
([])
const
selectedSoraProfileID
=
ref
(
''
)
type
SoraS3ProfileForm
=
{
profile_id
:
string
name
:
string
set_active
:
boolean
enabled
:
boolean
endpoint
:
string
region
:
string
bucket
:
string
access_key_id
:
string
secret_access_key
:
string
secret_access_key_configured
:
boolean
prefix
:
string
force_path_style
:
boolean
cdn_url
:
string
default_storage_quota_gb
:
number
}
const
soraProfileForm
=
ref
<
SoraS3ProfileForm
>
(
newDefaultSoraS3ProfileForm
())
async
function
loadSoraS3Profiles
()
{
loadingSoraProfiles
.
value
=
true
try
{
const
result
=
await
adminAPI
.
settings
.
listSoraS3Profiles
()
soraS3Profiles
.
value
=
result
.
items
||
[]
if
(
!
creatingSoraProfile
.
value
)
{
const
stillExists
=
selectedSoraProfileID
.
value
?
soraS3Profiles
.
value
.
some
((
item
)
=>
item
.
profile_id
===
selectedSoraProfileID
.
value
)
:
false
if
(
!
stillExists
)
{
selectedSoraProfileID
.
value
=
pickPreferredSoraProfileID
()
}
syncSoraProfileFormWithSelection
()
}
}
catch
(
error
)
{
appStore
.
showError
((
error
as
{
message
?:
string
})?.
message
||
t
(
'
errors.networkError
'
))
}
finally
{
loadingSoraProfiles
.
value
=
false
}
}
function
startCreateSoraProfile
()
{
creatingSoraProfile
.
value
=
true
selectedSoraProfileID
.
value
=
''
soraProfileForm
.
value
=
newDefaultSoraS3ProfileForm
()
soraProfileDrawerOpen
.
value
=
true
}
function
editSoraProfile
(
profileID
:
string
)
{
selectedSoraProfileID
.
value
=
profileID
creatingSoraProfile
.
value
=
false
syncSoraProfileFormWithSelection
()
soraProfileDrawerOpen
.
value
=
true
}
function
closeSoraProfileDrawer
()
{
soraProfileDrawerOpen
.
value
=
false
if
(
creatingSoraProfile
.
value
)
{
creatingSoraProfile
.
value
=
false
selectedSoraProfileID
.
value
=
pickPreferredSoraProfileID
()
syncSoraProfileFormWithSelection
()
}
}
async
function
saveSoraProfile
()
{
if
(
!
soraProfileForm
.
value
.
name
.
trim
())
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.profileNameRequired
'
))
return
}
if
(
creatingSoraProfile
.
value
&&
!
soraProfileForm
.
value
.
profile_id
.
trim
())
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.profileIDRequired
'
))
return
}
if
(
!
creatingSoraProfile
.
value
&&
!
selectedSoraProfileID
.
value
)
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.profileSelectRequired
'
))
return
}
if
(
soraProfileForm
.
value
.
enabled
)
{
if
(
!
soraProfileForm
.
value
.
endpoint
.
trim
())
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.endpointRequired
'
))
return
}
if
(
!
soraProfileForm
.
value
.
bucket
.
trim
())
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.bucketRequired
'
))
return
}
if
(
!
soraProfileForm
.
value
.
access_key_id
.
trim
())
{
appStore
.
showError
(
t
(
'
admin.settings.soraS3.accessKeyRequired
'
))
return
}
}
savingSoraProfile
.
value
=
true
try
{
if
(
creatingSoraProfile
.
value
)
{
const
created
=
await
adminAPI
.
settings
.
createSoraS3Profile
({
profile_id
:
soraProfileForm
.
value
.
profile_id
.
trim
(),
name
:
soraProfileForm
.
value
.
name
.
trim
(),
set_active
:
soraProfileForm
.
value
.
set_active
,
enabled
:
soraProfileForm
.
value
.
enabled
,
endpoint
:
soraProfileForm
.
value
.
endpoint
,
region
:
soraProfileForm
.
value
.
region
,
bucket
:
soraProfileForm
.
value
.
bucket
,
access_key_id
:
soraProfileForm
.
value
.
access_key_id
,
secret_access_key
:
soraProfileForm
.
value
.
secret_access_key
||
undefined
,
prefix
:
soraProfileForm
.
value
.
prefix
,
force_path_style
:
soraProfileForm
.
value
.
force_path_style
,
cdn_url
:
soraProfileForm
.
value
.
cdn_url
,
default_storage_quota_bytes
:
Math
.
round
((
soraProfileForm
.
value
.
default_storage_quota_gb
||
0
)
*
1024
*
1024
*
1024
)
})
selectedSoraProfileID
.
value
=
created
.
profile_id
creatingSoraProfile
.
value
=
false
soraProfileDrawerOpen
.
value
=
false
appStore
.
showSuccess
(
t
(
'
admin.settings.soraS3.profileCreated
'
))
}
else
{
await
adminAPI
.
settings
.
updateSoraS3Profile
(
selectedSoraProfileID
.
value
,
{
name
:
soraProfileForm
.
value
.
name
.
trim
(),
enabled
:
soraProfileForm
.
value
.
enabled
,
endpoint
:
soraProfileForm
.
value
.
endpoint
,
region
:
soraProfileForm
.
value
.
region
,
bucket
:
soraProfileForm
.
value
.
bucket
,
access_key_id
:
soraProfileForm
.
value
.
access_key_id
,
secret_access_key
:
soraProfileForm
.
value
.
secret_access_key
||
undefined
,
prefix
:
soraProfileForm
.
value
.
prefix
,
force_path_style
:
soraProfileForm
.
value
.
force_path_style
,
cdn_url
:
soraProfileForm
.
value
.
cdn_url
,
default_storage_quota_bytes
:
Math
.
round
((
soraProfileForm
.
value
.
default_storage_quota_gb
||
0
)
*
1024
*
1024
*
1024
)
})
soraProfileDrawerOpen
.
value
=
false
appStore
.
showSuccess
(
t
(
'
admin.settings.soraS3.profileSaved
'
))
}
await
loadSoraS3Profiles
()
}
catch
(
error
)
{
appStore
.
showError
((
error
as
{
message
?:
string
})?.
message
||
t
(
'
errors.networkError
'
))
}
finally
{
savingSoraProfile
.
value
=
false
}
}
async
function
testSoraProfileConnection
()
{
testingSoraProfile
.
value
=
true
try
{
const
result
=
await
adminAPI
.
settings
.
testSoraS3Connection
({
profile_id
:
creatingSoraProfile
.
value
?
undefined
:
selectedSoraProfileID
.
value
,
enabled
:
soraProfileForm
.
value
.
enabled
,
endpoint
:
soraProfileForm
.
value
.
endpoint
,
region
:
soraProfileForm
.
value
.
region
,
bucket
:
soraProfileForm
.
value
.
bucket
,
access_key_id
:
soraProfileForm
.
value
.
access_key_id
,
secret_access_key
:
soraProfileForm
.
value
.
secret_access_key
||
undefined
,
prefix
:
soraProfileForm
.
value
.
prefix
,
force_path_style
:
soraProfileForm
.
value
.
force_path_style
,
cdn_url
:
soraProfileForm
.
value
.
cdn_url
,
default_storage_quota_bytes
:
Math
.
round
((
soraProfileForm
.
value
.
default_storage_quota_gb
||
0
)
*
1024
*
1024
*
1024
)
})
appStore
.
showSuccess
(
result
.
message
||
t
(
'
admin.settings.soraS3.testSuccess
'
))
}
catch
(
error
)
{
appStore
.
showError
((
error
as
{
message
?:
string
})?.
message
||
t
(
'
errors.networkError
'
))
}
finally
{
testingSoraProfile
.
value
=
false
}
}
async
function
activateSoraProfile
(
profileID
:
string
)
{
activatingSoraProfile
.
value
=
true
try
{
await
adminAPI
.
settings
.
setActiveSoraS3Profile
(
profileID
)
appStore
.
showSuccess
(
t
(
'
admin.settings.soraS3.profileActivated
'
))
await
loadSoraS3Profiles
()
}
catch
(
error
)
{
appStore
.
showError
((
error
as
{
message
?:
string
})?.
message
||
t
(
'
errors.networkError
'
))
}
finally
{
activatingSoraProfile
.
value
=
false
}
}
async
function
removeSoraProfile
(
profileID
:
string
)
{
if
(
!
window
.
confirm
(
t
(
'
admin.settings.soraS3.deleteConfirm
'
,
{
profileID
})))
{
return
}
deletingSoraProfile
.
value
=
true
try
{
await
adminAPI
.
settings
.
deleteSoraS3Profile
(
profileID
)
if
(
selectedSoraProfileID
.
value
===
profileID
)
{
selectedSoraProfileID
.
value
=
''
}
appStore
.
showSuccess
(
t
(
'
admin.settings.soraS3.profileDeleted
'
))
await
loadSoraS3Profiles
()
}
catch
(
error
)
{
appStore
.
showError
((
error
as
{
message
?:
string
})?.
message
||
t
(
'
errors.networkError
'
))
}
finally
{
deletingSoraProfile
.
value
=
false
}
}
function
formatDate
(
value
?:
string
):
string
{
if
(
!
value
)
{
return
'
-
'
}
const
date
=
new
Date
(
value
)
if
(
Number
.
isNaN
(
date
.
getTime
()))
{
return
value
}
return
date
.
toLocaleString
()
}
function
formatStorageQuotaGB
(
bytes
:
number
):
string
{
if
(
!
bytes
||
bytes
<=
0
)
{
return
'
0 GB
'
}
const
gb
=
bytes
/
(
1024
*
1024
*
1024
)
return
`
${
gb
.
toFixed
(
gb
>=
10
?
0
:
1
)}
GB`
}
function
pickPreferredSoraProfileID
():
string
{
const
active
=
soraS3Profiles
.
value
.
find
((
item
)
=>
item
.
is_active
)
if
(
active
)
{
return
active
.
profile_id
}
return
soraS3Profiles
.
value
[
0
]?.
profile_id
||
''
}
function
syncSoraProfileFormWithSelection
()
{
const
profile
=
soraS3Profiles
.
value
.
find
((
item
)
=>
item
.
profile_id
===
selectedSoraProfileID
.
value
)
soraProfileForm
.
value
=
newDefaultSoraS3ProfileForm
(
profile
)
}
function
newDefaultSoraS3ProfileForm
(
profile
?:
SoraS3Profile
):
SoraS3ProfileForm
{
if
(
!
profile
)
{
return
{
profile_id
:
''
,
name
:
''
,
set_active
:
false
,
enabled
:
false
,
endpoint
:
''
,
region
:
''
,
bucket
:
''
,
access_key_id
:
''
,
secret_access_key
:
''
,
secret_access_key_configured
:
false
,
prefix
:
'
sora/
'
,
force_path_style
:
false
,
cdn_url
:
''
,
default_storage_quota_gb
:
0
}
}
const
quotaBytes
=
profile
.
default_storage_quota_bytes
||
0
return
{
profile_id
:
profile
.
profile_id
,
name
:
profile
.
name
,
set_active
:
false
,
enabled
:
profile
.
enabled
,
endpoint
:
profile
.
endpoint
||
''
,
region
:
profile
.
region
||
''
,
bucket
:
profile
.
bucket
||
''
,
access_key_id
:
profile
.
access_key_id
||
''
,
secret_access_key
:
''
,
secret_access_key_configured
:
Boolean
(
profile
.
secret_access_key_configured
),
prefix
:
profile
.
prefix
||
''
,
force_path_style
:
Boolean
(
profile
.
force_path_style
),
cdn_url
:
profile
.
cdn_url
||
''
,
default_storage_quota_gb
:
Number
((
quotaBytes
/
(
1024
*
1024
*
1024
)).
toFixed
(
2
))
}
}
onMounted
(
async
()
=>
{
await
loadSoraS3Profiles
()
})
</
script
>
</
script
>
<
style
scoped
>
.dm-drawer-mask-enter-active
,
.dm-drawer-mask-leave-active
{
transition
:
opacity
0.2s
ease
;
}
.dm-drawer-mask-enter-from
,
.dm-drawer-mask-leave-to
{
opacity
:
0
;
}
.dm-drawer-panel-enter-active
,
.dm-drawer-panel-leave-active
{
transition
:
transform
0.24s
cubic-bezier
(
0.22
,
1
,
0.36
,
1
),
opacity
0.2s
ease
;
}
.dm-drawer-panel-enter-from
,
.dm-drawer-panel-leave-to
{
opacity
:
0.96
;
transform
:
translateX
(
100%
);
}
@media
(
prefers-reduced-motion
:
reduce
)
{
.dm-drawer-mask-enter-active
,
.dm-drawer-mask-leave-active
,
.dm-drawer-panel-enter-active
,
.dm-drawer-panel-leave-active
{
transition-duration
:
0s
;
}
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment