added "File" Prop Editor to studio project
This commit is contained in:
parent
91b0ec5cfa
commit
a7325aebc9
4 changed files with 161 additions and 5 deletions
154
theatre/studio/src/propEditors/simpleEditors/FilePropEditor.tsx
Normal file
154
theatre/studio/src/propEditors/simpleEditors/FilePropEditor.tsx
Normal file
|
@ -0,0 +1,154 @@
|
|||
import type {PropTypeConfig_File} from '@theatre/core/propTypes'
|
||||
import {Package, Trash} from '@theatre/studio/uiComponents/icons'
|
||||
import React, {useCallback, useEffect} from 'react'
|
||||
import styled, {css} from 'styled-components'
|
||||
import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps'
|
||||
|
||||
const Container = styled.div<{empty: boolean}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
gap: 4px;
|
||||
`
|
||||
|
||||
const AddFile = styled.div`
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
// rotate 45deg
|
||||
transform: rotate(45deg);
|
||||
--checker-color: #ededed36;
|
||||
&:hover {
|
||||
--checker-color: #ededed77;
|
||||
}
|
||||
// checkerboard background with 4px squares
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
var(--checker-color) 25%,
|
||||
transparent 25%
|
||||
),
|
||||
linear-gradient(-45deg, var(--checker-color) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--checker-color) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--checker-color) 75%);
|
||||
background-size: 5px 5px;
|
||||
`
|
||||
|
||||
const InputLabel = styled.label<{empty: boolean}>`
|
||||
position: relative;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
|
||||
height: 18px;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
|
||||
overflow: hidden;
|
||||
color: #ccc;
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
border-radius: 99999px;
|
||||
border: 1px solid hwb(220deg 40% 52%);
|
||||
&:hover {
|
||||
border-color: hwb(220deg 45% 52%);
|
||||
}
|
||||
|
||||
${(props) => (props.empty ? css`` : css``)}
|
||||
`
|
||||
|
||||
// file input
|
||||
const Input = styled.input.attrs({type: 'file'})`
|
||||
display: none;
|
||||
`
|
||||
|
||||
const DeleteButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: #a8a8a9;
|
||||
|
||||
border: none;
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
${Container}:hover & {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: white;
|
||||
}
|
||||
`
|
||||
|
||||
function FilePropEditor({
|
||||
propConfig,
|
||||
editingTools,
|
||||
value,
|
||||
autoFocus,
|
||||
}: ISimplePropEditorReactProps<PropTypeConfig_File>) {
|
||||
const [previewUrl, setPreviewUrl] = React.useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setPreviewUrl(editingTools.getAssetUrl(value))
|
||||
} else {
|
||||
setPreviewUrl(undefined)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const onChange = useCallback(
|
||||
async (event) => {
|
||||
const file = event.target.files[0]
|
||||
editingTools.permanentlySetValue({type: 'file', id: undefined})
|
||||
const fileId = await editingTools.createAsset(file)
|
||||
|
||||
if (!fileId) {
|
||||
editingTools.permanentlySetValue(value)
|
||||
} else {
|
||||
editingTools.permanentlySetValue({
|
||||
type: 'file',
|
||||
id: fileId,
|
||||
})
|
||||
}
|
||||
event.target.value = null
|
||||
},
|
||||
[editingTools, value],
|
||||
)
|
||||
|
||||
const empty = !value?.id
|
||||
|
||||
return (
|
||||
<Container empty={empty}>
|
||||
<InputLabel
|
||||
empty={empty}
|
||||
title={
|
||||
empty ? 'Upload file' : `"${value.id}" (Click to upload new file)`
|
||||
}
|
||||
>
|
||||
<Input type="file" onChange={onChange} autoFocus={autoFocus} />
|
||||
{previewUrl ? <Package /> : <AddFile />}
|
||||
</InputLabel>
|
||||
|
||||
{!empty && (
|
||||
<DeleteButton
|
||||
title="Delete file"
|
||||
onClick={() => {
|
||||
editingTools.permanentlySetValue({type: 'file', id: undefined})
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
</DeleteButton>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilePropEditor
|
|
@ -8,6 +8,7 @@ import RgbaPropEditor from './RgbaPropEditor'
|
|||
import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps'
|
||||
import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType'
|
||||
import ImagePropEditor from './ImagePropEditor'
|
||||
import FilePropEditor from './FilePropEditor'
|
||||
|
||||
export const simplePropEditorByPropType: ISimplePropEditorByPropType = {
|
||||
number: NumberPropEditor,
|
||||
|
@ -16,6 +17,7 @@ export const simplePropEditorByPropType: ISimplePropEditorByPropType = {
|
|||
stringLiteral: StringLiteralPropEditor,
|
||||
rgba: RgbaPropEditor,
|
||||
image: ImagePropEditor,
|
||||
file: FilePropEditor,
|
||||
}
|
||||
|
||||
type ISimplePropEditorByPropType = {
|
||||
|
|
|
@ -20,7 +20,7 @@ import type {NearbyKeyframes} from './getNearbyKeyframesOfTrack'
|
|||
import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack'
|
||||
import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors'
|
||||
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
||||
import type {Asset} from '@theatre/shared/utils/assets'
|
||||
import type {Asset, File as AssetFile} from '@theatre/shared/utils/assets'
|
||||
|
||||
interface EditingToolsCommon<T> {
|
||||
value: T
|
||||
|
@ -33,7 +33,7 @@ interface EditingToolsCommon<T> {
|
|||
discardTemporaryValue(): void
|
||||
permanentlySetValue(v: T): void
|
||||
|
||||
getAssetUrl: (asset: Asset) => string | undefined
|
||||
getAssetUrl: (asset: Asset | AssetFile) => string | undefined
|
||||
createAsset(asset: File): Promise<string | null>
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ function createPrism<T extends SerializablePrimitive>(
|
|||
const editAssets = {
|
||||
createAsset: (asset: File): Promise<string | null> =>
|
||||
obj.sheet.project.assetStorage.createAsset(asset),
|
||||
getAssetUrl: (asset: Asset) =>
|
||||
getAssetUrl: (asset: Asset | AssetFile) =>
|
||||
asset.id
|
||||
? obj.sheet.project.assetStorage.getAssetUrl(asset.id)
|
||||
: undefined,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type {Asset} from '@theatre/shared/utils/assets'
|
||||
import type {Asset, File} from '@theatre/shared/utils/assets'
|
||||
|
||||
export interface IEditingTools<T> {
|
||||
temporarilySetValue(v: T): void
|
||||
discardTemporaryValue(): void
|
||||
permanentlySetValue(v: T): void
|
||||
|
||||
getAssetUrl(asset: Asset): string | undefined
|
||||
getAssetUrl(asset: Asset | File): string | undefined
|
||||
createAsset(asset: Blob): Promise<string | null>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue