Skip to content
Snippets Groups Projects
Unverified Commit 7d1dfb67 authored by Eric Rosenbaum's avatar Eric Rosenbaum Committed by GitHub
Browse files

Merge pull request #4974 from ericrosenbaum/feature/sound-editor-selection4

Sound editor update: selection-based editing
parents 5b1670f5 6bbff14a
No related branches found
No related tags found
No related merge requests found
Showing
with 179 additions and 52 deletions
......@@ -24,7 +24,7 @@
display: flex;
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
overflow: visible;
}
[dir="ltr"] .detail-area {
......
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import SelectionHandle from './selection-handle.jsx';
import Playhead from './playhead.jsx';
const AudioSelector = props => (
<div
className={classNames(styles.absolute, styles.selector)}
ref={props.containerRef}
onMouseDown={props.onNewSelectionMouseDown}
onTouchStart={props.onNewSelectionMouseDown}
>
{props.trimStart === null ? null : (
<Box
className={classNames(styles.absolute)}
style={{
left: `${props.trimStart * 100}%`,
width: `${100 * (props.trimEnd - props.trimStart)}%`
}}
>
<Box className={classNames(styles.absolute, styles.selectionBackground)} />
<SelectionHandle
handleStyle={styles.leftHandle}
onMouseDown={props.onTrimStartMouseDown}
/>
<SelectionHandle
handleStyle={styles.rightHandle}
onMouseDown={props.onTrimEndMouseDown}
/>
</Box>
)}
{props.playhead ? (
<Playhead
playbackPosition={props.playhead}
/>
) : null}
</div>
);
AudioSelector.propTypes = {
containerRef: PropTypes.func,
onNewSelectionMouseDown: PropTypes.func.isRequired,
onTrimEndMouseDown: PropTypes.func.isRequired,
onTrimStartMouseDown: PropTypes.func.isRequired,
playhead: PropTypes.number,
trimEnd: PropTypes.number,
trimStart: PropTypes.number
};
export default AudioSelector;
@import "../../css/colors.css";
$border-radius: 4px;
$trim-handle-width: 12px;
$trim-handle-height: 14px;
$trim-handle-width: 30px;
$trim-handle-height: 30px;
$trim-handle-border: 3px;
$stripe-size: 10px;
$hover-scale: 2;
$hover-scale: 1.25;
.absolute {
position: absolute;
......@@ -17,6 +18,10 @@ $hover-scale: 2;
transform: translateZ(0);
}
.selector {
cursor: pointer;
}
.trim-background {
cursor: pointer;
touch-action: none;
......@@ -35,6 +40,11 @@ $hover-scale: 2;
);
}
.selection-background {
background: $motion-primary;
opacity: 0.5;
}
.start-trim-background .trim-background-mask {
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
......@@ -53,6 +63,10 @@ $hover-scale: 2;
border: 1px solid $red-tertiary;
}
.selector .trim-line {
border: 1px solid $motion-tertiary;
}
.playhead-container {
position: absolute;
top: 0;
......@@ -68,31 +82,50 @@ $hover-scale: 2;
so that we can use transform: translateX() using percentages.
*/
width: 100%;
height: 100%;
border-left: 1px solid $motion-primary;
border-top: none;
border-bottom: none;
border-right: none;
}
.start-trim-line {
right: 0;
.right-handle {
transform: scaleX(-1);
}
.end-trim-line {
left: 0;
.selector .left-handle {
left: -1px
}
.selector .right-handle {
right: -1px
}
.trimmer .left-handle {
right: -1px
}
.trimmer .right-handle {
left: -1px
}
.trim-handle {
position: absolute;
left: calc(-$trim-handle-width / 2);
width: $trim-handle-width;
height: $trim-handle-height;
right: 0;
user-select: none;
}
.trimmer .trim-handle {
filter: hue-rotate(150deg);
}
.trim-handle img {
position: absolute;
width: $trim-handle-width;
height: $trim-handle-height;
left: calc($trim-handle-border * 1.5);
/* Make sure image dragging isn't triggered */
user-select: none;
......@@ -103,22 +136,13 @@ $hover-scale: 2;
}
.top-trim-handle {
top: -$trim-handle-height;
top: calc(-$trim-handle-height + $trim-handle-border);
}
.bottom-trim-handle {
bottom: -$trim-handle-height;
bottom: calc(-$trim-handle-height + $trim-handle-border);
}
.top-trim-handle img {
transform: rotate(180deg);
}
/* Increase handle size when anywhere on draggable area is hovered */
.trim-background:hover img {
transform: scale($hover-scale);
}
.trim-background:hover .top-trim-handle img {
transform: rotate(180deg) scale($hover-scale);
transform: scaleY(-1);
}
......@@ -3,11 +3,12 @@ import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import handleIcon from './icon--handle.svg';
import SelectionHandle from './selection-handle.jsx';
import Playhead from './playhead.jsx';
const AudioTrimmer = props => (
<div
className={styles.absolute}
className={classNames(styles.absolute, styles.trimmer)}
ref={props.containerRef}
>
{props.trimStart === null ? null : (
......@@ -20,28 +21,16 @@ const AudioTrimmer = props => (
onTouchStart={props.onTrimStartMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.startTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
<SelectionHandle
handleStyle={styles.leftHandle}
/>
</Box>
)}
{props.playhead ? (
<div className={styles.playheadContainer}>
<div
className={classNames(styles.trimLine, styles.playhead)}
style={{
transform: `translateX(${100 * props.playhead}%)`
}}
/>
</div>
<Playhead
playbackPosition={props.playhead}
/>
) : null}
{props.trimEnd === null ? null : (
<Box
className={classNames(styles.absolute, styles.trimBackground, styles.endTrimBackground)}
......@@ -53,14 +42,9 @@ const AudioTrimmer = props => (
onTouchStart={props.onTrimEndMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.endTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
<SelectionHandle
handleStyle={styles.rightHandle}
/>
</Box>
)}
</div>
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import styles from './audio-trimmer.css';
const Playhead = props => (
<div className={styles.playheadContainer}>
<div
className={classNames(styles.playhead)}
style={{
transform: `translateX(${100 * props.playbackPosition}%)`
}}
/>
</div>
);
Playhead.propTypes = {
playbackPosition: PropTypes.number
};
export default Playhead;
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import handleIcon from './icon--handle.svg';
const SelectionHandle = props => (
<Box
className={classNames(styles.trimLine, props.handleStyle)}
onMouseDown={props.onMouseDown}
onTouchStart={props.onMouseDown}
>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
);
SelectionHandle.propTypes = {
handleStyle: PropTypes.string,
onMouseDown: PropTypes.func
};
export default SelectionHandle;
......@@ -8,6 +8,7 @@
font-size: 0.75rem;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: $motion-primary;
border-radius: 0.5rem;
}
.container + .container {
......@@ -16,4 +17,14 @@
.title {
margin-top: 0.5rem;
text-align: center;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
.container:active {
background-color: $motion-light-transparent;
}
......@@ -5,14 +5,19 @@ import styles from './icon-button.css';
const IconButton = ({
img,
disabled,
className,
title,
onClick
}) => (
<div
className={classNames(styles.container, className)}
className={classNames(
styles.container,
className,
disabled ? styles.disabled : null
)}
role="button"
onClick={onClick}
onClick={disabled ? null : onClick}
>
<img
className={styles.icon}
......@@ -27,6 +32,7 @@ const IconButton = ({
IconButton.propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool,
img: PropTypes.string,
onClick: PropTypes.func.isRequired,
title: PropTypes.node.isRequired
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment