[gnome-sound-recorder/wip/cdavis/typescript: 3/8] general: Enable strict type mode




commit 358892519c2b73b892dc1b34e2bbe9d689f918e5
Author: Christopher Davis <christopherdavis gnome org>
Date:   Mon Aug 8 23:11:38 2022 -0400

    general: Enable strict type mode
    
    Enables strict type checking on our TypeScript code.

 package.json               |   2 +-
 src/application.ts         |  19 ++++---
 src/recorder.ts            | 132 +++++++++++++++++++++++++--------------------
 src/recorderWidget.ts      |   9 ++--
 src/recording.ts           |  45 ++++++++--------
 src/recordingList.ts       | 117 +++++++++++++++++++++-------------------
 src/recordingListWidget.ts |  14 +++--
 src/row.ts                 |  32 +++++------
 src/waveform.ts            |  39 ++++++++------
 src/window.ts              |  21 +++++---
 types/ambient.d.ts         |   3 ++
 11 files changed, 243 insertions(+), 190 deletions(-)
---
diff --git a/package.json b/package.json
index c44bbed..a91a4b4 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "@gi.ts/cli": "^1.5.5"
   },
   "scripts": {
-    "typecheck": "tsc",
+    "typecheck": "tsc --strict",
     "generate-types": "gi-ts generate",
     "config": "gi-ts config"
   }
diff --git a/src/application.ts b/src/application.ts
index 99a05cd..85269f8 100644
--- a/src/application.ts
+++ b/src/application.ts
@@ -26,7 +26,6 @@ import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
 import Gst from 'gi://Gst';
 import Gtk from 'gi://Gtk?version=4.0';
-import { AboutWindow } from 'types/adw.js';
 
 export const RecordingsDir = Gio.file_new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), 
pkg.name]));
 export const CacheDir = Gio.file_new_for_path(GLib.build_filenamev([GLib.get_user_cache_dir(), pkg.name]));
