HunspellManager.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { v4 as uuid } from 'uuid'
  2. import { createWorker } from '@/utils/worker'
  3. import getMeta from '@/utils/meta'
  4. import { debugConsole } from '@/utils/debugging'
  5. type Message =
  6. | {
  7. id?: string
  8. type: 'spell'
  9. words: string[]
  10. }
  11. | {
  12. id?: string
  13. type: 'suggest'
  14. word: string
  15. }
  16. | {
  17. id?: string
  18. type: 'add_word'
  19. word: string
  20. }
  21. | {
  22. id?: string
  23. type: 'remove_word'
  24. word: string
  25. }
  26. | {
  27. id?: string
  28. type: 'destroy'
  29. }
  30. export class HunspellManager {
  31. dictionariesRoot: string
  32. hunspellWorker!: Worker
  33. abortController: AbortController | undefined
  34. listening = false
  35. loaded = false
  36. pendingMessages: Message[] = []
  37. callbacks: Map<string, (value: unknown) => void> = new Map()
  38. constructor(
  39. private readonly language: string,
  40. private readonly learnedWords: string[]
  41. ) {
  42. const baseAssetPath = new URL(
  43. getMeta('ol-baseAssetPath'),
  44. window.location.href
  45. )
  46. this.dictionariesRoot = new URL(
  47. getMeta('ol-dictionariesRoot'),
  48. baseAssetPath
  49. ).toString()
  50. createWorker(() => {
  51. this.hunspellWorker = new Worker(
  52. new URL('./hunspell.worker.ts', import.meta.url),
  53. { type: 'module' }
  54. )
  55. this.hunspellWorker.addEventListener('message', this.receive.bind(this))
  56. })
  57. }
  58. destroy() {
  59. this.send({ type: 'destroy' }, () => {
  60. this.hunspellWorker.terminate()
  61. })
  62. }
  63. send(message: Message, callback: (value: unknown) => void) {
  64. debugConsole.log(message)
  65. if (callback) {
  66. message.id = uuid()
  67. this.callbacks.set(message.id, callback)
  68. }
  69. if (this.listening) {
  70. this.hunspellWorker.postMessage(message)
  71. } else {
  72. this.pendingMessages.push(message)
  73. }
  74. }
  75. receive(event: MessageEvent) {
  76. debugConsole.log(event.data)
  77. const { id, listening, loaded, ...rest } = event.data
  78. if (id) {
  79. const callback = this.callbacks.get(id)
  80. if (callback) {
  81. this.callbacks.delete(id)
  82. callback(rest)
  83. }
  84. } else if (listening) {
  85. this.listening = true
  86. this.hunspellWorker.postMessage({
  87. type: 'init',
  88. lang: this.language,
  89. learnedWords: this.learnedWords, // TODO: add words
  90. dictionariesRoot: this.dictionariesRoot,
  91. })
  92. for (const message of this.pendingMessages) {
  93. this.hunspellWorker.postMessage(message)
  94. this.pendingMessages.length = 0
  95. }
  96. } else if (loaded) {
  97. this.loaded = true
  98. // TODO: use this to display pending state?
  99. }
  100. }
  101. }