figure-modal.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import {
  2. ChangeSet,
  3. Extension,
  4. StateEffect,
  5. StateField,
  6. } from '@codemirror/state'
  7. type NestedReadonly<T> = {
  8. readonly [P in keyof T]: NestedReadonly<T[P]>
  9. }
  10. type FigureDataProps = {
  11. from: number
  12. to: number
  13. caption: {
  14. from: number
  15. to: number
  16. } | null
  17. label: { from: number; to: number } | null
  18. width?: number
  19. unknownGraphicsArguments?: string
  20. graphicsCommandArguments: {
  21. from: number
  22. to: number
  23. } | null
  24. graphicsCommand: { from: number; to: number }
  25. file: {
  26. from: number
  27. to: number
  28. path: string
  29. }
  30. }
  31. function mapFromTo<T extends { from: number; to: number } | null>(
  32. position: T,
  33. changes: ChangeSet
  34. ) {
  35. if (!position) {
  36. return position
  37. }
  38. return {
  39. ...position,
  40. from: changes.mapPos(position.from),
  41. to: changes.mapPos(position.to),
  42. }
  43. }
  44. export class FigureData {
  45. // eslint-disable-next-line no-useless-constructor
  46. constructor(private props: NestedReadonly<FigureDataProps>) {}
  47. public get from() {
  48. return this.props.from
  49. }
  50. public get to() {
  51. return this.props.to
  52. }
  53. public get caption() {
  54. return this.props.caption
  55. }
  56. public get label() {
  57. return this.props.label
  58. }
  59. public get width() {
  60. return this.props.width
  61. }
  62. public get unknownGraphicsArguments() {
  63. return this.props.unknownGraphicsArguments
  64. }
  65. public get graphicsCommandArguments() {
  66. return this.props.graphicsCommandArguments
  67. }
  68. public get graphicsCommand() {
  69. return this.props.graphicsCommand
  70. }
  71. public get file() {
  72. return this.props.file
  73. }
  74. map(changes: ChangeSet): FigureData {
  75. return new FigureData({
  76. from: changes.mapPos(this.from),
  77. to: changes.mapPos(this.to),
  78. caption: mapFromTo(this.caption, changes),
  79. label: mapFromTo(this.label, changes),
  80. graphicsCommand: mapFromTo(this.graphicsCommand, changes),
  81. width: this.width,
  82. file: mapFromTo(this.file, changes),
  83. graphicsCommandArguments: mapFromTo(
  84. this.graphicsCommandArguments,
  85. changes
  86. ),
  87. unknownGraphicsArguments: this.unknownGraphicsArguments,
  88. })
  89. }
  90. }
  91. export const editFigureDataEffect = StateEffect.define<FigureData | null>()
  92. export const editFigureData = StateField.define<FigureData | null>({
  93. create: () => null,
  94. update: (current, transaction) => {
  95. let value: FigureData | null | undefined
  96. for (const effect of transaction.effects) {
  97. if (effect.is(editFigureDataEffect)) {
  98. value = effect.value
  99. }
  100. }
  101. // Allow setting to null
  102. if (value !== undefined) {
  103. return value
  104. }
  105. if (!current) {
  106. return current
  107. }
  108. return current.map(transaction.changes)
  109. },
  110. })
  111. export const figureModal = (): Extension => [editFigureData]