HunspellManager.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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. baseAssetPath: string
  32. dictionariesRoot: string
  33. hunspellWorker!: Worker
  34. abortController: AbortController | undefined
  35. listening = false
  36. loaded = false
  37. pendingMessages: Message[] = []
  38. callbacks: Map<string, (value: unknown) => void> = new Map()
  39. constructor(
  40. private readonly language: string,
  41. private readonly learnedWords: string[]
  42. ) {
  43. this.baseAssetPath = new URL(
  44. getMeta('ol-baseAssetPath'),
  45. window.location.href
  46. ).toString()
  47. this.dictionariesRoot = getMeta('ol-dictionariesRoot')
  48. createWorker(() => {
  49. this.hunspellWorker = new Worker(
  50. new URL('./hunspell.worker.ts', import.meta.url),
  51. { type: 'module' }
  52. )
  53. this.hunspellWorker.addEventListener('message', this.receive.bind(this))
  54. })
  55. }
  56. destroy() {
  57. this.send({ type: 'destroy' }, () => {
  58. this.hunspellWorker.terminate()
  59. })
  60. }
  61. send(message: Message, callback: (value: unknown) => void) {
  62. debugConsole.log(message)
  63. if (callback) {
  64. message.id = uuid()
  65. this.callbacks.set(message.id, callback)
  66. }
  67. if (this.listening) {
  68. this.hunspellWorker.postMessage(message)
  69. } else {
  70. this.pendingMessages.push(message)
  71. }
  72. }
  73. receive(event: MessageEvent) {
  74. debugConsole.log(event.data)
  75. const { id, listening, loaded, ...rest } = event.data
  76. if (id) {
  77. const callback = this.callbacks.get(id)
  78. if (callback) {
  79. this.callbacks.delete(id)
  80. callback(rest)
  81. }
  82. } else if (listening) {
  83. this.listening = true
  84. this.hunspellWorker.postMessage({
  85. type: 'init',
  86. lang: this.language,
  87. learnedWords: this.learnedWords, // TODO: add words
  88. baseAssetPath: this.baseAssetPath,
  89. dictionariesRoot: this.dictionariesRoot,
  90. })
  91. for (const message of this.pendingMessages) {
  92. this.hunspellWorker.postMessage(message)
  93. this.pendingMessages.length = 0
  94. }
  95. } else if (loaded) {
  96. this.loaded = true
  97. // TODO: use this to display pending state?
  98. }
  99. }
  100. }