192 lines · 6.8 KB · TypeScript
RawDownload
import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema"; import * as Electron from "electron"; export class ElectronDialogPickFolderError extends Schema.TaggedError<ElectronDialogPickFolderError>()(  "ElectronDialogPickFolderError",  {    ownerWindowId: Schema.NullOr(Schema.Number),    defaultPath: Schema.NullOr(Schema.String),    cause: Schema.Defect(),  },) {  override get message(): string {    const owner = this.ownerWindowId === null ? "the application" : `window ${this.ownerWindowId}`;    const defaultPath = this.defaultPath === null ? "no default path" : this.defaultPath;    return `Failed to open the Electron folder picker for ${owner} with ${defaultPath}.`;  }} export class ElectronDialogPickFilesError extends Schema.TaggedError<ElectronDialogPickFilesError>()(  "ElectronDialogPickFilesError",  {    ownerWindowId: Schema.NullOr(Schema.Number),    defaultPath: Schema.NullOr(Schema.String),    cause: Schema.Defect(),  },) {  override get message(): string {    const owner = this.ownerWindowId === null ? "the application" : `window ${this.ownerWindowId}`;    const defaultPath = this.defaultPath === null ? "no default path" : this.defaultPath;    return `Failed to open the Electron file picker for ${owner} with ${defaultPath}.`;  }} export class ElectronDialogShowMessageBoxError extends Schema.TaggedError<ElectronDialogShowMessageBoxError>()(  "ElectronDialogShowMessageBoxError",  {    type: Schema.NullOr(Schema.Literals(["none", "info", "error", "question", "warning"])),    titleLength: Schema.NullOr(Schema.Number),    messageLength: Schema.Number,    detailLength: Schema.NullOr(Schema.Number),    buttonCount: Schema.Number,    cause: Schema.Defect(),  },) {  override get message(): string {    const type = this.type === null ? "untyped" : this.type;    return `Failed to show the Electron ${type} message box with ${this.buttonCount} buttons.`;  }} export class ElectronDialogShowErrorBoxError extends Schema.TaggedError<ElectronDialogShowErrorBoxError>()(  "ElectronDialogShowErrorBoxError",  {    titleLength: Schema.Number,    contentLength: Schema.Number,    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to show the Electron error box with a ${this.titleLength}-character title and ${this.contentLength}-character content.`;  }} export const ElectronDialogError = Schema.Union([  ElectronDialogPickFolderError,  ElectronDialogPickFilesError,  ElectronDialogShowMessageBoxError,  ElectronDialogShowErrorBoxError,]);export type ElectronDialogError = typeof ElectronDialogError.Type; export interface ElectronDialogPickFolderInput {  readonly owner: Option.Option<Electron.BrowserWindow>;  readonly defaultPath: Option.Option<string>;} export interface ElectronDialogPickFilesInput {  readonly owner: Option.Option<Electron.BrowserWindow>;  readonly defaultPath: Option.Option<string>;  readonly filters: readonly Electron.FileFilter[];  readonly multiple: boolean;} export class ElectronDialog extends Context.Service<  ElectronDialog,  {    readonly pickFolder: (      input: ElectronDialogPickFolderInput,    ) => Effect.Effect<Option.Option<string>, ElectronDialogPickFolderError>;    readonly pickFiles: (      input: ElectronDialogPickFilesInput,    ) => Effect.Effect<readonly string[], ElectronDialogPickFilesError>;    readonly showMessageBox: (      options: Electron.MessageBoxOptions,    ) => Effect.Effect<Electron.MessageBoxReturnValue, ElectronDialogShowMessageBoxError>;    readonly showErrorBox: (title: string, content: string) => Effect.Effect<void>;  }>()("@t3tools/desktop/electron/ElectronDialog") {} /** @public Service construction is part of the canonical Effect module API. */export const make = ElectronDialog.of({  pickFolder: Effect.fn("desktop.electron.dialog.pickFolder")(function* (input) {    const ownerWindowId = Option.match(input.owner, {      onNone: () => null,      onSome: (owner) => owner.id,    });    const defaultPath = Option.getOrNull(input.defaultPath);    const openDialogOptions: Electron.OpenDialogOptions = Option.match(input.defaultPath, {      onNone: () => ({        properties: ["openDirectory", "createDirectory"],      }),      onSome: (defaultPath) => ({        properties: ["openDirectory", "createDirectory"],        defaultPath,      }),    });    const result = yield* Effect.tryPromise({      try: () =>        Option.match(input.owner, {          onNone: () => Electron.dialog.showOpenDialog(openDialogOptions),          onSome: (owner) => Electron.dialog.showOpenDialog(owner, openDialogOptions),        }),      catch: (cause) =>        new ElectronDialogPickFolderError({          ownerWindowId,          defaultPath,          cause,        }),    });     if (result.canceled) {      return Option.none();    }    return Option.fromNullishOr(result.filePaths[0]);  }),  pickFiles: Effect.fn("desktop.electron.dialog.pickFiles")(function* (input) {    const ownerWindowId = Option.match(input.owner, {      onNone: () => null,      onSome: (owner) => owner.id,    });    const defaultPath = Option.getOrNull(input.defaultPath);    const openDialogOptions: Electron.OpenDialogOptions = {      properties: input.multiple ? ["openFile", "multiSelections"] : ["openFile"],      filters: [...input.filters],      ...(defaultPath === null ? {} : { defaultPath }),    };    const result = yield* Effect.tryPromise({      try: () =>        Option.match(input.owner, {          onNone: () => Electron.dialog.showOpenDialog(openDialogOptions),          onSome: (owner) => Electron.dialog.showOpenDialog(owner, openDialogOptions),        }),      catch: (cause) =>        new ElectronDialogPickFilesError({          ownerWindowId,          defaultPath,          cause,        }),    });    return result.canceled ? [] : result.filePaths;  }),  showMessageBox: (options) =>    Effect.tryPromise({      try: () => Electron.dialog.showMessageBox(options),      catch: (cause) =>        new ElectronDialogShowMessageBoxError({          type: options.type ?? null,          titleLength: options.title?.length ?? null,          messageLength: options.message.length,          detailLength: options.detail?.length ?? null,          buttonCount: options.buttons?.length ?? 0,          cause,        }),    }),  showErrorBox: (title, content) =>    Effect.try({      try: () => Electron.dialog.showErrorBox(title, content),      catch: (cause) =>        new ElectronDialogShowErrorBoxError({          titleLength: title.length,          contentLength: content.length,          cause,        }),    }).pipe(Effect.orDie),}); export const layer = Layer.succeed(ElectronDialog, make);