localStorage-based preference manager for web apps, userscripts etc
npm install ts-preferencesP Object
Preference
NumericPreference
StringPreference
RangePreference
MultichoicePreference
sh
npm install ts-preferences --save
`
Why?
ts-preferences gives you an easy, safe way of defining and accessing preferences for your application, without a lot of boilerplate code.
You can only set and get preferences that actually exist, so no more hassle with preference keys.
And when requesting a preference value, you can always trust that you _will_ get something and that it will have the right type, even if something goes wrong with localStorage.
Usage
$3
This rather artificial example shows how preferences can be used with full type-safety:
`typescript
import { BooleanPreference, IntegerPreference, PreferenceManager } from "ts-preferences";
const P = {
replace_title: new BooleanPreference({
key: "replace_title",
label: "Replace title",
default: true,
description: "Replace boring page title",
}),
counter: new IntegerPreference({
key: "counter",
label: "Counter",
default: 0,
description: "Weird counter thingy",
}),
};
// Initialize a preference manager:
const Preferences = new PreferenceManager(P, "my-awesome-app-");
// Replace title if corresponding preference is true:
if (Preferences.get(P.replace_title)) {
document.title = "top kek";
}
const counter = Preferences.get(P.counter);
// Randomize background-color if saved counter value is a multiple of 5:
if (counter % 5 === 0) {
document.body.style.backgroundColor = rgb(${upTo(255)}, ${upTo(255)}, ${upTo(255)});
}
// Save a new counter value:
Preferences.set(P.counter, counter + 1);
function upTo(max: number): number {
return Math.round(Math.random() * max);
}
`
(A preference which is automatically changed on every page load probably doesn't make too much sense beyond demonstration purposes.)
$3
get, set and reset work as expected if _and only if_ p is in the PreferencesObject used to create the PreferenceManager.
That is, you can use all preferences in P, _and only those_, when talking to ts-preferences.
The following code compiles, but crashes:
`typescript
import { BooleanPreference, PreferenceManager } from "ts-preferences";
const forgedPreference = new BooleanPreference({
key: "foo",
label: "foo label",
default: true,
});
const P = {
foo: new BooleanPreference({
key: "foo",
label: "foo label",
default: true,
}),
};
const Preferences = new PreferenceManager(P, "my-awesome-app-");
Preferences.get(P.foo); // OK
Preferences.set(P.foo, false); // OK
Preferences.get(forgedPreference); // throws exception
Preferences.set(forgedPreference, false); // throws exception
`
(Note that, although forgedPreference and P.foo are identical, they are not _the same object_, which is what counts in this case.)
You should only use members of your P object as input to get, set and reset.
If your editor supports TypeScript, it will autocomplete available preferences for you when you type e.g. Preferences.get(P._).
You may of course give your P object any name you want.
$3
A PreferencesObject can contain not only preferences, but also preference groups.
A group is simply an object with these properties:
* label – a label for the group.
* _ – a PreferencesObject representing the group.
* dependencies? – a list of dependencies for the group.
* extras? – optional object that can be used for anything.
An example of grouped preferences:
`typescript
const P = {
video: {
label: "Video Settings",
_: {
vsync: new BooleanPreference({
key: "video_vsync",
label: "V-Sync",
default: false,
}),
textures: new MultichoicePreference({
key: "video_textures",
label: "Texture Quality",
default: 2,
options: [
{ value: 1, label: "Low", },
{ value: 2, label: "Medium", },
{ value: 3, label: "High", },
],
}),
},
},
audio: {
label: "Audio Settings",
_: {
doppler: new BooleanPreference({
key: "audio_doppler",
label: "Doppler Effect",
default: true,
}),
},
},
};
`
In this case, you might do something like this in your application:
`typescript
if (Preferences.get(P.video._.vsync)) {
// ...
}
`
$3
Things can go wrong when getting or setting preferences.
For example, localStorage may not be accessible, or the string saved therein may not parse to a value of the expected type.
To take care of these cases in a graceful way, define a response handler and give it as an argument to the PreferenceManager constructor.
Here is a very basic example:
`typescript
import { AllowedTypes, PreferenceManager, RequestSummary, Response, Status } from "ts-preferences";
const P = {
// ...
};
const Preferences = new PreferenceManager(P, "my-awesome-app-", loggingResponseHandler);
function loggingResponseHandler(summary: RequestSummary, preferences: PreferenceManager): Response {
const response = summary.response;
switch (response.status) {
case Status.OK:
break;
default:
console.warn( There was an error with preference '${summary.preference.key}'.);
}
return response;
}
`
If you don't define a response handler, you will get no indication whatsoever if something goes wrong (but you _will_ get valid preference values).
If you want to use another response handler for a specific transaction, you can use getWith or setWith:
`typescript
const value = Preferences.getWith(loggingResponseHandler, P.foo);
`
Upgrading to v2
$3
* init is removed. Use the PreferenceManager constructor instead.
* Specifying a response handler is optional (defaults to SIMPLE_RESPONSE_HANDLER).
* The provided localStorage prefix is used as is (i.e. "-preference-" is not appended anymore). You should append it yourself so your users' saved preferences are not reset.
v1:
`typescript
import * as TSPreferences from "ts-preferences";
const Preferences = TSPreferences.init(
P,
"my-awesome-app",
TSPreferences.SIMPLE_RESPONSE_HANDLER,
);
`
v2:
`typescript
import { PreferenceManager } from "ts-preferences";
const Preferences = new PreferenceManager(
P,
"my-awesome-app-preference-", // NB: "-preference-" appended!
);
`
$3
* Status.LOCALSTORAGE_ERROR is renamed to Status.STORAGE_ERROR.
v1:
`typescript
switch (response.status) {
// ...
case Status.LOCALSTORAGE_ERROR:
// ...
}
`
v2:
`typescript
switch (response.status) {
// ...
case Status.STORAGE_ERROR:
// ...
}
`
$3
* enabled is renamed to shouldBeAvailable.
v1:
`typescript
Preferences.enabled(p);
`
v2:
`typescript
Preferences.shouldBeAvailable(p);
`
$3
* HTML menu generation is removed. (It was basically just function application anyway.)
v1:
`typescript
const menu = Preferences.htmlMenu(generator);
`
v2:
`typescript
const menu = generator(P);
`
API Reference
Every preference takes an argument of type PreferenceData, which for the different preference types has the properties listed below.
$3
#### key: string
Used for saving preference values to localStorage.
Must be unique for every preference.
#### label: string
User-readable label to be displayed in a generated GUI.
#### default: T
Default value for the preference.
#### description?: string
Optional user-readable description to be displayed in a generated GUI.
Defaults to "".
#### constraints?: Constraint
Optional list of constraints that preference values must satisfy, in addition to any constraints included in the preference class.
Each constraint must be an object with these properties:
* requirement: (value: T) => boolean – the predicate that values must satisfy.
* message: (value: T) => string – an error message for when a value does not satisfy the predicate.
#### dependencies?: Dependency
Optional list of dependencies that can be used to indicate a dependency relation between preferences, which in turn can be used to enable or disable a preference in the GUI based on the values of other preferences.
Each dependency must be an object with these properties:
* preference: Preference – the preference depended on.
* condition: (value: T) => boolean – a predicate that the value of that preference must satisfy.
#### extras?: { readonly [key: string]: any }
Optional object that can be used for anything, for example styling a single preference.
Should be used with great care because it has no type-safety at all.
$3
#### multiline: boolean
Whether values may contain line breaks.
#### minLength?: number
Optional minimum length.
Defaults to 0.
#### maxLength?: number
Optional maximum length.
Defaults to Infinity.
$3
#### min: number
Minimum allowed value.
#### max: number
Maximum allowed value.
$3
#### options: MultichoicePreferenceOption
A list of available options.
Must contain at least two elements.
Each element must have these properties:
* label: string – a user-readable label for the option.
* value: T` – the value represented by the option.