UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

283 lines (282 loc) 10.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { raceTimeout } from '@sussudio/base/common/async.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { FileAccess } from '@sussudio/base/common/network.mjs'; import { IAccessibilityService } from '../../accessibility/common/accessibility.mjs'; import { IConfigurationService } from '../../configuration/common/configuration.mjs'; import { createDecorator } from '../../instantiation/common/instantiation.mjs'; import { Event } from '@sussudio/base/common/event.mjs'; import { localize } from 'vscode-nls.mjs'; import { observableFromEvent, derived } from '@sussudio/base/common/observable.mjs'; export const IAudioCueService = createDecorator('audioCue'); let AudioCueService = class AudioCueService extends Disposable { configurationService; accessibilityService; _serviceBrand; screenReaderAttached = observableFromEvent(this.accessibilityService.onDidChangeScreenReaderOptimized, () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized(), ); constructor(configurationService, accessibilityService) { super(); this.configurationService = configurationService; this.accessibilityService = accessibilityService; } async playAudioCue(cue, allowManyInParallel = false) { if (this.isEnabled(cue)) { await this.playSound(cue.sound, allowManyInParallel); } } async playAudioCues(cues) { // Some audio cues might reuse sounds. Don't play the same sound twice. const sounds = new Set(cues.filter((cue) => this.isEnabled(cue)).map((cue) => cue.sound)); await Promise.all(Array.from(sounds).map((sound) => this.playSound(sound))); } getVolumeInPercent() { const volume = this.configurationService.getValue('audioCues.volume'); if (typeof volume !== 'number') { return 50; } return Math.max(Math.min(volume, 100), 0); } playingSounds = new Set(); async playSound(sound, allowManyInParallel = false) { if (!allowManyInParallel && this.playingSounds.has(sound)) { return; } this.playingSounds.add(sound); const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(); const audio = new Audio(url); audio.volume = this.getVolumeInPercent() / 100; audio.addEventListener('ended', () => { this.playingSounds.delete(sound); }); try { try { // Don't play when loading takes more than 1s, due to loading, decoding or playing issues. // Delayed sounds are very confusing. await raceTimeout(audio.play(), 1000); } catch (e) { console.error('Error while playing sound', e); } } finally { audio.remove(); } } obsoleteAudioCuesEnabled = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled'), ), () => /** @description config: audioCues.enabled */ this.configurationService.getValue('audioCues.enabled'), ); isEnabledCache = new Cache((cue) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration(cue.settingsKey)), () => this.configurationService.getValue(cue.settingsKey), ); return derived('audio cue enabled', (reader) => { const setting = settingObservable.read(reader); if (setting === 'on' || (setting === 'auto' && this.screenReaderAttached.read(reader))) { return true; } const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); if (obsoleteSetting === 'on' || (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader))) { return true; } return false; }); }); isEnabled(cue) { return this.isEnabledCache.get(cue).get(); } onEnabledChanged(cue) { return eventFromObservable(this.isEnabledCache.get(cue)); } }; AudioCueService = __decorate([__param(0, IConfigurationService), __param(1, IAccessibilityService)], AudioCueService); export { AudioCueService }; function eventFromObservable(observable) { return (listener) => { let count = 0; let didChange = false; const observer = { beginUpdate() { count++; }, endUpdate() { count--; if (count === 0 && didChange) { didChange = false; listener(); } }, handleChange() { if (count === 0) { listener(); } else { didChange = true; } }, }; observable.addObserver(observer); return { dispose() { observable.removeObserver(observer); }, }; }; } class Cache { getValue; map = new Map(); constructor(getValue) { this.getValue = getValue; } get(arg) { if (this.map.has(arg)) { return this.map.get(arg); } const value = this.getValue(arg); this.map.set(arg, value); return value; } } /** * Corresponds to the audio files in ./media. */ export class Sound { fileName; static register(options) { const sound = new Sound(options.fileName); return sound; } static error = Sound.register({ fileName: 'error.mp3' }); static warning = Sound.register({ fileName: 'warning.mp3' }); static foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); static break = Sound.register({ fileName: 'break.mp3' }); static quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); static taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); static taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); static terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); static diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); static diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); constructor(fileName) { this.fileName = fileName; } } export class AudioCue { sound; name; settingsKey; static _audioCues = new Set(); static register(options) { const audioCue = new AudioCue(options.sound, options.name, options.settingsKey); AudioCue._audioCues.add(audioCue); return audioCue; } static get allAudioCues() { return [...this._audioCues]; } static error = AudioCue.register({ name: localize('audioCues.lineHasError.name', 'Error on Line'), sound: Sound.error, settingsKey: 'audioCues.lineHasError', }); static warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', }); static foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', }); static break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', }); static inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', }); static terminalQuickFix = AudioCue.register({ name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', }); static onDebugBreak = AudioCue.register({ name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', }); static noInlayHints = AudioCue.register({ name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, settingsKey: 'audioCues.noInlayHints', }); static taskCompleted = AudioCue.register({ name: localize('audioCues.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', }); static taskFailed = AudioCue.register({ name: localize('audioCues.taskFailed', 'Task Failed'), sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', }); static terminalBell = AudioCue.register({ name: localize('audioCues.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', }); static notebookCellCompleted = AudioCue.register({ name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', }); static notebookCellFailed = AudioCue.register({ name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', }); static diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', }); static diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', }); constructor(sound, name, settingsKey) { this.sound = sound; this.name = name; this.settingsKey = settingsKey; } }