semaphore/src/routes/_components/dialog/components/MediaAltEditor.html

193 lines
5.1 KiB
HTML

<div class="media-alt-editor">
<textarea
id="the-media-alt-input-{realm}-{index}"
class="media-alt-input"
placeholder="Describe for the visually impaired"
ref:textarea
bind:value=rawText
></textarea>
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
Describe for the visually impaired
</label>
<LengthGauge
{length}
{overLimit}
max={mediaAltCharLimit}
/>
<LengthIndicator
{length}
{overLimit}
max={mediaAltCharLimit}
style="width: 100%; text-align: right;"
/>
<button class="extract-text-button" type="button"
on:click="onClick()"
disabled={extracting}
>
<SvgIcon href="{extracting ? '#fa-spinner' : '#fa-magic'}"
className="extract-text-svg {extracting ? 'spin' : ''}"
/>
<span>
{#if extracting}
Extracting text…
{:else}
Extract text from image
{/if}
</span>
</button>
</div>
<style>
.media-alt-editor {
display: flex;
flex-direction: column;
}
.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;
}
.extract-text-button {
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
}
.extract-text-button span {
margin-left: 15px;
}
:global(.extract-text-svg) {
fill: var(--button-text);
width: 18px;
height: 18px;
}
@media (max-height: 767px) {
.media-alt-input {
max-height: 40vh;
width: 100%;
}
}
@media (min-height: 768px) {
.media-alt-input {
min-width: 250px;
}
}
</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'
import { MEDIA_ALT_CHAR_LIMIT } from '../../../_static/media'
import LengthGauge from '../../LengthGauge.html'
import LengthIndicator from '../../LengthIndicator.html'
import { length } from 'stringz'
import { runTesseract } from '../../../_utils/runTesseract'
import SvgIcon from '../../SvgIcon.html'
import { toast } from '../../toast/toast'
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
export default {
oncreate () {
this.setupAutosize()
this.setupSyncFromStore()
this.setupSyncToStore()
},
ondestroy () {
this.teardownAutosize()
},
store: () => store,
data: () => ({
rawText: '',
mediaAltCharLimit: MEDIA_ALT_CHAR_LIMIT,
extracting: false
}),
computed: {
length: ({ rawText }) => length(rawText || ''),
overLimit: ({ mediaAltCharLimit, length }) => length > mediaAltCharLimit,
url: ({ media, index }) => get(media, [index, 'data', 'url'])
},
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)
},
async onClick () {
this.set({ extracting: true })
try {
const { url } = this.get()
const text = await runTesseract(url)
const { media, index, realm } = this.get()
if (media[index].description !== text) {
media[index].description = text
this.store.setComposeData(realm, { media })
this.store.save()
}
} catch (err) {
console.error(err)
/* no await */ toast.say(
'Unable to extract text. Ensure your instance supports cross-origin resource sharing (CORS) for images.'
)
} finally {
this.set({ extracting: false })
}
}
},
components: {
LengthGauge,
LengthIndicator,
SvgIcon
}
}
</script>