-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add Enumerable class * refactor: split Action into KeyAction, DialAction, and KeyInMultiAction * refactor: devices to use store, add initial tracking of actions * refactor: update Enumerable to be inheritable * feat: allow Enumerable to be constructed from another Enumerable * feat: update action to include device and coordinates * refactor: update devices to inherit Enumerable * style: fix linting * feat: track visible actions on devices * feat: update events to use Action instance * fix: action type * feat: simplify action store * feat: add type-checking helpers * test: fix tests * test: fix tests * test: fix tests * test: fix tests * style: linting * refactor: update actions to be a service, allowing for it to be iterated over * feat: add visible actions to SingletonAction * refactor: merge MultiActionKey in KeyAction * test: mock ActionStore (WIP) * refactor: action and device store * refactor: improve exports * refactor: decouple stores * chore: fix linting * refactor: remove deprecation notice for v1 * refactor: remove deprecation notice for v1 * refactor!: remove deviceId from events, export types over classes --------- Co-authored-by: Richard Herman <[email protected]>
- Loading branch information
Showing
41 changed files
with
2,514 additions
and
1,156 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
/** | ||
* Provides a read-only iterable collection of items. | ||
*/ | ||
export class Enumerable<T> { | ||
/** | ||
* Backing function responsible for providing the iterator of items. | ||
*/ | ||
readonly #items: () => Iterable<T>; | ||
|
||
/** | ||
* Backing function for {@link Enumerable.length}. | ||
*/ | ||
readonly #length: () => number; | ||
|
||
/** | ||
* Initializes a new instance of the {@link Enumerable} class. | ||
* @param source Source that contains the items. | ||
* @returns The enumerable. | ||
*/ | ||
constructor(source: Enumerable<T> | Map<unknown, T> | Set<T> | T[]) { | ||
if (source instanceof Enumerable) { | ||
this.#items = source.#items; | ||
this.#length = source.#length; | ||
} else if (Array.isArray(source)) { | ||
this.#items = (): Iterable<T> => source; | ||
this.#length = (): number => source.length; | ||
} else { | ||
this.#items = (): IterableIterator<T> => source.values(); | ||
this.#length = (): number => source.size; | ||
} | ||
} | ||
|
||
/** | ||
* Gets the number of items in the enumerable. | ||
* @returns The number of items. | ||
*/ | ||
public get length(): number { | ||
return this.#length(); | ||
} | ||
|
||
/** | ||
* Gets the iterator for the enumerable. | ||
* @yields The items. | ||
*/ | ||
public *[Symbol.iterator](): IterableIterator<T> { | ||
for (const item of this.#items()) { | ||
yield item; | ||
} | ||
} | ||
|
||
/** | ||
* Determines whether all items satisfy the specified predicate. | ||
* @param predicate Function that determines whether each item fulfils the predicate. | ||
* @returns `true` when all items satisfy the predicate; otherwise `false`. | ||
*/ | ||
public every(predicate: (value: T) => boolean): boolean { | ||
for (const item of this.#items()) { | ||
if (!predicate(item)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Returns an iterable of items that meet the specified condition. | ||
* @param predicate Function that determines which items to filter. | ||
* @yields The filtered items; items that returned `true` when invoked against the predicate. | ||
*/ | ||
public *filter(predicate: (value: T) => boolean): IterableIterator<T> { | ||
for (const item of this.#items()) { | ||
if (predicate(item)) { | ||
yield item; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Finds the first item that satisfies the specified predicate. | ||
* @param predicate Predicate to match items against. | ||
* @returns The first item that satisfied the predicate; otherwise `undefined`. | ||
*/ | ||
public find(predicate: (value: T) => boolean): T | undefined { | ||
for (const item of this.#items()) { | ||
if (predicate(item)) { | ||
return item; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Finds the last item that satisfies the specified predicate. | ||
* @param predicate Predicate to match items against. | ||
* @returns The first item that satisfied the predicate; otherwise `undefined`. | ||
*/ | ||
public findLast(predicate: (value: T) => boolean): T | undefined { | ||
let result = undefined; | ||
for (const item of this.#items()) { | ||
if (predicate(item)) { | ||
result = item; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Iterates over each item, and invokes the specified function. | ||
* @param fn Function to invoke against each item. | ||
*/ | ||
public forEach(fn: (item: T) => void): void { | ||
for (const item of this.#items()) { | ||
fn(item); | ||
} | ||
} | ||
|
||
/** | ||
* Determines whether the search item exists in the collection exists. | ||
* @param search Item to search for. | ||
* @returns `true` when the item was found; otherwise `false`. | ||
*/ | ||
public includes(search: T): boolean { | ||
return this.some((item) => item === search); | ||
} | ||
|
||
/** | ||
* Maps each item within the collection to a new structure using the specified mapping function. | ||
* @param mapper Function responsible for mapping the items. | ||
* @yields The mapped items. | ||
*/ | ||
public *map<U>(mapper: (value: T) => U): Iterable<U> { | ||
for (const item of this.#items()) { | ||
yield mapper(item); | ||
} | ||
} | ||
|
||
/** | ||
* Applies the accumulator function to each item, and returns the result. | ||
* @param accumulator Function responsible for accumulating all items within the collection. | ||
* @returns Result of accumulating each value. | ||
*/ | ||
public reduce(accumulator: (previous: T, current: T) => T): T; | ||
/** | ||
* Applies the accumulator function to each item, and returns the result. | ||
* @param accumulator Function responsible for accumulating all items within the collection. | ||
* @param initial Initial value supplied to the accumulator. | ||
* @returns Result of accumulating each value. | ||
*/ | ||
public reduce<R>(accumulator: (previous: R, current: T) => R, initial: R): R; | ||
/** | ||
* Applies the accumulator function to each item, and returns the result. | ||
* @param accumulator Function responsible for accumulating all items within the collection. | ||
* @param initial Initial value supplied to the accumulator. | ||
* @returns Result of accumulating each value. | ||
*/ | ||
public reduce<R>(accumulator: (previous: R | T, current: T) => R | T, initial?: R | T): R | T { | ||
if (this.length === 0) { | ||
if (initial === undefined) { | ||
throw new TypeError("Reduce of empty enumerable with no initial value."); | ||
} | ||
|
||
return initial; | ||
} | ||
|
||
let result = initial; | ||
for (const item of this.#items()) { | ||
if (result === undefined) { | ||
result = item; | ||
} else { | ||
result = accumulator(result, item); | ||
} | ||
} | ||
|
||
return result!; | ||
} | ||
|
||
/** | ||
* Determines whether an item in the collection exists that satisfies the specified predicate. | ||
* @param predicate Function used to search for an item. | ||
* @returns `true` when the item was found; otherwise `false`. | ||
*/ | ||
public some(predicate: (value: T) => boolean): boolean { | ||
for (const item of this.#items()) { | ||
if (predicate(item)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { DeviceType } from "../../../api/device"; | ||
import type { Device } from "../../devices"; | ||
import { deviceStore } from "../../devices/store"; | ||
|
||
const { ReadOnlyActionStore } = jest.requireActual("../store"); | ||
const { KeyAction } = jest.requireActual("../key"); | ||
const { DialAction } = jest.requireActual("../dial"); | ||
|
||
jest.mock("../../devices/store"); | ||
|
||
jest.spyOn(deviceStore, "getDeviceById").mockReturnValue({ | ||
id: "device123", | ||
isConnected: true, | ||
name: "Device 1", | ||
size: { | ||
columns: 5, | ||
rows: 3 | ||
}, | ||
type: DeviceType.StreamDeck | ||
} as unknown as Device); | ||
|
||
export const actionStore = { | ||
set: jest.fn(), | ||
delete: jest.fn(), | ||
getActionById: jest.fn().mockImplementation((id: string) => { | ||
if (id === "dial123") { | ||
return new DialAction({ | ||
action: "com.elgato.test.dial", | ||
context: id, | ||
device: "device123", | ||
event: "willAppear", | ||
payload: { | ||
controller: "Encoder", | ||
coordinates: { | ||
column: 1, | ||
row: 2 | ||
}, | ||
isInMultiAction: false, | ||
settings: {} | ||
} | ||
}); | ||
} | ||
|
||
return new KeyAction({ | ||
action: "com.elgato.test.key", | ||
context: id, | ||
device: "device123", | ||
event: "willAppear", | ||
payload: { | ||
controller: "Keypad", | ||
coordinates: { | ||
column: 1, | ||
row: 2 | ||
}, | ||
isInMultiAction: false, | ||
settings: {} | ||
} | ||
}); | ||
}) | ||
}; | ||
|
||
export { ReadOnlyActionStore }; |
Oops, something went wrong.