@@ -35,10 +34,10 @@ export const Settings = new Gio.Settings({ schema: pkg.name });
 import { Window, WindowClass } from './window.js';
 
 export const Application = GObject.registerClass(class Application extends Adw.Application {
-    private window: WindowClass;
+    private window?: WindowClass;
 
-    _init(): void {
-        super._init({ application_id: pkg.name, resource_base_path: '/org/gnome/SoundRecorder/' });
+    constructor() {
+        super({ application_id: pkg.name, resource_base_path: '/org/gnome/SoundRecorder/' });
         GLib.set_application_name(_('Sound Recorder'));
         GLib.setenv('PULSE_PROP_media.role', 'production', true);
         GLib.setenv('PULSE_PROP_application.icon_name', pkg.name, true);
@@ -71,7 +70,9 @@ export const Application = GObject.registerClass(class Application extends Adw.A
 
         let quitAction = new Gio.SimpleAction({ name: 'quit' });
         quitAction.connect('activate', () => {
-            this.get_active_window().close();
+            if (this.window) {
+                this.window.close();
+            }
         });
         this.add_action(quitAction);
 
@@ -103,7 +104,7 @@ export const Application = GObject.registerClass(class Application extends Adw.A
         try {
             CacheDir.make_directory_with_parents(null);
             RecordingsDir.make_directory_with_parents(null);
-        } catch (e) {
+        } catch (e: any) {
             if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                 console.error(`Failed to create directory ${e}`);
 
@@ -119,6 +120,10 @@ export const Application = GObject.registerClass(class Application extends Adw.A
     }
 
     _showAbout(): void {
+        let appName = GLib.get_application_name();
+        if (!appName)
+            appName = _('Sound Recorder');
+
         let aboutDialog = new Adw.AboutWindow({
             artists: [
                 'Reda Lazri <the red shortcut gmail com>',
@@ -135,7 +140,7 @@ export const Application = GObject.registerClass(class Application extends Adw.A
             ],
             /* Translators: Replace "translator-credits" with your names, one name per line */
             translator_credits: _('translator-credits'),
-            application_name: GLib.get_application_name(),
+            application_name: appName,
             comments: _('A Sound Recording Application for GNOME'),
             license_type: Gtk.License.GPL_2_0,
             application_icon: pkg.name,
diff --git a/src/recorder.ts b/src/recorder.ts
index 84d17e0..22ac8bc 100644
--- a/src/recorder.ts
+++ b/src/recorder.ts
@@ -60,10 +60,10 @@ export const EncodingProfiles = [
         extension: 'm4a' },
 ];
 
-var AudioChannels = {
-    0: { name: 'stereo', channels: 2 },
-    1: { name: 'mono', channels: 1 },
-};
+var AudioChannels = [
+    { name: 'stereo', channels: 2 },
+    { name: 'mono', channels: 1 },
+];
 
 export type RecorderClass = InstanceType<typeof Recorder>;
 
@@ -87,44 +87,49 @@ export const Recorder = GObject.registerClass({
     _current_peak!: number;
 
     pipeline: Gst.Pipeline;
-    level: Gst.Element;
-    ebin: Gst.Element;
-    filesink: Gst.Element;
-    recordBus: Gst.Bus;
-    handlerId: number;
-    file: Gio.File;
-    timeout: number;
-    _pipeState: Gst.State;
-
-    _init(): void {
+    level?: Gst.Element;
+    ebin?: Gst.Element;
+    filesink?: Gst.Element;
+    recordBus?: Gst.Bus | null;
+    handlerId?: number | null;
+    file?: Gio.File;
+    timeout?: number | null;
+    _pipeState?: Gst.State;
+
+    constructor() {
+        super();
         this._peaks = [];
-        super._init({});
 
-        let srcElement: Gst.Element, audioConvert: Gst.Element, caps: Gst.Caps;
+        let srcElement: Gst.Element;
+        let audioConvert: Gst.Element;
+        let caps: Gst.Caps;
+
+        this.pipeline = new Gst.Pipeline({ name: 'pipe' });
+
         try {
-            this.pipeline = new Gst.Pipeline({ name: 'pipe' });
-            srcElement = Gst.ElementFactory.make('pulsesrc', 'srcElement');
-            audioConvert = Gst.ElementFactory.make('audioconvert', 'audioConvert');
-            caps = Gst.Caps.from_string('audio/x-raw');
-            this.level = Gst.ElementFactory.make('level', 'level');
-            this.ebin = Gst.ElementFactory.make('encodebin', 'ebin');
-            this.filesink = Gst.ElementFactory.make('filesink', 'filesink');
+            srcElement = Gst.ElementFactory.make('pulsesrc', 'srcElement')!;
+            audioConvert = Gst.ElementFactory.make('audioconvert', 'audioConvert')!;
+            caps = Gst.Caps.from_string('audio/x-raw')!;
+            this.level = Gst.ElementFactory.make('level', 'level')!;
+            this.ebin = Gst.ElementFactory.make('encodebin', 'ebin')!;
+            this.filesink = Gst.ElementFactory.make('filesink', 'filesink')!;
         } catch (error) {
             log(`Not all elements could be created.\n${error}`);
         }
 
         try {
-            this.pipeline.add(srcElement);
-            this.pipeline.add(audioConvert);
-            this.pipeline.add(this.level);
-            this.pipeline.add(this.ebin);
-            this.pipeline.add(this.filesink);
+            this.pipeline.add(srcElement!);
+            this.pipeline.add(audioConvert!);
+            this.pipeline.add(this.level!);
+            this.pipeline.add(this.ebin!);
+            this.pipeline.add(this.filesink!);
         } catch (error) {
             log(`Not all elements could be addded.\n${error}`);
         }
 
-        srcElement.link(audioConvert);
-        audioConvert.link_filtered(this.level, caps);
+        srcElement!.link(audioConvert!);
+        audioConvert!.link_filtered(this.level!, caps!);
+
     }
 
     start(): void {
@@ -139,15 +144,15 @@ export const Recorder = GObject.registerClass({
         this.recordBus = this.pipeline.get_bus();
         this.recordBus.add_signal_watch();
         this.handlerId = this.recordBus.connect('message', (_, message: Gst.Message) => {
-            if (message !== null)
+            if (message)
                 this._onMessageReceived(message);
         });
 
 
-        this.ebin.set_property('profile', this._getProfile());
-        this.filesink.set_property('location', this.file.get_path());
-        this.level.link(this.ebin);
-        this.ebin.link(this.filesink);
+        this.ebin!.set_property('profile', this._getProfile());
+        this.filesink!.set_property('location', this.file.get_path());
+        this.level!.link(this.ebin!);
+        this.ebin!.link(this.filesink!);
 
         this.state = Gst.State.PLAYING;
 
@@ -168,7 +173,7 @@ export const Recorder = GObject.registerClass({
             this.state = Gst.State.PLAYING;
     }
 
-    stop(): RecordingClass {
+    stop(): RecordingClass | undefined {
         this.state = Gst.State.NULL;
         this.duration = 0;
         if (this.timeout) {
@@ -176,10 +181,11 @@ export const Recorder = GObject.registerClass({
             this.timeout = null;
         }
 
-        if (this.recordBus) {
+        if (this.recordBus && this.handlerId) {
             this.recordBus.remove_watch();
             this.recordBus.disconnect(this.handlerId);
             this.recordBus = null;
+            this.handlerId = null;
         }
 
 
@@ -190,7 +196,7 @@ export const Recorder = GObject.registerClass({
             return recording;
         }
 
-        return null;
+        return undefined;
     }
 
     _onMessageReceived(message: Gst.Message): void {
@@ -217,7 +223,10 @@ export const Recorder = GObject.registerClass({
             this.stop();
             break;
         case Gst.MessageType.WARNING:
-            log(message.parse_warning()[0].toString());
+            let warning = message.parse_warning()[0];
+            if (warning) {
+                log(warning.toString());
+            }
             break;
         case Gst.MessageType.ERROR:
             log(message.parse_error().toString());
@@ -230,19 +239,24 @@ export const Recorder = GObject.registerClass({
         return AudioChannels[channelIndex].channels;
     }
 
-    _getProfile(): GstPbutils.EncodingContainerProfile {
+    _getProfile(): GstPbutils.EncodingContainerProfile | undefined {
         let profileIndex = Settings.get_enum('audio-profile');
         const profile = EncodingProfiles[profileIndex];
 
         let audioCaps = Gst.Caps.from_string(profile.audioCaps);
-        audioCaps.set_value('channels', this._getChannel());
-
-        let encodingProfile = GstPbutils.EncodingAudioProfile.new(audioCaps, null, null, 1);
-        let containerCaps = Gst.Caps.from_string(profile.containerCaps);
-        let containerProfile = GstPbutils.EncodingContainerProfile.new('record', null, containerCaps, null);
-        containerProfile.add_profile(encodingProfile);
+        audioCaps?.set_value('channels', this._getChannel());
+
+        if (audioCaps) {
+            let encodingProfile = GstPbutils.EncodingAudioProfile.new(audioCaps, null, null, 1);
+            let containerCaps = Gst.Caps.from_string(profile.containerCaps);
+            if (containerCaps) {
+                let containerProfile = GstPbutils.EncodingContainerProfile.new('record', null, 
containerCaps, null);
+                containerProfile.add_profile(encodingProfile);
+                return containerProfile;
+            }
+        }
 
-        return containerProfile;
+        return undefined;
     }
 
     get duration(): number {
@@ -256,12 +270,14 @@ export const Recorder = GObject.registerClass({
 
     // eslint-disable-next-line camelcase
     set current_peak(peak: number) {
-        if (peak > 0)
-            peak = 0;
+        if (this._peaks) {
+            if (peak > 0)
+                peak = 0;
 
-        this._current_peak = Math.pow(10, peak / 20);
-        this._peaks.push(this._current_peak);
-        this.notify('current-peak');
+            this._current_peak = Math.pow(10, peak / 20);
+            this._peaks.push(this._current_peak);
+            this.notify('current-peak');
+        }
     }
 
     set duration(val: number) {
@@ -269,16 +285,18 @@ export const Recorder = GObject.registerClass({
         this.notify('duration');
     }
 
-    get state(): Gst.State {
+    get state(): Gst.State | undefined {
         return this._pipeState;
     }
 
-    set state(s: Gst.State) {
+    set state(s: Gst.State | undefined) {
         this._pipeState = s;
-        const ret = this.pipeline.set_state(this._pipeState);
+        if (this._pipeState) {
+            const ret = this.pipeline.set_state(this._pipeState);
 
-        if (ret === Gst.StateChangeReturn.FAILURE)
-            log('Unable to update the recorder pipeline state');
+            if (ret === Gst.StateChangeReturn.FAILURE)
+                log('Unable to update the recorder pipeline state');
+        }
     }
 
 });
diff --git a/src/recorderWidget.ts b/src/recorderWidget.ts
index dd3da79..f282853 100644
--- a/src/recorderWidget.ts
+++ b/src/recorderWidget.ts
@@ -39,8 +39,8 @@ export const RecorderWidget = GObject.registerClass({
     waveform: WaveFormClass;
     actionsGroup: Gio.SimpleActionGroup;
 
-    _init(recorder: RecorderClass): void {
-        super._init({});
+    constructor(recorder: RecorderClass) {
+        super();
         this.recorder = recorder;
 
         this.waveform = new WaveForm({
@@ -122,8 +122,9 @@ export const RecorderWidget = GObject.registerClass({
             case Gtk.ResponseType.YES: {
                 const recording = this.recorder.stop();
                 this.state = RecorderState.Stopped;
-
-                recording.delete();
+                if (recording) {
+                    recording.delete();
+                }
                 this.emit('canceled');
                 break;
             }
diff --git a/src/recording.ts b/src/recording.ts
index 9df4ace..3b35c47 100644
--- a/src/recording.ts
+++ b/src/recording.ts
@@ -25,24 +25,25 @@ export const Recording = GObject.registerClass({
             'name',
             'Recording Name', 'Recording name in string',
             GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT,
-            null),
+            ''),
     },
 }, class Recording extends GObject.Object {
     _file: Gio.File;
     _peaks: number[];
     _loadedPeaks: number[];
-    _extension: string;
+    _extension?: string;
     _timeModified: GLib.DateTime;
     _timeCreated: GLib.DateTime;
-    _duration: number;
+    _duration?: number;
 
-    pipeline: Gst.Bin;
+    pipeline?: Gst.Bin | null;
+
+    constructor(file: Gio.File) {
+        super();
 
-    _init(file: Gio.File): void {
         this._file = file;
         this._peaks = []
         this._loadedPeaks = [];
-        super._init({});
 
         let info = file.query_info('time::created,time::modified,standard::content-type', 0, null);
         const contentType = info.get_attribute_string('standard::content-type');
@@ -70,18 +71,18 @@ export const Recording = GObject.registerClass({
         discoverer.discover_uri_async(this.uri);
     }
 
-    get name(): string {
+    get name(): string | null  {
         return this._file.get_basename();
     }
 
-    set name(filename: string) {
+    set name(filename: string | null) {
         if (filename && filename !== this.name) {
             this._file = this._file.set_display_name(filename, null);
             this.notify('name');
         }
     }
 
-    get extension(): string {
+    get extension(): string | undefined {
         return this._extension;
     }
 
@@ -115,8 +116,9 @@ export const Recording = GObject.registerClass({
             this.emit('peaks-updated');
             let enc = new TextEncoder();
             const buffer = new GLib.Bytes(enc.encode(JSON.stringify(data)));
-            this.waveformCache.replace_contents_bytes_async(buffer, null, false, 
Gio.FileCreateFlags.REPLACE_DESTINATION, null, (obj: Gio.File, res: Gio.AsyncResult) => {
-                obj.replace_contents_finish(res);
+            this.waveformCache.replace_contents_bytes_async(buffer, null, false, 
Gio.FileCreateFlags.REPLACE_DESTINATION, null, (obj: Gio.File | null, res: Gio.AsyncResult) => {
+                if (obj)
+                    obj.replace_contents_finish(res);
             });
         }
     }
@@ -145,11 +147,11 @@ export const Recording = GObject.registerClass({
 
     loadPeaks(): void {
         if (this.waveformCache.query_exists(null)) {
-            this.waveformCache.load_bytes_async(null, (obj: Gio.File, res: Gio.AsyncResult) => {
-                const bytes = obj.load_bytes_finish(res)[0];
+            this.waveformCache.load_bytes_async(null, (obj: Gio.File | null, res: Gio.AsyncResult) => {
+                const bytes = obj?.load_bytes_finish(res)[0];
                 try {
                     let decoder = new TextDecoder('utf-8');
-                    this._peaks = JSON.parse(decoder.decode(bytes.get_data()));
+                    this._peaks = JSON.parse(decoder.decode(bytes?.get_data()!));
                     this.emit('peaks-updated');
                 } catch (error) {
                     log(`Error reading waveform data file: ${this.name}_data`);
@@ -166,21 +168,20 @@ export const Recording = GObject.registerClass({
 
 
         let uridecodebin = this.pipeline.get_by_name('uridecodebin');
-        uridecodebin.set_property('uri', this.uri);
+        uridecodebin?.set_property('uri', this.uri);
 
         let fakesink = this.pipeline.get_by_name('faked');
-        fakesink.set_property('qos', false);
-        fakesink.set_property('sync', true);
+        fakesink?.set_property('qos', false);
+        fakesink?.set_property('sync', true);
 
         const bus = this.pipeline.get_bus();
         this.pipeline.set_state(Gst.State.PLAYING);
-        bus.add_signal_watch();
+        bus?.add_signal_watch();
 
-        bus.connect('message', (_bus: Gst.Bus, message: Gst.Message) => {
-            let s: Gst.Structure;
+        bus?.connect('message', (_bus: Gst.Bus, message: Gst.Message) => {
             switch (message.type) {
             case Gst.MessageType.ELEMENT:
-                s = message.get_structure();
+                let s = message.get_structure();
                 if (s && s.has_name('level')) {
                     const peakVal = s.get_value('peak') as unknown as GObject.ValueArray;
 
@@ -192,7 +193,7 @@ export const Recording = GObject.registerClass({
                 break;
             case Gst.MessageType.EOS:
                 this.peaks = this._loadedPeaks;
-                this.pipeline.set_state(Gst.State.NULL);
+                this.pipeline?.set_state(Gst.State.NULL);
                 this.pipeline = null;
                 break;
             }
diff --git a/src/recordingList.ts b/src/recordingList.ts
index 5a3d614..5c9c4c0 100644
--- a/src/recordingList.ts
+++ b/src/recordingList.ts
@@ -9,14 +9,13 @@ import { Recording, RecordingClass } from './recording.js';
 export type RecordingListClass = InstanceType<typeof RecordingList>;
 
 export const RecordingList = GObject.registerClass(class RecordingList extends Gio.ListStore {
-    _enumerator: Gio.FileEnumerator;
+    _enumerator?: Gio.FileEnumerator;
 
     cancellable: Gio.Cancellable;
     dirMonitor: Gio.FileMonitor;
 
-    _init(): void {
-        super._init({ });
-
+    constructor() {
+        super();
         this.cancellable = new Gio.Cancellable();
         // Monitor Direcotry actions
         this.dirMonitor = RecordingsDir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, 
this.cancellable);
@@ -56,79 +55,85 @@ export const RecordingList = GObject.registerClass(class RecordingList extends G
         const fileEnumerator = oldDir.enumerate_children('standard::name', 
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, this.cancellable);
         let allCopied = true;
 
-        const copyFiles = function (obj: Gio.FileEnumerator, res: Gio.AsyncResult) {
-            try {
-                const fileInfos = obj.next_files_finish(res);
-                if (fileInfos.length) {
-                    fileInfos.forEach((info: Gio.FileInfo) => {
-                        const name = info.get_name();
-                        const src = oldDir.get_child(name);
-                        /* Translators: ""%s (Old)"" is the new name assigned to a file moved from
-                            the old recordings location */
-                        const dest = RecordingsDir.get_child(_('%s (Old)').format(name));
-
-                        // @ts-expect-error
-                        src.copy_async(dest, Gio.FileCopyFlags.OVERWRITE, GLib.PRIORITY_LOW, 
this.cancellable, null, (objCopy: Gio.File, resCopy: Gio.AsyncResult) => {
-                            try {
-                                objCopy.copy_finish(resCopy);
-                                objCopy.trash_async(GLib.PRIORITY_LOW, this.cancellable, null);
-                                this.dirMonitor.emit_event(dest, src, Gio.FileMonitorEvent.MOVED_IN);
-                            } catch (e) {
-                                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
-                                    console.error(`Failed to copy recording ${name} to the new location`);
-                                    log(e);
-                                }
-                                allCopied = false;
-                            }
-                        });
+        fileEnumerator.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, (obj, res) => {
+            this._copyFiles(obj, res, allCopied);
+        });
+    }
 
-                    });
-                    fileEnumerator.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, copyFiles);
-                } else {
-                    fileEnumerator.close(this.cancellable);
-                    if (allCopied) {
-                        oldDir.delete_async(GLib.PRIORITY_LOW, this.cancellable, (objDelete: Gio.File, 
resDelete: Gio.AsyncResult) => {
-                            try {
-                                objDelete.delete_finish(resDelete);
-                            } catch (e) {
-                                log('Failed to remove the old Recordings directory. Ignore if you\'re using 
flatpak');
+    _copyFiles(fileEnumerator: Gio.FileEnumerator | null, res: Gio.AsyncResult, allCopied: boolean) {
+        let oldDir = fileEnumerator?.container;
+        try {
+            const fileInfos = fileEnumerator?.next_files_finish(res);
+            if (fileInfos && fileInfos.length) {
+                fileInfos.forEach((info: Gio.FileInfo) => {
+                    const name = info.get_name();
+                    const src = oldDir?.get_child(name);
+                    /* Translators: ""%s (Old)"" is the new name assigned to a file moved from
+                        the old recordings location */
+                    const dest = RecordingsDir.get_child(_('%s (Old)').format(name));
+
+                    // @ts-expect-error
+                    src?.copy_async(dest, Gio.FileCopyFlags.OVERWRITE, GLib.PRIORITY_LOW, this.cancellable, 
null, (objCopy: Gio.File, resCopy: Gio.AsyncResult) => {
+                        try {
+                            objCopy.copy_finish(resCopy);
+                            objCopy.trash_async(GLib.PRIORITY_LOW, this.cancellable, null);
+                            this.dirMonitor.emit_event(dest, src, Gio.FileMonitorEvent.MOVED_IN);
+                        } catch (e: any) {
+                            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
+                                console.error(`Failed to copy recording ${name} to the new location`);
                                 log(e);
                             }
-                        });
-                    }
-                }
-            } catch (e) {
-                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
-                    console.error(`Failed to copy old  recordings ${e}`);
+                            allCopied = false;
+                        }
+                    });
 
+                });
+                fileEnumerator?.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, (obj, res) => {
+                    this._copyFiles(obj, res, allCopied);
+                });
+            } else {
+                fileEnumerator?.close(this.cancellable);
+                if (allCopied) {
+                    oldDir?.delete_async(GLib.PRIORITY_LOW, this.cancellable, (objDelete: Gio.File | null, 
resDelete: Gio.AsyncResult) => {
+                        try {
+                            objDelete?.delete_finish(resDelete);
+                        } catch (e: any) {
+                            log('Failed to remove the old Recordings directory. Ignore if you\'re using 
flatpak');
+                            log(e);
+                        }
+                    });
+                }
             }
-        }.bind(this);
-        fileEnumerator.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, copyFiles);
+        } catch (e: any) {
+            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+                console.error(`Failed to copy old  recordings ${e}`);
+
+        }
     }
 
-    _enumerateDirectory(obj: Gio.File, res: Gio.AsyncResult): void {
-        this._enumerator = obj.enumerate_children_finish(res);
+    _enumerateDirectory(obj: Gio.File | null, res: Gio.AsyncResult): void {
+        this._enumerator = obj?.enumerate_children_finish(res);
         if (this._enumerator === null) {
             log('The contents of the Recordings directory were not indexed.');
             return;
         }
-        this._enumerator.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, 
this._onNextFiles.bind(this));
+        this._enumerator?.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, 
this._onNextFiles.bind(this));
     }
 
-    _onNextFiles(obj: Gio.FileEnumerator, res: Gio.AsyncResult): void {
+    _onNextFiles(obj: Gio.FileEnumerator | null, res: Gio.AsyncResult): void {
         try {
-            let fileInfos = obj.next_files_finish(res);
-            if (fileInfos.length) {
+            let fileInfos = obj?.next_files_finish(res);
+            if (fileInfos && fileInfos.length) {
                 fileInfos.forEach(info => {
                     const file = RecordingsDir.get_child(info.get_name());
                     const recording = new Recording(file);
                     this.sortedInsert(recording);
                 });
-                this._enumerator.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, 
this._onNextFiles.bind(this));
+                this._enumerator?.next_files_async(5, GLib.PRIORITY_LOW, this.cancellable, 
this._onNextFiles.bind(this));
             } else {
-                this._enumerator.close(this.cancellable);
+                this._enumerator?.close(this.cancellable);
             }
-        } catch (e) {
+        } catch (e: any) {
             if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                 console.error(`Failed to load recordings ${e}`);
 
diff --git a/src/recordingListWidget.ts b/src/recordingListWidget.ts
index b8a40ac..e34ea18 100644
--- a/src/recordingListWidget.ts
+++ b/src/recordingListWidget.ts
@@ -18,11 +18,11 @@ export const RecordingsListWidget = GObject.registerClass({
 }, class RecordingsListWidget extends Adw.Bin {
     _player: GstPlayer.Player;
     list: Gtk.ListBox;
-    activeRow: RowClass;
-    activePlayingRow: RowClass;
+    activeRow?: RowClass | null;
+    activePlayingRow?: RowClass | null;
 
-    _init(model: Gio.ListModel, player: GstPlayer.Player): void {
-        super._init();
+    constructor(model: Gio.ListModel, player: GstPlayer.Player) {
+        super();
         this.list = Gtk.ListBox.new();
         this.list.valign = Gtk.Align.START;
         this.list.margin_start = 8;
@@ -40,7 +40,8 @@ export const RecordingsListWidget = GObject.registerClass({
                 this.activePlayingRow.state = RowState.Paused;
                 this.activePlayingRow.waveform.position = 0.0;
             } else if (state === GstPlayer.PlayerState.PLAYING) {
-                this.activePlayingRow.state = RowState.Playing;
+                if (this.activePlayingRow)
+                    this.activePlayingRow.state = RowState.Playing;
             }
         });
 
@@ -51,7 +52,10 @@ export const RecordingsListWidget = GObject.registerClass({
             }
         });
 
+        // @ts-expect-error
         this.list.bind_model(model, (recording: RecordingClass) => {
+            // This is weird - does using `constructor()` break how it's recognized?
+            // @ts-expect-error
             let row = new Row(recording);
 
             row.waveform.connect('gesture-pressed', _ => {
diff --git a/src/row.ts b/src/row.ts
index 4ab6cc1..00b8a9e 100644
--- a/src/row.ts
+++ b/src/row.ts
@@ -43,13 +43,13 @@ export const Row = GObject.registerClass({
     _rightStack!: Gtk.Stack;
     _name!: Gtk.Label;
     _entry!: Gtk.Entry;
-    _date: Gtk.Label;
-    _duration: Gtk.Label;
-    _revealer: Gtk.Revealer;
-    _playbackControls: Gtk.Box;
-    _saveBtn: Gtk.Button;
-    _playBtn: Gtk.Button;
-    _pauseBtn: Gtk.Button;
+    _date!: Gtk.Label;
+    _duration!: Gtk.Label;
+    _revealer!: Gtk.Revealer;
+    _playbackControls!: Gtk.Box;
+    _saveBtn!: Gtk.Button;
+    _playBtn!: Gtk.Button;
+    _pauseBtn!: Gtk.Button;
 
     _recording: RecordingClass;
     _expanded: boolean;
@@ -58,7 +58,7 @@ export const Row = GObject.registerClass({
 
     waveform: WaveFormClass;
     actionGroup: Gio.SimpleActionGroup;
-    exportDialog: Gtk.FileChooserNative;
+    exportDialog?: Gtk.FileChooserNative | null;
 
     saveRenameAction: Gio.SimpleAction;
     renameAction: Gio.SimpleAction;
@@ -66,12 +66,13 @@ export const Row = GObject.registerClass({
     playAction: Gio.SimpleAction;
     keyController: Gtk.EventControllerKey;
 
-    _init(recording: RecordingClass): void {
+    constructor(recording: RecordingClass) {
+        super();
+
         this._recording = recording;
         this._expanded = false;
         this._editMode = false;
-
-        super._init({});
+        this._state = RowState.Paused;
 
         this.waveform = new WaveForm({
             margin_top: 18,
@@ -86,7 +87,7 @@ export const Row = GObject.registerClass({
             this._recording.loadPeaks();
         }
 
-        if (recording.timeModified !== null && !recording.timeModified.equal(recording.timeCreated))
+        if (recording.timeModified)
             this._date.label = displayDateTime(recording.timeModified);
         else
             this._date.label = displayDateTime(recording.timeCreated);
@@ -104,10 +105,11 @@ export const Row = GObject.registerClass({
             this.exportDialog.set_current_name(`${this._recording.name}.${this._recording.extension}`);
             this.exportDialog.connect('response', (_dialog: Gtk.FileChooserNative, response: number) => {
                 if (response === Gtk.ResponseType.ACCEPT) {
-                    const dest = this.exportDialog.get_file();
-                    this._recording.save(dest);
+                    const dest = this.exportDialog?.get_file();
+                    if (dest)
+                        this._recording.save(dest);
                 }
-                this.exportDialog.destroy();
+                this.exportDialog?.destroy();
                 this.exportDialog = null;
             });
             this.exportDialog.show();
diff --git a/src/waveform.ts b/src/waveform.ts
index e1cc860..523e311 100644
--- a/src/waveform.ts
+++ b/src/waveform.ts
@@ -59,19 +59,19 @@ export const WaveForm = GObject.registerClass({
 }, class WaveForm extends Gtk.DrawingArea {
     _peaks: number[];
     _position: number;
-    _dragGesture: Gtk.GestureDrag;
+    _dragGesture?: Gtk.GestureDrag;
     _hcId: number;
-    _lastX: number;
+    _lastX?: number;
 
     lastPosition: number;
     waveType: WaveType;
 
-    _init(params, type: WaveType): void {
+    constructor(params: Partial<Gtk.DrawingArea.ConstructorProperties> | undefined, type: WaveType) {
+        super(params);
         this._peaks = [];
         this._position = 0;
         this.lastPosition = 0;
         this.waveType = type;
-        super._init(params);
 
         if (this.waveType === WaveType.Player) {
             this._dragGesture = Gtk.GestureDrag.new();
@@ -94,8 +94,10 @@ export const WaveForm = GObject.registerClass({
     }
 
     dragUpdate(_gesture: Gtk.GestureDrag, offsetX: number): void {
-        this._position = this._clamped(offsetX + this._lastX);
-        this.queue_draw();
+        if (this._lastX) {
+            this._position = this._clamped(offsetX + this._lastX);
+            this.queue_draw();
+        }
     }
 
     dragEnd(): void {
@@ -103,7 +105,8 @@ export const WaveForm = GObject.registerClass({
         this.emit('position-changed', this.position);
     }
 
-    drawFunc(da: WaveFormClass, ctx: Cairo.Context) {
+    drawFunc(superDa: Gtk.DrawingArea, ctx: Cairo.Context) {
+        let da = superDa as WaveFormClass;
         const maxHeight = da.get_allocated_height();
         const vertiCenter = maxHeight / 2;
         const horizCenter = da.get_allocated_width() / 2;
@@ -148,12 +151,14 @@ export const WaveForm = GObject.registerClass({
         });
     }
 
-    set peak(p) {
-        if (this._peaks.length > this.get_allocated_width() / (2 * GUTTER))
-            this._peaks.pop();
+    set peak(p: number) {
+        if (this._peaks) {
+            if (this._peaks.length > this.get_allocated_width() / (2 * GUTTER))
+                this._peaks.pop();
 
-        this._peaks.unshift(p.toFixed(2));
-        this.queue_draw();
+            this._peaks.unshift(p.toFixed(2));
+            this.queue_draw();
+        }
     }
 
     set peaks(p: number[]) {
@@ -162,10 +167,12 @@ export const WaveForm = GObject.registerClass({
     }
 
     set position(pos: number) {
-        this._position = this._clamped(-pos * this._peaks.length * GUTTER);
-        this._lastX = this._position;
-        this.queue_draw();
-        this.notify('position');
+        if (this._peaks) {
+            this._position = this._clamped(-pos * this._peaks.length * GUTTER);
+            this._lastX = this._position;
+            this.queue_draw();
+            this.notify('position');
+        }
     }
 
     get position(): number {
diff --git a/src/window.ts b/src/window.ts
index 534581f..beee9a2 100644
--- a/src/window.ts
+++ b/src/window.ts
@@ -61,15 +61,16 @@ export const Window = GObject.registerClass({
     _recordingListWidget: RecordingsListWidgetClass;
 
     toastUndo: boolean;
-    undoSignalID: number;
+    undoSignalID: number | null;
     undoAction: Gio.SimpleAction;
 
     _state: WindowState;
 
-    _init(params): void {
-        super._init(Object.assign({
-            icon_name: pkg.name,
-        }, params));
+    constructor(params: Partial<Adw.Application.ConstructorProperties>) {
+        super(params);
+
+        this.iconName = pkg.name;
+        this._state = WindowState.Empty;
 
         this.recorder = new Recorder();
         this.recorderWidget = new RecorderWidget(this.recorder);
@@ -94,7 +95,13 @@ export const Window = GObject.registerClass({
 
         this._recordingListWidget.connect('row-deleted', (_listBox: Gtk.ListBox, recording: RecordingClass, 
index: number) => {
             this._recordingList.remove(index);
-            this.sendNotification(_('"%s" deleted').format(recording.name), recording, index);
+            let message: string;
+            if (recording.name) {
+                message = _('"%s" deleted').format(recording.name);
+            } else {
+                message = _('Recording deleted');
+            }
+            this.sendNotification(message, recording, index);
         });
 
         const builder = Gtk.Builder.new_from_resource('/org/gnome/SoundRecorder/gtk/help-overlay.ui');
@@ -108,7 +115,7 @@ export const Window = GObject.registerClass({
 
         let openMenuAction = new Gio.SimpleAction({ name: 'open-primary-menu', state: new GLib.Variant('b', 
true) });
         openMenuAction.connect('activate', action => {
-            const state = action.get_state().get_boolean();
+            const state = action.get_state()?.get_boolean();
             action.state = new GLib.Variant('b', !state);
         });
         this.add_action(openMenuAction);
diff --git a/types/ambient.d.ts b/types/ambient.d.ts
index ba0bd11..d77bf73 100644
--- a/types/ambient.d.ts
+++ b/types/ambient.d.ts
@@ -64,4 +64,7 @@ declare class TextEncoder {
 declare interface String {
   format(...replacements: string[]): string;
   format(...replacements: number[]): string;
+}
+declare interface Number {
+  toFixed(digits: number): number;
 }
\ No newline at end of file


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]