You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

clipboard.ts 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { getShapeUtils } from './shape-utils'
  2. import { Data, Shape } from 'types'
  3. import { getCommonBounds } from 'utils'
  4. import tld from 'utils/tld'
  5. import state from './state'
  6. class Clipboard {
  7. current: string
  8. fallback = false
  9. copy = (shapes: Shape[], onComplete?: () => void) => {
  10. this.current = JSON.stringify({ id: 'tldr', shapes })
  11. navigator.permissions.query({ name: 'clipboard-write' }).then((result) => {
  12. if (result.state == 'granted' || result.state == 'prompt') {
  13. navigator.clipboard.writeText(this.current).then(onComplete, () => {
  14. console.warn('Error, could not copy to clipboard. Fallback?')
  15. this.fallback = true
  16. })
  17. } else {
  18. this.fallback = true
  19. }
  20. })
  21. }
  22. paste = () => {
  23. navigator.clipboard
  24. .readText()
  25. .then(this.sendPastedTextToState, this.sendPastedTextToState)
  26. }
  27. sendPastedTextToState(text = this.current) {
  28. if (text === undefined) return
  29. try {
  30. const clipboardData = JSON.parse(text)
  31. state.send('PASTED_SHAPES_FROM_CLIPBOARD', {
  32. shapes: clipboardData.shapes,
  33. })
  34. } catch (e) {
  35. // The text wasn't valid JSON, or it wasn't ours, so paste it as a text object
  36. state.send('PASTED_TEXT_FROM_CLIPBOARD', { text })
  37. }
  38. }
  39. clear = () => {
  40. this.current = undefined
  41. }
  42. copySelectionToSvg(data: Data) {
  43. const shapes = tld.getSelectedShapes(data)
  44. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  45. shapes
  46. .sort((a, b) => a.childIndex - b.childIndex)
  47. .forEach((shape) => {
  48. const group = document.getElementById(shape.id + '-group')
  49. const node = document.getElementById(shape.id)
  50. const groupClone = group.cloneNode()
  51. groupClone.appendChild(node.cloneNode(true))
  52. svg.appendChild(groupClone)
  53. })
  54. const bounds = getCommonBounds(
  55. ...shapes.map((shape) => getShapeUtils(shape).getBounds(shape))
  56. )
  57. // No content
  58. if (!bounds) return
  59. const padding = 16
  60. // Resize the element to the bounding box
  61. svg.setAttribute(
  62. 'viewBox',
  63. [
  64. bounds.minX - padding,
  65. bounds.minY - padding,
  66. bounds.width + padding * 2,
  67. bounds.height + padding * 2,
  68. ].join(' ')
  69. )
  70. svg.setAttribute('width', String(bounds.width))
  71. svg.setAttribute('height', String(bounds.height))
  72. // Take a snapshot of the element
  73. const s = new XMLSerializer()
  74. const svgString = s.serializeToString(svg)
  75. // Copy to clipboard!
  76. try {
  77. navigator.clipboard.writeText(svgString)
  78. } catch (e) {
  79. this.copyStringToClipboard(svgString)
  80. }
  81. }
  82. copyStringToClipboard = (string: string) => {
  83. let result: boolean | null
  84. const textarea = document.createElement('textarea')
  85. textarea.setAttribute('position', 'fixed')
  86. textarea.setAttribute('top', '0')
  87. textarea.setAttribute('readonly', 'true')
  88. textarea.setAttribute('contenteditable', 'true')
  89. textarea.style.position = 'fixed'
  90. textarea.value = string
  91. document.body.appendChild(textarea)
  92. textarea.focus()
  93. textarea.select()
  94. try {
  95. const range = document.createRange()
  96. range.selectNodeContents(textarea)
  97. const sel = window.getSelection()
  98. sel.removeAllRanges()
  99. sel.addRange(range)
  100. textarea.setSelectionRange(0, textarea.value.length)
  101. result = document.execCommand('copy')
  102. } catch (err) {
  103. result = null
  104. } finally {
  105. document.body.removeChild(textarea)
  106. }
  107. if (!result) return false
  108. return true
  109. }
  110. }
  111. export default new Clipboard()