-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SLEIGHT-68, SLEIGHT-84, SLEIGHT-87: negative indicator, prep for rele…
…ase, negative range validation
- Loading branch information
Showing
76 changed files
with
1,587 additions
and
601 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,38 +1,116 @@ | ||
# Sleight | ||
|
||
Sleight is an app which creates voice dictation components and rules. | ||
Sleight is a tool that lets you make and edit speech commands for other (free) software without knowing Python/etc. | ||
|
||
## Usage | ||
### What Other Software? | ||
|
||
TODO: this section | ||
[Dragonfly](https://dragonfly2.readthedocs.io) to start, but other frameworks are on the roadmap. (See the roadmap section below.) | ||
|
||
## Export Formats | ||
## What Problems Does this Solve? | ||
|
||
TODO: this section | ||
Creating voice commands in Python/ Vocola/ Talon is fairly technical. Sleight aims to lower the bar for all and increase velocity for power users. | ||
|
||
## [npm](https://www.npmjs.com/) | ||
Along with these productivity goals, Sleight is looking to improve [shareability/portability](#todo-export-formats) of voice commands and [grammar resilience](#todo-lockability) against framework changes/ updates. | ||
|
||
## Where It's At Now | ||
|
||
Sleight is very much alpha software at this point. Though it already supports a subset of the Dragonfly specification and can export full Dragonfly rules, there is much which needs to be done. | ||
|
||
### Help Wanted | ||
|
||
There are various ways you can contribute to Sleight: | ||
|
||
- giving feedback: see the [feedback](#feedback) section below. | ||
- expanding/narrowing/ordering the roadmap | ||
- writing documentation | ||
- code contributions | ||
- demos (YouTube/etc.) | ||
|
||
### Feedback | ||
|
||
Sleight needs: | ||
|
||
- bug reports | ||
- UI/UX improvement suggestions | ||
- validation suggestions | ||
- are there ways to create or import invalid data which Sleight allows? | ||
- feature requests | ||
- please have _lots_ of patience here | ||
- alternatively, open a PR ;) | ||
- TypeScript/React best practices suggestions | ||
|
||
## Roadmap Thus Far | ||
|
||
This is a very rough roadmap at this point, and not necessary in order of importance. | ||
|
||
- documentation | ||
- demos | ||
- keyboard shortcuts (customizable) | ||
- exports to other frameworks | ||
- Caster | ||
- Vocola | ||
- Talon | ||
- model changes | ||
- follow immutable model principle | ||
- model version adapters | ||
- aim to provide common "primitives" rather than implement any particular framework's specification | ||
- add more | ||
- simplify existing `Action`s | ||
- get away from resemblance to Dragonfly's model | ||
- web API | ||
- read-only at first | ||
- cleanup | ||
- code | ||
- UI/UX | ||
|
||
## Design Philosophy | ||
|
||
Sleight has thus far been designed with "strong opinions loosely held". Among them are the following. | ||
|
||
### Libraries Usage Should Be Minimized | ||
|
||
Sometimes bringing in a library is the best solution, but especially in the JS world, churn is high and packages break often. Therefore, to minimize maintenance, adequate consideration has to be given to the question of when to build versus when to "buy". | ||
|
||
The approach that Sleight has taken thus far has been to mostly keep `packages.json` small, building simple utilities for simple jobs and including only a handful of libraries. | ||
|
||
### Optimize for Popularity | ||
|
||
When choosing a library or a framework, there are multiple ways to decide what's best. You could opt for performance, ergonomics, stability, or any number of other attributes. | ||
|
||
Sleight is a project in its infancy, so it has thus far optimized for popularity: React over Vue or Angular; Electron over Tauri; etc. The main idea here is that using popular choices will have the best chance of attracting code contributors. | ||
|
||
Popular choices will also likely be decent choices, even if they're not the best choices. | ||
|
||
### TypeScript | ||
|
||
Versus JavaScript? No competition. | ||
|
||
### React Testing Library | ||
|
||
RTL's philosophy overlaps (and inspired) Sleight's in two aspects. | ||
|
||
1. Tests should access the DOM in the same way the user does. This seems like an obvious accessibility benefit, and forces the developer to care about accessibility if they care about testing. | ||
2. Functional tests provide greater flexibility than unit tests. Fixing unit tests which broke because underlying implementations changed isn't a great use of anyone's time. Unit tests are necessary and useful, but should mainly cover implementation details which overly complicate functional tests. | ||
|
||
## Running the Project | ||
|
||
In the project directory, you can run: | ||
|
||
### `npm start` | ||
### `npm run react-start` | ||
|
||
Runs the app in the development mode.\ | ||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. | ||
This will run the app in the development mode. | ||
|
||
The page will reload if you make edits.\ | ||
You can then open [http://localhost:3000](http://localhost:3000) to view it in the browser. | ||
|
||
The page will reload if you make edits. | ||
You will also see any lint errors in the console. | ||
|
||
### `npm test` | ||
### `npm run react-test` | ||
|
||
Launches the test runner in the interactive watch mode.\ | ||
This launches the test runner in the interactive watch mode. | ||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | ||
|
||
### `npm run build` | ||
|
||
Builds the app for production to the `build` folder.\ | ||
It correctly bundles React in production mode and optimizes the build for the best performance. | ||
|
||
The build is minified and the filenames include the hashes.\ | ||
Your app is ready to be deployed! | ||
|
||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | ||
Builds the app for production to the `build` folder. | ||
It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes. |
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,34 @@ | ||
import { container } from '../../di/config/brandi-config'; | ||
import { Tokens } from '../../di/config/brandi-tokens'; | ||
import { Field } from '../../validation/validation-field'; | ||
import { isNone } from './maybe'; | ||
|
||
describe('field groups supplier tests', () => { | ||
const fieldGroupsSupplier = container.get(Tokens.FieldGroupsSupplier); | ||
|
||
it('should include all field groups found by naming convention', () => { | ||
const allActionValueFields = Object.keys(Field) | ||
.filter((i) => !isNaN(Number(i))) | ||
.map((field) => +field) | ||
.filter((field) => { | ||
const fieldName = Field[field]; | ||
const prefix = fieldName.startsWith('AC_'); | ||
const suffix = | ||
fieldName.endsWith('_RADIO') || | ||
fieldName.endsWith('_VALUE') || | ||
fieldName.endsWith('_VAR'); | ||
return prefix && suffix; | ||
}); | ||
|
||
const missing = allActionValueFields | ||
.map((field) => ({ | ||
name: Field[field], | ||
metadata: fieldGroupsSupplier.getGroupByField(field), | ||
})) | ||
.filter((result) => isNone(result.metadata)) | ||
.map((result) => result.name) | ||
.join(', '); | ||
|
||
expect(missing).toBeFalsy(); | ||
}); | ||
}); |
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,151 @@ | ||
import { VariableType } from '../../data/model/variable/variable-types'; | ||
import { ExhaustivenessFailureError } from '../../error/exhaustiveness-failure-error'; | ||
import { | ||
ActionValueFieldGroup, | ||
groupFieldsOf, | ||
} from '../../ui/model/action/action-value-type-name-group'; | ||
import { | ||
bringAppPathGroup, | ||
bringAppStarDirGroup, | ||
bringAppTitleGroup, | ||
} from '../../ui/model/action/bring-app/bring-app-action-value-field-group'; | ||
import { mimicWordsGroup } from '../../ui/model/action/mimic/mimic-action-value-field-group'; | ||
import { | ||
mMoveXGroup, | ||
mMoveYGroup, | ||
mMouseButtonGroup, | ||
mPauseGroup, | ||
mRepeatGroup, | ||
mDirectionGroup, | ||
} from '../../ui/model/action/mouse/mouse-action-value-field-groups'; | ||
import { pSecondsGroup } from '../../ui/model/action/pause/pause-action-value-field-group'; | ||
import { | ||
skDirectionGroup, | ||
skInnerPauseGroup, | ||
skKeyToSendGroup, | ||
skOuterPauseGroup, | ||
skRepeatGroup, | ||
} from '../../ui/model/action/send-key/send-key-action-value-field-groups'; | ||
import { stTextGroup } from '../../ui/model/action/send-text/send-text-action-value-field-group'; | ||
import { | ||
wfwExecutableGroup, | ||
wfwTitleGroup, | ||
wfwWaitSecondsGroup, | ||
} from '../../ui/model/action/wait-for-window/wait-for-window-action-value-field-group'; | ||
import { Field } from '../../validation/validation-field'; | ||
import { maybe, Maybe } from './maybe'; | ||
|
||
export const enum FieldMetaDataType { | ||
TEXT, | ||
NUMBER, | ||
ENUM, | ||
ANY, | ||
} | ||
|
||
interface AbstractFieldMetaData { | ||
type: FieldMetaDataType; | ||
fields: Field[]; | ||
} | ||
|
||
interface NumberFieldMetaData extends AbstractFieldMetaData { | ||
type: FieldMetaDataType.NUMBER; | ||
min?: number; | ||
} | ||
|
||
interface NonNumberFieldMetaData extends AbstractFieldMetaData { | ||
type: FieldMetaDataType.TEXT | FieldMetaDataType.ENUM | FieldMetaDataType.ANY; | ||
} | ||
|
||
/** Can't just use ActionValueFieldGroup b/c type of CFA parameters is not static. */ | ||
type FieldMetaData = NumberFieldMetaData | NonNumberFieldMetaData; | ||
|
||
/** Since field metadata is needed by validators and elsewhere, need a way to query it. | ||
* By convention, the exhaustiveness-checking unit test for this will depend on | ||
* a naming convention for now. | ||
*/ | ||
export type FieldGroupsSupplier = { | ||
getGroupByField: (field: Field) => Maybe<FieldMetaData>; | ||
getAllGroups(): FieldMetaData[]; | ||
}; | ||
|
||
export class DefaultFieldGroupsSupplier implements FieldGroupsSupplier { | ||
private map: Map<Field, FieldMetaData>; | ||
constructor() { | ||
this.map = new Map(); | ||
} | ||
|
||
getGroupByField(field: Field): Maybe<FieldMetaData> { | ||
if (!this.map.size) { | ||
this.constructMapOnce(); | ||
} | ||
return maybe(this.map.get(field)); | ||
} | ||
|
||
getAllGroups(): FieldMetaData[] { | ||
return [ | ||
this.convertFieldGroup(bringAppPathGroup), | ||
this.convertFieldGroup(bringAppTitleGroup), | ||
this.convertFieldGroup(bringAppStarDirGroup), | ||
this.createCFAMetadata(), | ||
this.convertFieldGroup(mimicWordsGroup), | ||
this.convertFieldGroup(mMoveXGroup), | ||
this.convertFieldGroup(mMoveYGroup), | ||
this.convertFieldGroup(mMouseButtonGroup), | ||
this.convertFieldGroup(mPauseGroup), | ||
this.convertFieldGroup(mRepeatGroup), | ||
this.convertFieldGroup(mDirectionGroup), | ||
this.convertFieldGroup(pSecondsGroup), | ||
this.convertFieldGroup(skKeyToSendGroup), | ||
this.convertFieldGroup(skOuterPauseGroup), | ||
this.convertFieldGroup(skInnerPauseGroup), | ||
this.convertFieldGroup(skRepeatGroup), | ||
this.convertFieldGroup(skDirectionGroup), | ||
this.convertFieldGroup(stTextGroup), | ||
this.convertFieldGroup(wfwTitleGroup), | ||
this.convertFieldGroup(wfwExecutableGroup), | ||
this.convertFieldGroup(wfwWaitSecondsGroup), | ||
]; | ||
} | ||
|
||
private createCFAMetadata() { | ||
return { | ||
type: FieldMetaDataType.ANY, | ||
fields: [ | ||
Field.AC_CALL_FUNC_PARAMETER_RADIO, | ||
Field.AC_CALL_FUNC_PARAMETER_VALUE, | ||
Field.AC_CALL_FUNC_PARAMETER_VAR, | ||
], | ||
}; | ||
} | ||
|
||
private constructMapOnce(): void { | ||
const all = this.getAllGroups(); | ||
for (const group of all) { | ||
group.fields.forEach((key) => this.map.set(key, group)); | ||
} | ||
} | ||
|
||
private convertFieldGroup(group: ActionValueFieldGroup): FieldMetaData { | ||
const groupType = group.type; | ||
switch (groupType) { | ||
case VariableType.Enum.TEXT: | ||
return { | ||
type: FieldMetaDataType.TEXT, | ||
fields: groupFieldsOf(group), | ||
}; | ||
case VariableType.Enum.NUMBER: | ||
return { | ||
type: FieldMetaDataType.NUMBER, | ||
fields: groupFieldsOf(group), | ||
min: group.min, | ||
}; | ||
case VariableType.Enum.ENUM: | ||
return { | ||
type: FieldMetaDataType.ENUM, | ||
fields: groupFieldsOf(group), | ||
}; | ||
default: | ||
throw new ExhaustivenessFailureError(groupType); | ||
} | ||
} | ||
} |
Oops, something went wrong.