All files / submodels/copy update.ts

75% Statements 15/20
100% Branches 2/2
64.28% Functions 9/14
75% Lines 15/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89                  4x       5x                 4x 17x   5x 5x 5x           3x     2x     7x     7x   7x                           4x                             4x   4x                          
import { Data, Duration, Effect, HashMap, Option, Schema } from 'effect'
import { Command, type Update } from 'foldkit'
import { modifyFields } from 'foldkit/struct'
 
import { Message } from './message'
import { Feedback, type Model } from './model'
 
type UpdateReturn = Update.Return<Model, Message>
 
const showFeedback = (
  model: Model,
  cellId: string,
  feedback: Feedback,
): UpdateReturn => ({
  model: modifyFields(model, {
    feedbackByCellId: HashMap.set(cellId, feedback),
  }),
  commands: [WaitBeforeHidingCopyFeedback({ cellId, copyId: feedback.copyId })],
})
 
// UPDATE
 
export const update = (model: Model, message: Message) =>
  Message.match<UpdateReturn>(message, {
    ClickedCopy: ({ cellId, text }) => {
      const copyId = model.copyCount + 1
      return {
        model: modifyFields(model, { copyCount: () => copyId }),
        commands: [CopyText({ cellId, text, copyId })],
      }
    },
 
    SucceededCopyText: ({ cellId, copyId }) =>
      showFeedback(model, cellId, Feedback.Copied({ copyId })),
 
    FailedCopyText: ({ cellId, copyId }) =>
      showFeedback(model, cellId, Feedback.Failed({ copyId })),
 
    // NOTE: a newer copy of the same cell owns the feedback now, so an older wait leaves it alone.
    CompletedWaitBeforeHidingCopyFeedback: ({ cellId, copyId }) => ({
      model: modifyFields(model, {
        feedbackByCellId: feedbackByCellId =>
          Option.exists(
            HashMap.get(feedbackByCellId, cellId),
            feedback => feedback.copyId === copyId,
          )
            ? HashMap.remove(feedbackByCellId, cellId)
            : feedbackByCellId,
      }),
    }),
  })
 
// COMMAND
 
class ClipboardRefusedError extends Data.TaggedError('ClipboardRefusedError')<{
  readonly cause: unknown
}> {}
 
export const CopyText = Command.define('CopyText', {
  args: { cellId: Schema.String, text: Schema.String, copyId: Schema.Int },
  messages: [Message.SucceededCopyText, Message.FailedCopyText],
  execute: ({ cellId, text, copyId }) =>
    Effect.tryPromise({
      try: () => navigator.clipboard.writeText(text),
      catch: cause => new ClipboardRefusedError({ cause }),
    }).pipe(
      Effect.as(Message.SucceededCopyText({ cellId, copyId })),
      Effect.catch(() =>
        Effect.succeed(Message.FailedCopyText({ cellId, copyId })),
      ),
    ),
})
 
const COPY_FEEDBACK_DURATION = Duration.millis(1200)
 
export const WaitBeforeHidingCopyFeedback = Command.define(
  'WaitBeforeHidingCopyFeedback',
  {
    args: { cellId: Schema.String, copyId: Schema.Int },
    messages: [Message.CompletedWaitBeforeHidingCopyFeedback],
    execute: ({ cellId, copyId }) =>
      Effect.sleep(COPY_FEEDBACK_DURATION).pipe(
        Effect.as(
          Message.CompletedWaitBeforeHidingCopyFeedback({ cellId, copyId }),
        ),
      ),
  },
)