feat: combine alt/focal point into single "media edit" dialog (#1430)

* feat: combine alt/focal point into single "media edit" dialog

* resize text automatically
This commit is contained in:
Nolan Lawson 2019-08-24 19:28:12 -07:00 committed by GitHub
parent 7b32c71c93
commit 7f9195c2af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 298 additions and 119 deletions

View file

@ -7,12 +7,11 @@
aria-hidden="true" aria-hidden="true"
/> />
<div class="compose-media-buttons"> <div class="compose-media-buttons">
<button class="compose-media-button compose-media-focal-button {focalHidden ? 'compose-media-hidden' : ''}" <button class="compose-media-button compose-media-focal-button"
aria-hidden={focalHidden} aria-label="Edit"
aria-label="Change preview" title="Edit"
title="Change preview" on:click="onEdit()" >
on:click="onSetFocalPoint()" > <SvgIcon className="compose-media-button-svg" href="#fa-pencil" />
<SvgIcon className="compose-media-button-svg" href="#fa-crosshairs" />
</button> </button>
<button class="compose-media-button compose-media-delete-button" <button class="compose-media-button compose-media-delete-button"
aria-label="Delete" aria-label="Delete"
@ -27,6 +26,7 @@
placeholder="Description" placeholder="Description"
ref:textarea ref:textarea
bind:value=rawText bind:value=rawText
maxlength="420"
></textarea> ></textarea>
<label for="compose-media-input-{uuid}" class="sr-only"> <label for="compose-media-input-{uuid}" class="sr-only">
Describe for the visually impaired (image, video) or auditorily impaired (audio, video) Describe for the visually impaired (image, video) or auditorily impaired (audio, video)
@ -98,11 +98,6 @@
height: 18px; height: 18px;
} }
.compose-media-hidden {
visibility: hidden;
pointer-events: none;
}
.audio-preview { .audio-preview {
background: var(--audio-bg); background: var(--audio-bg);
} }
@ -130,10 +125,11 @@
import { ONE_TRANSPARENT_PIXEL } from '../../_static/media' import { ONE_TRANSPARENT_PIXEL } from '../../_static/media'
import { get } from '../../_utils/lodash-lite' import { get } from '../../_utils/lodash-lite'
import { coordsToPercent } from '../../_utils/coordsToPercent' import { coordsToPercent } from '../../_utils/coordsToPercent'
import { importMediaFocalPointDialog } from '../dialog/asyncDialogs' import { importShowMediaEditDialog } from '../dialog/asyncDialogs'
import { throttleTimer } from '../../_utils/throttleTimer' import { throttleTimer } from '../../_utils/throttleTimer'
const updateMediaInStore = throttleTimer(scheduleIdleTask) const updateMediaInStore = throttleTimer(scheduleIdleTask)
const resizeTextarea = process.browser && throttleTimer(requestAnimationFrame)
export default { export default {
oncreate () { oncreate () {
@ -165,8 +161,7 @@
return 'center center' return 'center center'
} }
return `${coordsToPercent(focusX)}% ${100 - coordsToPercent(focusY)}%` return `${coordsToPercent(focusX)}% ${100 - coordsToPercent(focusY)}%`
}, }
focalHidden: ({ type }) => type !== 'image'
}, },
store: () => store, store: () => store,
methods: { methods: {
@ -178,6 +173,7 @@
const text = get(media, [index, 'description'], '') const text = get(media, [index, 'description'], '')
if (rawText !== text) { if (rawText !== text) {
this.set({ rawText: text }) this.set({ rawText: text })
resizeTextarea(() => autosize.update(this.refs.textarea))
} }
const focusX = get(media, [index, 'focusX'], 0) const focusX = get(media, [index, 'focusX'], 0)
const focusY = get(media, [index, 'focusY'], 0) const focusY = get(media, [index, 'focusY'], 0)
@ -206,10 +202,10 @@
const { realm, index } = this.get() const { realm, index } = this.get()
deleteMedia(realm, index) deleteMedia(realm, index)
}, },
async onSetFocalPoint () { async onEdit () {
const { realm, index } = this.get() const { realm, index, type } = this.get()
const showMediaFocalPointDialog = await importMediaFocalPointDialog() const showMediaEditDialog = await importShowMediaEditDialog()
showMediaFocalPointDialog(realm, index) showMediaEditDialog(realm, index, type)
} }
}, },
components: { components: {

View file

@ -44,6 +44,6 @@ export const importShowReportDialog = () => import(
/* webpackChunkName: 'showReportDialog' */ './creators/showReportDialog' /* webpackChunkName: 'showReportDialog' */ './creators/showReportDialog'
).then(getDefault) ).then(getDefault)
export const importMediaFocalPointDialog = () => import( export const importShowMediaEditDialog = () => import(
/* webpackChunkName: 'mediaFocalPointDialog' */ './creators/mediaFocalPointDialog' /* webpackChunkName: 'showMediaEditDialog' */ './creators/showMediaEditDialog'
).then(getDefault) ).then(getDefault)

View file

@ -0,0 +1,104 @@
<div class="media-alt-editor">
<textarea
id="the-media-alt-input-{realm}-{index}"
class="media-alt-input"
placeholder="Description"
ref:textarea
bind:value=rawText
maxlength="420"
></textarea>
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
Describe for the visually impaired
</label>
</div>
<style>
.media-alt-editor {
display: flex;
}
.media-alt-input {
padding: 5px;
border: 1px solid var(--input-border);
min-height: 75px;
resize: none;
overflow: hidden;
word-wrap: break-word;
/* Text must be at least 16px or else iOS Safari zooms in */
font-size: 1.2em;
max-height: 70vh;
}
@media (max-height: 767px) {
.media-alt-input {
max-height: 40vh;
width: 100%;
}
}
</style>
<script>
import { requestPostAnimationFrame } from '../../../_utils/requestPostAnimationFrame'
import { mark, stop } from '../../../_utils/marks'
import { autosize } from '../../../_thirdparty/autosize/autosize'
import { observe } from 'svelte-extras'
import { throttleTimer } from '../../../_utils/throttleTimer'
import { get } from '../../../_utils/lodash-lite'
import { store } from '../../../_store/store'
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
export default {
oncreate () {
this.setupAutosize()
this.setupSyncFromStore()
this.setupSyncToStore()
},
ondestroy () {
this.teardownAutosize()
},
store: () => store,
data: () => ({
rawText: ''
}),
methods: {
observe,
setupSyncFromStore () {
this.observe('media', media => {
media = media || []
const { index, rawText } = this.get()
const text = get(media, [index, 'description'], '')
if (rawText !== text) {
this.set({ rawText: text })
}
})
},
setupSyncToStore () {
this.observe('rawText', rawText => {
updateRawTextInStore(() => {
const { realm, index, media } = this.get()
if (media[index].description !== rawText) {
media[index].description = rawText
this.store.setComposeData(realm, { media })
this.store.save()
}
this.fire('resize')
})
}, { init: false })
},
setupAutosize () {
const textarea = this.refs.textarea
requestPostAnimationFrame(() => {
mark('autosize()')
autosize(textarea)
stop('autosize()')
})
},
teardownAutosize () {
mark('autosize.destroy()')
autosize.destroy(this.refs.textarea)
stop('autosize.destroy()')
},
measure () {
autosize.update(this.refs.textarea)
}
}
}
</script>

View file

@ -0,0 +1,100 @@
<ModalDialog
{id}
{label}
{title}
background="var(--main-bg)"
className="media-edit-dialog"
on:show="measure()"
>
<div class="media-edit-dialog-container">
<div class="media-edit-header-and-item">
<h2>Description</h2>
<MediaAltEditor
{realm}
{index}
{media}
on:resize="measure()"
ref:altEditor
/>
</div>
{#if type === 'image'}
<div class="media-edit-header-and-item">
<h2>Preview (focal point)</h2>
<MediaFocalPointEditor
{realm}
{index}
{media}
ref:focalPointEditor
/>
</div>
{/if}
</div>
</ModalDialog>
<style>
:global(.media-edit-dialog) {
max-width: calc(100%);
}
.media-edit-dialog-container {
display: flex;
flex-direction: row;
max-height: calc(100% - 44px); /* 44px X button height */
height: 100%;
width: 100%;
}
.media-edit-header-and-item {
padding: 10px;
}
.media-edit-header-and-item h2 {
margin-bottom: 10px;
}
@media (max-width: 767px) {
.media-edit-dialog-container {
flex-direction: column;
overflow: auto;
}
.media-edit-dialog-container {
max-height: calc(100% - 25px); /* 25px X button height */
}
}
</style>
<script>
import ModalDialog from './ModalDialog.html'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
import MediaFocalPointEditor from '../components/MediaFocalPointEditor.html'
import MediaAltEditor from '../components/MediaAltEditor.html'
import { store } from '../../../_store/store'
import { get } from '../../../_utils/lodash-lite'
export default {
oncreate,
components: {
ModalDialog,
MediaFocalPointEditor,
MediaAltEditor
},
methods: {
show,
close,
measure () {
this.refs.altEditor.measure()
if (this.refs.focalPointEditor) {
this.refs.focalPointEditor.measure()
}
}
},
store: () => store,
computed: {
media: ({ $currentInstance, $composeData, realm }) => (
get($composeData, [$currentInstance, realm, 'media'])
)
}
}
</script>

View file

@ -1,86 +1,71 @@
<ModalDialog <form class="media-focal-point-container"
{id} aria-label="Enter the focal point (X, Y) for this media"
{label} on:resize="measure()"
{title}
background="var(--main-bg)"
className="media-focal-point-dialog"
on:show="measure()"
> >
<form class="media-focal-point-container" <div class="media-focal-point-image-container" ref:container>
aria-label="Enter the focal point (X, Y) for this media" <img
on:resize="measure()" {intrinsicsize}
> class="media-focal-point-image"
<div class="media-focal-point-image-container" ref:container> src={previewSrc}
<img alt={shortName}
{intrinsicsize} on:load="onImageLoad()"
class="media-focal-point-image" />
src={previewSrc} <div class="media-focal-point-backdrop"></div>
alt={shortName} <div class="media-draggable-area"
on:load="onImageLoad()" style={draggableAreaStyle}
/> >
<div class="media-focal-point-backdrop"></div> <!-- 52px == 32px icon width + 10px padding -->
<div class="media-draggable-area" <Draggable
style={draggableAreaStyle} draggableClass="media-draggable-area-inner"
indicatorClass="media-focal-point-indicator {imageLoaded ? '': 'hidden'} {dragging ? 'dragging' : ''}"
indicatorWidth={52}
indicatorHeight={52}
x={indicatorX}
y={indicatorY}
on:dragStart="onDragStart()"
on:dragEnd="onDragEnd()"
on:change="onDraggableChange(event)"
> >
<!-- 52px == 32px icon width + 10px padding --> <SvgIcon
<Draggable className="media-focal-point-indicator-svg"
draggableClass="media-draggable-area-inner" href="#fa-crosshairs"
indicatorClass="media-focal-point-indicator {imageLoaded ? '': 'hidden'} {dragging ? 'dragging' : ''}" />
indicatorWidth={52} </Draggable>
indicatorHeight={52}
x={indicatorX}
y={indicatorY}
on:dragStart="onDragStart()"
on:dragEnd="onDragEnd()"
on:change="onDraggableChange(event)"
>
<SvgIcon
className="media-focal-point-indicator-svg"
href="#fa-crosshairs"
/>
</Draggable>
</div>
</div> </div>
<div class="media-focal-point-inputs"> </div>
<div class="media-focal-point-input-pair"> <div class="media-focal-point-inputs">
<label for="media-focal-point-x-input-{realm}"> <div class="media-focal-point-input-pair">
X coordinate <label for="media-focal-point-x-input-{realm}">
</label> X coordinate
<input type="number" </label>
step="0.01" <input type="number"
min="-1" step="0.01"
max="1" min="-1"
inputmode="decimal" max="1"
placeholder="0" inputmode="decimal"
id="media-focal-point-x-input-{realm}" placeholder="0"
bind:value="rawFocusX" id="media-focal-point-x-input-{realm}"
/> bind:value="rawFocusX"
</div>
<div class="media-focal-point-input-pair">
<label for="media-focal-point-y-input-{realm}">
Y coordinate
</label>
<input type="number"
step="0.01"
min="-1"
max="1"
inputmode="decimal"
placeholder="0"
id="media-focal-point-y-input-{realm}"
bind:value="rawFocusY"
/> />
</div>
</div> </div>
</form> <div class="media-focal-point-input-pair">
</ModalDialog> <label for="media-focal-point-y-input-{realm}">
Y coordinate
</label>
<input type="number"
step="0.01"
min="-1"
max="1"
inputmode="decimal"
placeholder="0"
id="media-focal-point-y-input-{realm}"
bind:value="rawFocusY"
/>
</div>
</div>
</form>
<style> <style>
:global(.media-focal-point-dialog) {
max-width: calc(100%);
}
.media-focal-point-container { .media-focal-point-container {
height: calc(100% - 44px); /* 44px X button height */
width: calc(100vw - 40px);
padding-top: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -120,7 +105,7 @@
.media-focal-point-inputs { .media-focal-point-inputs {
display: flex; display: flex;
padding: 20px 40px; padding: 10px;
justify-content: space-around; justify-content: space-around;
width: auto; width: auto;
} }
@ -130,6 +115,10 @@
align-items: center; align-items: center;
} }
.media-focal-point-input-pair:first-child {
margin-right: 10px;
}
.media-focal-point-input-pair input { .media-focal-point-input-pair input {
margin-left: 20px; margin-left: 20px;
} }
@ -180,10 +169,6 @@
} }
</style> </style>
<script> <script>
import ModalDialog from './ModalDialog.html'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
import { store } from '../../../_store/store' import { store } from '../../../_store/store'
import { get } from '../../../_utils/lodash-lite' import { get } from '../../../_utils/lodash-lite'
import { observe } from 'svelte-extras' import { observe } from 'svelte-extras'
@ -207,12 +192,10 @@
export default { export default {
oncreate () { oncreate () {
onCreateDialog.call(this)
this.setupSyncFromStore() this.setupSyncFromStore()
this.setupSyncToStore() this.setupSyncToStore()
}, },
components: { components: {
ModalDialog,
SvgIcon, SvgIcon,
Draggable Draggable
}, },
@ -226,9 +209,6 @@
}), }),
store: () => store, store: () => store,
computed: { computed: {
media: ({ $currentInstance, $composeData, realm }) => (
get($composeData, [$currentInstance, realm, 'media'])
),
mediaItem: ({ media, index }) => get(media, [index]), mediaItem: ({ media, index }) => get(media, [index]),
focusX: ({ mediaItem }) => get(mediaItem, ['focusX'], 0), focusX: ({ mediaItem }) => get(mediaItem, ['focusX'], 0),
focusY: ({ mediaItem }) => get(mediaItem, ['focusY'], 0), focusY: ({ mediaItem }) => get(mediaItem, ['focusY'], 0),
@ -267,8 +247,6 @@
}, },
methods: { methods: {
observe, observe,
show,
close,
setupSyncFromStore () { setupSyncFromStore () {
this.observe('mediaItem', mediaItem => { this.observe('mediaItem', mediaItem => {
const { rawFocusX, rawFocusY } = this.get() const { rawFocusX, rawFocusY } = this.get()

View file

@ -1,11 +0,0 @@
import MediaFocalPointDialog from '../components/MediaFocalPointDialog.html'
import { showDialog } from './showDialog'
export default function showMediaFocalPointDialog (realm, index) {
return showDialog(MediaFocalPointDialog, {
label: 'Change preview dialog',
title: 'Change preview (focal point)',
realm,
index
})
}

View file

@ -0,0 +1,12 @@
import MediaFocalPointDialog from '../components/MediaEditDialog.html'
import { showDialog } from './showDialog'
export default function showMediaEditDialog (realm, index, type) {
return showDialog(MediaFocalPointDialog, {
label: 'Edit media',
title: 'Edit media',
realm,
index,
type
})
}