Merge branch 'config_api'

This commit is contained in:
Oliver Blanthorn 2018-12-13 13:03:42 +00:00
commit be2618c352
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
5 changed files with 145 additions and 38 deletions

View file

@ -8,6 +8,9 @@ export function toSimpleType(typeNode) {
switch (typeNode.kind) {
case ts.SyntaxKind.VoidKeyword:
return new AllTypes.VoidType()
// IndexedAccessTypes are things like `fn<T keyof Class>(x: T, y: Class[T])`
// This doesn't seem to be easy to deal with so let's kludge it for now
case ts.SyntaxKind.IndexedAccessType:
case ts.SyntaxKind.AnyKeyword:
return new AllTypes.AnyType()
case ts.SyntaxKind.BooleanKeyword:
@ -26,7 +29,9 @@ export function toSimpleType(typeNode) {
if (!typeNode.typeArguments) {
// If there are no typeArguments, this is not a parametric type and we can return the type directly
try {
return toSimpleType(typeNode.typeName.symbol.declarations[0].type)
return toSimpleType(
typeNode.typeName.symbol.declarations[0].type,
)
} catch (e) {
// Fall back to what you'd do with typeArguments
}
@ -47,16 +52,21 @@ export function toSimpleType(typeNode) {
toSimpleType(typeNode.type),
)
case ts.SyntaxKind.TypeLiteral:
let members = typeNode.members.map(member => {
if (member.kind == ts.SyntaxKind.IndexSignature) {
// Something like this: { [str: string]: string[] }
return ["", toSimpleType(member.type)]
}
// Very fun feature: when you have an object literal with >20 members, typescript will decide to replace some of them with a "... X more ..." node that obviously doesn't have a corresponding symbol, hence this check and the filter after the map
if (member.name.symbol)
return [member.name.symbol.escapedName, toSimpleType(member.type)];
}).filter(m => m)
return new AllTypes.ObjectType(new Map(members));
let members = typeNode.members
.map(member => {
if (member.kind == ts.SyntaxKind.IndexSignature) {
// Something like this: { [str: string]: string[] }
return ["", toSimpleType(member.type)]
}
// Very fun feature: when you have an object literal with >20 members, typescript will decide to replace some of them with a "... X more ..." node that obviously doesn't have a corresponding symbol, hence this check and the filter after the map
if (member.name.symbol)
return [
member.name.symbol.escapedName,
toSimpleType(member.type),
]
})
.filter(m => m)
return new AllTypes.ObjectType(new Map(members))
case ts.SyntaxKind.ArrayType:
return new AllTypes.ArrayType(toSimpleType(typeNode.elementType))
case ts.SyntaxKind.TupleType:
@ -71,6 +81,8 @@ export function toSimpleType(typeNode) {
case ts.SyntaxKind.LiteralType:
return new AllTypes.LiteralTypeType(typeNode.literal.text)
break
case ts.SyntaxKind.IndexedAccessType:
default:
console.log(typeNode)
throw new Error(`Unhandled kind (${typeNode.kind}) for ${typeNode}`)
@ -89,10 +101,22 @@ function isNodeExported(node: ts.Node): boolean {
/** True if node is marked as @hidden in its documentation */
function isNodeHidden(sourceFile, node): boolean {
return sourceFile && node.jsDoc && !!node.jsDoc.find(doc => sourceFile.text.slice(doc.pos, doc.end).search("@hidden") != -1)
return (
sourceFile &&
node.jsDoc &&
!!node.jsDoc.find(
doc =>
sourceFile.text.slice(doc.pos, doc.end).search("@hidden") != -1,
)
)
}
function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, node: any) {
function visit(
checker: any,
sourceFile: any,
file: AllMetadata.FileMetadata,
node: any,
) {
let symbol = checker.getSymbolAtLocation(node.name)
if (symbol && isNodeExported(node)) {
let nodeName = symbol.escapedName
@ -114,7 +138,11 @@ function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, no
: new AllTypes.AnyType()
file.setFunction(
nodeName,
new AllMetadata.SymbolMetadata(doc, t, isNodeHidden(sourceFile, node)),
new AllMetadata.SymbolMetadata(
doc,
t,
isNodeHidden(sourceFile, node),
),
)
return
@ -145,7 +173,11 @@ function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, no
: new AllTypes.AnyType()
clazz.setMember(
name,
new AllMetadata.SymbolMetadata(doc, t, isNodeHidden(sourceFile, node)),
new AllMetadata.SymbolMetadata(
doc,
t,
isNodeHidden(sourceFile, node),
),
)
})
return
@ -202,7 +234,8 @@ function generateMetadata(
}
// We need to specify Type itself because it won't exist in AllTypes.js since it's an interface
let imports = `import { Type } from "../compiler/types/AllTypes"\n` +
let imports =
`import { Type } from "../compiler/types/AllTypes"\n` +
`import {${Object.keys(AllTypes).join(
", ",
)}} from "../compiler/types/AllTypes"\n` +

View file

@ -79,13 +79,11 @@ function removeCSPListener() {
config.getAsync("csp").then(csp => csp === "clobber" && addCSPListener())
browser.storage.onChanged.addListener((changes, areaname) => {
if ("userconfig" in changes) {
if (changes.userconfig.newValue.csp === "clobber") {
addCSPListener()
} else {
removeCSPListener()
}
config.addChangeListener("csp", (old, cur) => {
if (cur === "clobber") {
addCSPListener()
} else {
removeCSPListener()
}
})

View file

@ -14,14 +14,9 @@ async function getDuration() {
opts.duration = await config.getAsync("scrollduration")
return opts.duration
}
browser.storage.onChanged.addListener(changes => {
if ("userconfig" in changes) {
if ("smoothscroll" in changes.userconfig.newValue)
opts.smooth = changes.userconfig.newValue["smoothscroll"]
if ("scrollduration" in changes.userconfig.newValue)
opts.duration = changes.userconfig.newValue["scrollduration"]
}
})
config.addChangeListener("smoothscroll", (prev, cur) => opts.smooth = cur)
config.addChangeListener("scrollduration", (prev, cur) => opts.duration = cur)
class ScrollingData {
// time at which the scrolling animation started

View file

@ -80,12 +80,7 @@ function retheme() {
})
}
// Hacky listener
browser.storage.onChanged.addListener((changes, areaname) => {
if ("userconfig" in changes) {
retheme()
}
})
config.addChangeListener("theme", retheme)
// Sometimes pages will overwrite class names of elements. We use a MutationObserver to make sure that the HTML element always has a TridactylTheme class
// We can't just call theme() because it would first try to remove class names from the element, which would trigger the MutationObserver before we had a chance to add the theme class and thus cause infinite recursion

View file

@ -810,6 +810,9 @@ function setDeepProperty(obj, value, target) {
}
}
/** @hidden
* Merges two objects and any child objects they may have
*/
export function mergeDeep(o1, o2) {
let r = Array.isArray(o1) ? o1.slice() : Object.create(o1)
Object.assign(r, o1, o2)
@ -820,7 +823,11 @@ export function mergeDeep(o1, o2) {
return r
}
export function getURL(url, target) {
/** @hidden
* Gets a site-specific setting.
*/
export function getURL(url: string, target: string[]) {
if (!USERCONFIG.subconfigs) return undefined
// For each key
return (
@ -1145,16 +1152,95 @@ async function init() {
}
}
/** @hidden */
const changeListeners = new Map()
/** @hidden
* @param name The name of a "toplevel" config setting (i.e. "nmaps", not "nmaps.j")
* @param listener A function to call when the value of $name is modified in the config. Takes the previous and new value as parameters.
*/
export function addChangeListener<P extends keyof default_config>(
name: P,
listener: (old: default_config[P], neww: default_config[P]) => void,
) {
let arr = changeListeners.get(name)
if (!arr) {
arr = []
changeListeners.set(name, arr)
}
arr.push(listener)
}
/** @hidden
* Removes event listeners set with addChangeListener
*/
export function removeChangeListener<P extends keyof default_config>(
name: P,
listener: (old: default_config[P], neww: default_config[P]) => void,
) {
let arr = changeListeners.get(name)
if (!arr) return
let i = arr.indexOf(listener)
if (i >= 0) arr.splice(i, 1)
}
// Listen for changes to the storage and update the USERCONFIG if appropriate.
// TODO: BUG! Sync and local storage are merged at startup, but not by this thing.
browser.storage.onChanged.addListener(async (changes, areaname) => {
if (CONFIGNAME in changes) {
let defaultConf = new default_config()
// newValue is undefined when calling browser.storage.AREANAME.clear()
if (changes[CONFIGNAME].newValue !== undefined) {
// A key has been :unset if it exists in USERCONFIG and doesn't in changes and if its value in USERCONFIG is different from the one it has in default_config
let unsetKeys = Object.keys(USERCONFIG).filter(
k =>
changes[CONFIGNAME].newValue[k] === undefined &&
JSON.stringify(USERCONFIG[k]) !=
JSON.stringify(defaultConf[k]),
)
// A key has changed if it is defined in USERCONFIG and its value in USERCONFIG is different from the one in `changes` or if the value in defaultConf is different from the one in `changes`
let changedKeys = Object.keys(changes[CONFIGNAME].newValue).filter(
k =>
JSON.stringify(
USERCONFIG[k] !== undefined
? USERCONFIG[k]
: defaultConf[k],
) != JSON.stringify(changes[CONFIGNAME].newValue[k]),
)
let old = USERCONFIG
USERCONFIG = changes[CONFIGNAME].newValue
// Trigger listeners
unsetKeys.forEach(key => {
let arr = changeListeners.get(key)
if (arr) {
arr.forEach(f => f(old[key], defaultConf[key]))
}
})
changedKeys.forEach(key => {
let arr = changeListeners.get(key)
if (arr) {
let v = old[key] === undefined ? defaultConf[key] : old[key]
arr.forEach(f => f(v, USERCONFIG[key]))
}
})
} else if (areaname === (await get("storageloc"))) {
// If newValue is undefined and AREANAME is the same value as STORAGELOC, the user wants to clean their config
let old = USERCONFIG
USERCONFIG = o({})
Object.keys(old)
.filter(key => old[key] != defaultConf[key])
.forEach(key => {
let arr = changeListeners.get(key)
if (arr) {
arr.forEach(f => f(old[key], defaultConf[key]))
}
})
}
}
})