HunspellManager.ts 2.5 KB

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