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 {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps'
|
||||||
import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType'
|
import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType'
|
||||||
import ImagePropEditor from './ImagePropEditor'
|
import ImagePropEditor from './ImagePropEditor'
|
||||||
|
import FilePropEditor from './FilePropEditor'
|
||||||
|
|
||||||
export const simplePropEditorByPropType: ISimplePropEditorByPropType = {
|
export const simplePropEditorByPropType: ISimplePropEditorByPropType = {
|
||||||
number: NumberPropEditor,
|
number: NumberPropEditor,
|
||||||
|
@ -16,6 +17,7 @@ export const simplePropEditorByPropType: ISimplePropEditorByPropType = {
|
||||||
stringLiteral: StringLiteralPropEditor,
|
stringLiteral: StringLiteralPropEditor,
|
||||||
rgba: RgbaPropEditor,
|
rgba: RgbaPropEditor,
|
||||||
image: ImagePropEditor,
|
image: ImagePropEditor,
|
||||||
|
file: FilePropEditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ISimplePropEditorByPropType = {
|
type ISimplePropEditorByPropType = {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type {NearbyKeyframes} from './getNearbyKeyframesOfTrack'
|
||||||
import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack'
|
import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack'
|
||||||
import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors'
|
import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors'
|
||||||
import NextPrevKeyframeCursors 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> {
|
interface EditingToolsCommon<T> {
|
||||||
value: T
|
value: T
|
||||||
|
@ -33,7 +33,7 @@ interface EditingToolsCommon<T> {
|
||||||
discardTemporaryValue(): void
|
discardTemporaryValue(): void
|
||||||
permanentlySetValue(v: T): void
|
permanentlySetValue(v: T): void
|
||||||
|
|
||||||
getAssetUrl: (asset: Asset) => string | undefined
|
getAssetUrl: (asset: Asset | AssetFile) => string | undefined
|
||||||
createAsset(asset: File): Promise<string | null>
|
createAsset(asset: File): Promise<string | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ function createPrism<T extends SerializablePrimitive>(
|
||||||
const editAssets = {
|
const editAssets = {
|
||||||
createAsset: (asset: File): Promise<string | null> =>
|
createAsset: (asset: File): Promise<string | null> =>
|
||||||
obj.sheet.project.assetStorage.createAsset(asset),
|
obj.sheet.project.assetStorage.createAsset(asset),
|
||||||
getAssetUrl: (asset: Asset) =>
|
getAssetUrl: (asset: Asset | AssetFile) =>
|
||||||
asset.id
|
asset.id
|
||||||
? obj.sheet.project.assetStorage.getAssetUrl(asset.id)
|
? obj.sheet.project.assetStorage.getAssetUrl(asset.id)
|
||||||
: undefined,
|
: 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> {
|
export interface IEditingTools<T> {
|
||||||
temporarilySetValue(v: T): void
|
temporarilySetValue(v: T): void
|
||||||
discardTemporaryValue(): void
|
discardTemporaryValue(): void
|
||||||
permanentlySetValue(v: T): void
|
permanentlySetValue(v: T): void
|
||||||
|
|
||||||
getAssetUrl(asset: Asset): string | undefined
|
getAssetUrl(asset: Asset | File): string | undefined
|
||||||
createAsset(asset: Blob): Promise<string | null>
|
createAsset(asset: Blob): Promise<string | null>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue