Safari Focus Bug
Opened this issue · 0 comments
indigofa commented
I'm having a bug if I insert image in the first block video screenshare , if the image is inserted at the first block the focus is at the body and If i continue to type it crashed the browser.
This is the code I have
import React from "react"
import { EditorBlock, EditorState } from "draft-js"
import axios from "axios"
import { updateDataOfBlock, addNewBlockAt } from "./model/index.js"
import { image } from "./icons"
export default class ImageBlock extends React.Component {
constructor(props) {
super(props)
let existing_data = this.props.block.getData().toJS()
this.image_tag = null
this.config = this.props.blockProps.config
this.file = this.props.blockProps.data.get("file")
this.state = {
loading: false,
selected: false,
loading_progress: 0,
caption: this.defaultPlaceholder(),
direction: existing_data.direction || "center",
width: 0,
height: 0,
file: null,
url: this.blockPropsSrc() || this.defaultUrl(existing_data),
aspect_ratio: this.defaultAspectRatio(existing_data),
}
}
componentDidMount() {
this.figCaptionNode.focus()
return this.replaceImg()
}
componentWillUnmount() {
//debugger
}
blockPropsSrc = () => {
return this.props.blockProps.data.src
}
defaultUrl = data => {
if (data.url) {
return data.url
}
if (data.url) {
if (data.file) {
return URL.createObjectURL(data.file)
} else {
return data.url
}
} else {
return this.props.blockProps.data.src
}
}
defaultPlaceholder = () => {
return this.props.blockProps.config.image_caption_placeholder
}
defaultAspectRatio = data => {
if (data.aspect_ratio) {
return {
width: data.aspect_ratio["width"],
height: data.aspect_ratio["height"],
ratio: data.aspect_ratio["ratio"],
}
} else {
return {
width: 0,
height: 0,
ratio: 100,
}
}
}
getAspectRatio = (w, h) => {
let maxWidth = 1000
let maxHeight = 1000
let ratio = 0
let width = w // Current image width
let height = h // Current image height
// Check if the current width is larger than the max
if (width > maxWidth) {
ratio = maxWidth / width // get ratio for scaling image
height = height * ratio // Reset height to match scaled image
width = width * ratio // Reset width to match scaled image
// Check if current height is larger than max
} else if (height > maxHeight) {
ratio = maxHeight / height // get ratio for scaling image
width = width * ratio // Reset width to match scaled image
height = height * ratio // Reset height to match scaled image
}
let fill_ratio = (height / width) * 100
let result = { width, height, ratio: fill_ratio }
// console.log result
return result
}
// will update block state
updateData = () => {
let { blockProps, block } = this.props
let { getEditorState } = blockProps
let { setEditorState } = blockProps
let data = block.getData()
let newData = data.merge(this.state).merge({ forceUpload: false })
return setEditorState(updateDataOfBlock(getEditorState(), block, newData))
}
replaceImg = () => {
this.img = new Image()
this.img.src = this.image_tag.src
this.setState({
url: this.img.src,
})
let self = this
// exit only when not blob and not forceUload
if (
!this.img.src.includes("blob:") &&
!this.props.block.data.get("forceUpload")
) {
return
}
return (this.img.onload = () => {
this.setState({
width: this.img.width,
height: this.img.height,
aspect_ratio: self.getAspectRatio(this.img.width, this.img.height),
})
return this.handleUpload()
})
}
startLoader = () => {
return this.setState({
loading: true,
})
}
stopLoader = () => {
return this.setState({
loading: false,
})
}
handleUpload = () => {
this.startLoader()
this.updateData()
return this.uploadFile()
}
aspectRatio = () => {
return {
maxWidth: `${this.state.aspect_ratio.width}`,
maxHeight: `${this.state.aspect_ratio.height}`,
ratio: `${this.state.aspect_ratio.height}`,
}
}
updateDataSelection = () => {
const { getEditorState, setEditorState } = this.props.blockProps
const newselection = getEditorState()
.getSelection()
.merge({
anchorKey: this.props.block.getKey(),
focusKey: this.props.block.getKey(),
})
return setEditorState(
EditorState.forceSelection(getEditorState(), newselection)
)
}
handleGrafFigureSelectImg = e => {
e.preventDefault()
return this.setState({ selected: true }, this.updateDataSelection)
}
//main_editor.onChange(main_editor.state.editorState)
coords = () => {
return {
maxWidth: `${this.state.aspect_ratio.width}px`,
maxHeight: `${this.state.aspect_ratio.height}px`,
}
}
getBase64Image = img => {
let canvas = document.createElement("canvas")
canvas.width = img.width
canvas.height = img.height
let ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0)
let dataURL = canvas.toDataURL("image/png")
return dataURL
}
formatData = () => {
let formData = new FormData()
if (this.file) {
let formName = this.config.upload_formName || "file"
formData.append(formName, this.file)
return formData
} else {
formData.append("url", this.props.blockProps.data.get("url"))
return formData
}
}
getUploadUrl = () => {
let url = this.config.upload_url
if (typeof url === "function") {
return url()
} else {
return url
}
}
getUploadHeaders() {
return this.config.upload_headers || {}
}
uploadFile = () => {
// custom upload handler
if (this.config.upload_handler) {
return this.config.upload_handler(this.formatData().get("file"), this)
}
if (!this.config.upload_url) {
this.stopLoader()
return
}
this.props.blockProps.addLock()
axios({
method: "post",
url: this.getUploadUrl(),
headers: this.getUploadHeaders(),
data: this.formatData(),
onUploadProgress: e => {
return this.updateProgressBar(e)
},
})
.then(result => {
this.uploadCompleted(result.data.url)
if (this.config.upload_callback) {
return this.config.upload_callback(result, this)
}
})
.catch(error => {
this.uploadFailed()
console.log(`ERROR: got error uploading file ${error}`)
if (this.config.upload_error_callback) {
return this.config.upload_error_callback(error, this)
}
})
return json_response => {
return this.uploadCompleted(json_response.url)
}
}
uploadFailed = () => {
this.props.blockProps.removeLock()
this.stopLoader()
}
uploadCompleted(url) {
this.setState({ url }, this.updateData)
this.props.blockProps.removeLock()
this.stopLoader()
this.file = null
}
updateProgressBar(e) {
let complete = this.state.loading_progress
if (e.lengthComputable) {
complete = (e.loaded / e.total) * 100
complete = complete != null ? complete : { complete: 0 }
this.setState({
loading_progress: complete,
})
return console.log(`complete: ${complete}`)
}
}
placeHolderEnabled = () => {
return this.state.enabled || this.props.block.getText()
}
placeholderText = () => {
return this.config.image_caption_placeholder || "caption here (optional)"
}
handleFocus(e) {}
render = () => {
return (
<figure ref="image_tag2" suppressContentEditableWarning={true}>
<div
role="button"
tabIndex="0"
className="aspectRatioPlaceholder is-locked"
style={this.coords()}
onClick={this.handleGrafFigureSelectImg}
onKeyDown={this.handleGrafFigureSelectImg}
>
<div
style={{ paddingBottom: `${this.state.aspect_ratio.ratio}%` }}
className="aspect-ratio-fill"
/>
<img
src={this.state.url}
ref={ref => (this.image_tag = ref)}
height={this.state.aspect_ratio.height}
width={this.state.aspect_ratio.width}
className="graf-image"
contentEditable={false}
alt={this.state.url}
/>
<Loader
toggle={this.state.loading}
progress={this.state.loading_progress}
/>
</div>
<figcaption className="imageCaption">
{this.props.block.getText().length === 0 ? (
<span
className="danteDefaultPlaceholder"
ref={node => (this.figCaptionNode = node)}
>
{this.placeholderText()}
</span>
) : (
undefined
)}
<EditorBlock
{...Object.assign({}, this.props, {
editable: true,
className: "imageCaption",
})}
/>
</figcaption>
</figure>
)
}
}
class Loader extends React.Component {
render = () => {
return (
<div>
{this.props.toggle ? (
<div className="image-upoader-loader">
<p>
{this.props.progress === 100 ? (
"processing image..."
) : (
<span>
<span>loading</span>
</span>
)}
</p>
</div>
) : (
undefined
)}
</div>
)
}
}
export const ImageBlockConfig = (options = {}) => {
let config = {
title: "add an image",
type: "image",
icon: image,
block: ImageBlock,
editable: true,
renderable: true,
breakOnContinuous: true,
wrapper_class: "graf graf--figure",
selected_class: "is-selected is-mediaFocused",
selectedFn: block => {
const { direction } = block.getData().toJS()
switch (direction) {
case "left":
return "graf--layoutOutsetLeft"
case "center":
return ""
case "wide":
return "sectionLayout--fullWidth"
case "fill":
return "graf--layoutFillWidth"
default:
return ""
}
},
handleEnterWithoutText(ctx, block) {
const { editorState } = ctx.state
return ctx.onChange(addNewBlockAt(editorState, block.getKey()))
},
handleEnterWithText(ctx, block) {
const { editorState } = ctx.state
return ctx.onChange(addNewBlockAt(editorState, block.getKey()))
},
widget_options: {
displayOnInlineTooltip: true,
insertion: "upload",
insert_block: "image",
},
options: {
upload_url: "",
upload_headers: null,
upload_formName: "file",
upload_callback: null,
upload_error_callback: null,
delete_block_callback: null,
image_caption_placeholder: "type a caption (optional)",
},
}
return Object.assign(config, options)
}
React Component
// Uploader fn used in ImageBlockConfig upload_handler: handleUploadContentMediaHander
const handleUploadContentMediaHander = (file, imageBlock) => {
setLoadingContentMedia(true)
imageBlock.startLoader()
const ext = file.name.substr(file.name.lastIndexOf(".") + 1)
const filename = `${uuidv4()}.${ext}`
const getSignedUrl = async cb => {
const response = await Api.getImageUploadUrl(getToken(), [
{ key: filename },
])
cb(null, response)
}
const uploadMedia = async (storage, cb) => {
try {
await fetch(storage[0].url, {
method: "PUT",
body: file,
})
setLoadingContentMedia(false)
imageBlock.stopLoader()
cb(null, storage)
} catch (err) {
setLoadingContentMedia(false)
imageBlock.stopLoader()
cb(err)
}
}
<Dante
content={editorState.current}
body_placeholder="Body"
widgets={[
ImageBlockConfig({
options: {
upload_handler: handleUploadContentMediaHander,
},
})
]}
default_wrappers={[
{ className: "h1-level-1", block: "header-one" },
]}
onChange={editor => {
const html = convertToHTML({
styleToHTML: style => {
if (style.startsWith("CUSTOM_COLOR_")) {
return (
<span
style={{
color: style.substr(style.length - 7),
}}
/>
)
}
},
entityToHTML: (entity, originalText) => {
if (entity.type === "LINK") {
return (
<a href={entity.data.url}>{originalText}</a>
)
}
return originalText
},
blockToHTML: block => {
if (block.type === "image") {
return (
<img
src={block.data.url}
alt={block.text}
data-previewsource={block.data.prevUrl}
/>
)
}
if (block.type === "code-block") {
return (
<code>
<pre>{block.text}</pre>
</code>
)
}
},
})(editor.state.editorState._immutable.currentContent)
}}
/>