141 lines
3.7 KiB
TypeScript
141 lines
3.7 KiB
TypeScript
import { getShapeUtils } from './shape-utils'
|
|
import { Data, Shape } from 'types'
|
|
import { getCommonBounds } from 'utils'
|
|
import tld from 'utils/tld'
|
|
import state from './state'
|
|
|
|
class Clipboard {
|
|
current: string
|
|
fallback = false
|
|
|
|
copy = (shapes: Shape[], onComplete?: () => void) => {
|
|
this.current = JSON.stringify({ id: 'tldr', shapes })
|
|
|
|
navigator.permissions.query({ name: 'clipboard-write' }).then((result) => {
|
|
if (result.state == 'granted' || result.state == 'prompt') {
|
|
navigator.clipboard.writeText(this.current).then(onComplete, () => {
|
|
console.warn('Error, could not copy to clipboard. Fallback?')
|
|
this.fallback = true
|
|
})
|
|
} else {
|
|
this.fallback = true
|
|
}
|
|
})
|
|
}
|
|
|
|
paste = () => {
|
|
navigator.clipboard
|
|
.readText()
|
|
.then(this.sendPastedTextToState, this.sendPastedTextToState)
|
|
}
|
|
|
|
sendPastedTextToState(text = this.current) {
|
|
if (text === undefined) return
|
|
|
|
try {
|
|
const clipboardData = JSON.parse(text)
|
|
state.send('PASTED_SHAPES_FROM_CLIPBOARD', {
|
|
shapes: clipboardData.shapes,
|
|
})
|
|
} catch (e) {
|
|
// The text wasn't valid JSON, or it wasn't ours, so paste it as a text object
|
|
state.send('PASTED_TEXT_FROM_CLIPBOARD', { text })
|
|
}
|
|
}
|
|
|
|
clear = () => {
|
|
this.current = undefined
|
|
}
|
|
|
|
copySelectionToSvg(data: Data) {
|
|
const shapes = tld.getSelectedShapes(data)
|
|
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
|
|
shapes
|
|
.sort((a, b) => a.childIndex - b.childIndex)
|
|
.forEach((shape) => {
|
|
const group = document.getElementById(shape.id + '-group')
|
|
const node = document.getElementById(shape.id)
|
|
|
|
const groupClone = group.cloneNode()
|
|
groupClone.appendChild(node.cloneNode(true))
|
|
|
|
svg.appendChild(groupClone)
|
|
})
|
|
|
|
const bounds = getCommonBounds(
|
|
...shapes.map((shape) => getShapeUtils(shape).getBounds(shape))
|
|
)
|
|
|
|
// No content
|
|
if (!bounds) return
|
|
|
|
const padding = 16
|
|
|
|
// Resize the element to the bounding box
|
|
svg.setAttribute(
|
|
'viewBox',
|
|
[
|
|
bounds.minX - padding,
|
|
bounds.minY - padding,
|
|
bounds.width + padding * 2,
|
|
bounds.height + padding * 2,
|
|
].join(' ')
|
|
)
|
|
|
|
svg.setAttribute('width', String(bounds.width))
|
|
svg.setAttribute('height', String(bounds.height))
|
|
|
|
// Take a snapshot of the element
|
|
const s = new XMLSerializer()
|
|
const svgString = s
|
|
.serializeToString(svg)
|
|
.replaceAll(' ', '')
|
|
.replaceAll(/((\s|")[0-9]*\.[0-9]{2})([0-9]*)(\b|"|\))/g, '$1')
|
|
|
|
// Copy to clipboard!
|
|
try {
|
|
navigator.clipboard.writeText(svgString)
|
|
} catch (e) {
|
|
this.copyStringToClipboard(svgString)
|
|
}
|
|
}
|
|
|
|
copyStringToClipboard = (string: string) => {
|
|
let result: boolean | null
|
|
|
|
const textarea = document.createElement('textarea')
|
|
textarea.setAttribute('position', 'fixed')
|
|
textarea.setAttribute('top', '0')
|
|
textarea.setAttribute('readonly', 'true')
|
|
textarea.setAttribute('contenteditable', 'true')
|
|
textarea.style.position = 'fixed'
|
|
textarea.value = string
|
|
document.body.appendChild(textarea)
|
|
textarea.focus()
|
|
textarea.select()
|
|
|
|
try {
|
|
const range = document.createRange()
|
|
range.selectNodeContents(textarea)
|
|
|
|
const sel = window.getSelection()
|
|
sel.removeAllRanges()
|
|
sel.addRange(range)
|
|
|
|
textarea.setSelectionRange(0, textarea.value.length)
|
|
result = document.execCommand('copy')
|
|
} catch (err) {
|
|
result = null
|
|
} finally {
|
|
document.body.removeChild(textarea)
|
|
}
|
|
|
|
if (!result) return false
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
export default new Clipboard()
|