Skip to content

Commit

Permalink
Merge pull request #88 from nebulabroadcast/87-friendlier-buttons-in-…
Browse files Browse the repository at this point in the history
…the-asset-editor-navbar

Friendlier asset editor navbar
  • Loading branch information
martastain authored Oct 20, 2024
2 parents 459859a + dfff002 commit 2313bee
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 231 deletions.
19 changes: 15 additions & 4 deletions frontend/src/components/Button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,30 @@ const BaseButton = styled.button`
background: ${(props) => props.theme.inputBackground};
color: ${(props) => props.theme.colors.text};
font-size: ${(props) => props.theme.fontSize};
padding-left: ${(props) => props.theme.inputPadding};
padding-right: ${(props) => props.theme.inputPadding};
padding-left: 12px;
padding-right: 12px;
min-height: ${(props) => props.theme.inputHeight};
max-height: ${(props) => props.theme.inputHeight};
min-width: ${(props) => props.theme.inputHeight} !important;
&.icon-only {
padding: 0;
min-width: ${(props) => props.theme.inputHeight};
max-width: ${(props) => props.theme.inputHeight};
min-height: ${(props) => props.theme.inputHeight};
max-height: ${(props) => props.theme.inputHeight};
display: flex;
align-items: center;
justify-content: center;
}
user-select: none;
user-drag: none;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
gap: 8px;
cursor: pointer;
white-space: nowrap;
Expand Down Expand Up @@ -76,7 +87,7 @@ const Button = forwardRef(
return (
<BaseButton
{...props}
className={clsx(className, { active })}
className={clsx(className, { active }, !label && 'icon-only')}
title={tooltip}
ref={ref}
>
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/components/RadioButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from 'styled-components'

import { forwardRef } from 'react'
import clsx from 'clsx'

import Button from './Button'

const RadioContainer = styled.div`
display: flex;
gap: 1px;
button {
border-radius: 0;
border-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
`

const RadioButton = ({ options, value, onChange }) => {
return (
<RadioContainer>
{options.map((option) => (
<Button
key={option.value}
onClick={() => onChange(option.value)}
className={clsx({ active: option.value === value })}
icon={option.icon}
label={option.label}
tooltip={option.tooltip}
style={option.buttonStyle}
/>
))}
</RadioContainer>
)
}

export default RadioButton
1 change: 1 addition & 0 deletions frontend/src/components/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export { default as TextArea } from './TextArea'
export { default as Button } from './Button'
export { default as DatePicker } from './DatePicker'
export { default as Canvas } from './Canvas'
export { default as RadioButton } from './RadioButton'

export { DateTime, Timestamp } from './Fields'
export { Form, FormRow } from './Form'
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/containers/Upload.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ const UploadButton = ({ assetData, disabled }) => {
)}
<Button
icon="upload"
tooltip="Upload media file"
label="Upload media"
onClick={() => setDialogVisible(true)}
disabled={disabled}
/>
Expand Down
64 changes: 37 additions & 27 deletions frontend/src/pages/AssetEditor/AssetEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { Loader } from '/src/components'

import AssetEditorNav from './EditorNav'
import AssetMainProps from './AssetMainProps'
import EditorForm from './EditorForm'
import Preview from './Preview'

Expand Down Expand Up @@ -69,10 +70,7 @@ const AssetEditor = () => {
const [originalData, setOriginalData] = useState({})
const [loading, setLoading] = useState(false)
const [ConfirmDialog, confirm] = useConfirm()
const [previewVisible, setPreviewVisible] = useLocalStorage(
'previewVisible',
false
)
const [editorMode, setEditorMode] = useLocalStorage('editorMode', 'metadata')

// Load asset data

Expand Down Expand Up @@ -294,24 +292,23 @@ const AssetEditor = () => {

// Render

return (
<div className="grow column">
<AssetEditorNav
assetData={assetData}
onNewAsset={onNewAsset}
onCloneAsset={onCloneAsset}
onRevert={onRevert}
onSave={onSave}
setMeta={setMeta}
isChanged={isChanged}
previewVisible={previewVisible}
setPreviewVisible={setPreviewVisible}
enabledActions={enabledActions}
/>
const mainComponent = () => {
switch (editorMode) {
case 'preview':
return (
<div className="grow row">
<Preview assetData={assetData} setAssetData={setAssetData} />
</div>
)

{Object.keys(assetData || {}).length ? (
<div className="grow row">
{!previewVisible && (
default:
return (
<main className="grow column">
<AssetMainProps
assetData={assetData}
setMeta={setMeta}
enabledActions={enabledActions}
/>
<section
className={clsx('grow', 'column', {
'section-changed': isChanged,
Expand All @@ -337,13 +334,26 @@ const AssetEditor = () => {
/>
</div>
</section>
)}
</main>
)
}
}

{previewVisible && (
<Preview assetData={assetData} setAssetData={setAssetData} />
)}
</div>
) : null}
return (
<div className="grow column">
<AssetEditorNav
assetData={assetData}
onNewAsset={onNewAsset}
onCloneAsset={onCloneAsset}
onRevert={onRevert}
onSave={onSave}
setMeta={setMeta}
isChanged={isChanged}
editorMode={editorMode}
setEditorMode={setEditorMode}
enabledActions={enabledActions}
/>
{Object.keys(assetData || {}).length && mainComponent()}
<ConfirmDialog />
</div>
)
Expand Down
180 changes: 180 additions & 0 deletions frontend/src/pages/AssetEditor/AssetMainProps.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import nebula from '/src/nebula'

import { useDispatch } from 'react-redux'
import { useState, useMemo } from 'react'
import {
setCurrentViewId,
setSearchQuery,
showSendToDialog,
} from '/src/actions'

import {
Navbar,
Button,
Spacer,
Dropdown,
ToolbarSeparator,
InputTimecode,
Dialog,
} from '/src/components'

import { UploadButton } from '/src/containers/Upload'

import MetadataDetail from './MetadataDetail'
import ContextActionResult from './ContextAction'
import AssigneesButton from './AssigneesButton'

import contentType from 'content-type'

const AssetEditorNav = ({ assetData, setMeta, enabledActions }) => {
const [detailsVisible, setDetailsVisible] = useState(false)
const [contextActionResult, setContextActionResult] = useState(null)
const dispatch = useDispatch()

const currentFolder = useMemo(() => {
if (!nebula.settings.folders) return null
for (const f of nebula.settings.folders) {
if (f.id !== assetData?.id_folder) continue
return f
}
}, [{ ...assetData }])

const folderOptions = useMemo(() => {
return nebula.getWritableFolders().map((f) => ({
label: f.name,
style: { borderLeft: `4px solid ${f.color}` },
onClick: () => setMeta('id_folder', f.id),
}))
}, [])

// Actions

const scopedEndpoints = useMemo(() => {
const result = []
for (const scopedEndpoints of nebula.getScopedEndpoints('asset')) {
result.push({
label: scopedEndpoints.title,
onClick: () => {
nebula
.request(scopedEndpoints.endpoint, { id_asset: assetData.id })
.then((response) => {
setContextActionResult({
contentType: contentType.parse(response.headers['content-type'])
.type,
payload: response.data,
})
})
},
})
}
return result
}, [assetData.id])

const linkOptions = useMemo(() => {
if (!currentFolder) return []

return currentFolder.links.map((l) => ({
label: l.name,
disabled: !assetData[l['source_key']],
onClick: () => {
const query = `${l['target_key']}:${assetData[l['source_key']]}`
dispatch(setCurrentViewId(l.view))
dispatch(setSearchQuery(query))
},
}))
}, [currentFolder])

const assetActions = useMemo(() => {
const result = [
{
label: 'Send to...',
onClick: () => dispatch(showSendToDialog({ ids: [assetData.id] })),
},
...scopedEndpoints,
...linkOptions,
]
if (result.length > 1) {
result[1].separator = true
}
return result
}, [scopedEndpoints, linkOptions])

// End actions

const fps = useMemo(() => {
if (!assetData) return 25
return assetData['video/fps_f'] || 25
}, [assetData['video/fps_f']])

return (
<Navbar>
{detailsVisible && (
<Dialog
style={{ height: '80%', width: '80%' }}
onHide={() => setDetailsVisible(false)}
>
<MetadataDetail assetData={assetData} />
</Dialog>
)}

{contextActionResult && (
<ContextActionResult
mime={contextActionResult.contentType}
payload={contextActionResult.payload}
onHide={() => setContextActionResult(null)}
/>
)}

<Dropdown
options={folderOptions}
buttonStyle={{
borderLeft: ` 4px solid ${currentFolder?.color}`,
minWidth: 130,
width: 130,
}}
label={currentFolder?.name || 'no folder'}
disabled={!enabledActions.folderChange}
/>

<InputTimecode
value={assetData?.duration}
fps={fps}
onChange={(val) => setMeta('duration', val)}
tooltip="Asset duration"
readOnly={assetData.status || !enabledActions.edit}
/>

<ToolbarSeparator />

{enabledActions.advanced && (
<AssigneesButton
assignees={assetData?.assignees || []}
setAssignees={(val) => setMeta('assignees', val)}
/>
)}

<Spacer />

{enabledActions.advanced && (
<>
<Dropdown
options={assetActions}
disabled={!enabledActions.actions}
label="Actions"
/>
<Button
icon="manage_search"
label="Details"
onClick={() => setDetailsVisible(true)}
/>
</>
)}

{nebula.settings?.system?.ui_asset_upload && (
<UploadButton assetData={assetData} disabled={!enabledActions.upload} />
)}
</Navbar>
)
}

export default AssetEditorNav
2 changes: 1 addition & 1 deletion frontend/src/pages/AssetEditor/AssigneesButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const AssigneesButton = ({ assignees, setAssignees }) => {
)}
<Button
icon="person"
tooltip="Assignees"
label="Assignees"
onClick={() => setDialogVisible(true)}
active={assignees?.length > 0}
/>
Expand Down
Loading

0 comments on commit 2313bee

Please sign in to comment.