1import { PluginState } from '../types'; 2import { SelectableValue } from '../types/select'; 3 4export interface RegistryItem { 5 id: string; // Unique Key -- saved in configs 6 name: string; // Display Name, can change without breaking configs 7 description?: string; 8 aliasIds?: string[]; // when the ID changes, we may want backwards compatibility ('current' => 'last') 9 10 /** 11 * Some extensions should not be user selectable 12 * like: 'all' and 'any' matchers; 13 */ 14 excludeFromPicker?: boolean; 15 16 /** 17 * Optional feature state 18 */ 19 state?: PluginState; 20} 21 22export interface RegistryItemWithOptions<TOptions = any> extends RegistryItem { 23 /** 24 * Convert the options to a string 25 */ 26 getOptionsDisplayText?: (options: TOptions) => string; 27 28 /** 29 * Default options used if nothing else is specified 30 */ 31 defaultOptions?: TOptions; 32} 33 34interface RegistrySelectInfo { 35 options: Array<SelectableValue<string>>; 36 current: Array<SelectableValue<string>>; 37} 38 39export class Registry<T extends RegistryItem> { 40 private ordered: T[] = []; 41 private byId = new Map<string, T>(); 42 private initialized = false; 43 44 constructor(private init?: () => T[]) {} 45 46 setInit = (init: () => T[]) => { 47 if (this.initialized) { 48 throw new Error('Registry already initialized'); 49 } 50 this.init = init; 51 }; 52 53 getIfExists(id: string | undefined): T | undefined { 54 if (!this.initialized) { 55 this.initialize(); 56 } 57 58 if (id) { 59 return this.byId.get(id); 60 } 61 62 return undefined; 63 } 64 65 private initialize() { 66 if (this.init) { 67 for (const ext of this.init()) { 68 this.register(ext); 69 } 70 } 71 this.sort(); 72 this.initialized = true; 73 } 74 75 get(id: string): T { 76 const v = this.getIfExists(id); 77 if (!v) { 78 throw new Error(`"${id}" not found in: ${this.list().map((v) => v.id)}`); 79 } 80 return v; 81 } 82 83 selectOptions(current?: string[], filter?: (ext: T) => boolean): RegistrySelectInfo { 84 if (!this.initialized) { 85 this.initialize(); 86 } 87 88 const select = { 89 options: [], 90 current: [], 91 } as RegistrySelectInfo; 92 93 const currentOptions: Record<string, SelectableValue<string>> = {}; 94 if (current) { 95 for (const id of current) { 96 currentOptions[id] = {}; 97 } 98 } 99 100 for (const ext of this.ordered) { 101 if (ext.excludeFromPicker) { 102 continue; 103 } 104 if (filter && !filter(ext)) { 105 continue; 106 } 107 108 const option = { 109 value: ext.id, 110 label: ext.name, 111 description: ext.description, 112 }; 113 114 if (ext.state === PluginState.alpha) { 115 option.label += ' (alpha)'; 116 } 117 118 select.options.push(option); 119 if (currentOptions[ext.id]) { 120 currentOptions[ext.id] = option; 121 } 122 } 123 124 if (current) { 125 // this makes sure we preserve the order of ids 126 select.current = Object.values(currentOptions); 127 } 128 129 return select; 130 } 131 132 /** 133 * Return a list of values by ID, or all values if not specified 134 */ 135 list(ids?: any[]): T[] { 136 if (!this.initialized) { 137 this.initialize(); 138 } 139 140 if (ids) { 141 const found: T[] = []; 142 for (const id of ids) { 143 const v = this.getIfExists(id); 144 if (v) { 145 found.push(v); 146 } 147 } 148 return found; 149 } 150 151 return this.ordered; 152 } 153 154 isEmpty(): boolean { 155 if (!this.initialized) { 156 this.initialize(); 157 } 158 159 return this.ordered.length === 0; 160 } 161 162 register(ext: T) { 163 if (this.byId.has(ext.id)) { 164 throw new Error('Duplicate Key:' + ext.id); 165 } 166 167 this.byId.set(ext.id, ext); 168 this.ordered.push(ext); 169 170 if (ext.aliasIds) { 171 for (const alias of ext.aliasIds) { 172 if (!this.byId.has(alias)) { 173 this.byId.set(alias, ext); 174 } 175 } 176 } 177 178 if (this.initialized) { 179 this.sort(); 180 } 181 } 182 183 private sort() { 184 // TODO sort the list 185 } 186} 187