@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
133 lines (131 loc) • 4.29 kB
JavaScript
/*
* Copyright (C) 2022 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Takes a dictionary with explicit keys and returns an object with three type-safe properties:
*
* - `byKey`: The original dictionary, but with the key of each value added to it as `[keyProp]`
* - `values`: An array of the values in the original dictionary
* - `keys`: An array of the keys in the original dictionary
* - `TKey`: A placeholder value that holds the type of the keys
* - `TValue`: A placeholder value that holds the type of the values
*
* This is useful to add explicit type checking to the pattern where a literal dictionary is used to define
* a relationship between a known set of keys to some value type.
*
* For example:
*
* ```
* const {
* byKey: itemDict,
* values: items,
* keys: itemSlugs,
* } = typedKeyDict(
* {
* undo: {
* label: 'Undo',
* description: 'Undo the last action',
* performAction: () => {
* // Do work
* },
* },
* redo: {
* label: 'Redo',
* description: 'Redo the last undone action, or repeat the last action',
* performAction: () => {
* // Do work
* },
* },
* },
* 'slug'
* )
*
* // Values of itemDict now have a `slug` property with type `'undo' | 'redo'`
*
* const slug: 'undo' | 'redo' = itemDict.undo.slug
*
* // Key and value iteration are now type safe:
*
* items.forEach(item => {
* // item is the correct type, unlike what Object.values gives
* })
*
* itemSlugs.forEach(slug => {
* // slug is the correct type ('undo' | 'redo'), unlike what Object.values gives
* })
*
* ```
*
* Additionally, explicit type arguments can be provided, ensuring that an interface is implemented, or that all keys
* in a union type are implemented.
*
* For example:
*
* ```
* type ItemSlug = 'undo' | 'redo'
*
* interface ActionItem {
* label: string
* description: string
* performAction: () => void
* }
*
* typedKeyDict<ItemSlug, ActionItem, 'slug'>(
* {
* undo: {
* label: 'Undo',
* description: 'Undo the last action',
* performAction: () => {
* // Do work
* },
* },
* },
* 'key'
* )
* ```
*
* Will not compile, because `redo` is not specified in the map, but is defined as a valid value for `ItemSlug`.
* This can be very helpful if `ItemSlug` is defined in another file. Adding an additional key to it will force
* you to fix anywhere it is used in this way.
*
* @param dict The input dictionary
* @param keyProp The name of the property to add to the resulting objects containing the key for each value. Defaults to 'key'
*/
export function typedKeyDict(dict, keyProp) {
// Manually handle the default value of 'key'. TypeScript default values can't be used due to trickiness with the
// type inference.
const keyPropStr = keyProp === undefined ? 'key' : keyProp;
// Add the key to the values in the map
const entriesWithKey = Object.entries(dict).map(entry => {
const key = entry[0];
const value = entry[1];
return [key, {
...value,
[keyPropStr]: key
}];
});
return {
byKey: Object.fromEntries(entriesWithKey),
keys: entriesWithKey.map(it => it[0]),
values: entriesWithKey.map(it => it[1]),
// Note: We're cheating here to assign an undefined value to a property with a type that doesn't include undefined.
// This is done so that TKey and TValue can have the right type, making using them as easy as `typeof info.TKey`
// The properties are not meant to be used in any other way
TKey: undefined,
TValue: undefined
};
}