react-native-control-center 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/app.plugin.js +14 -0
- package/cli/bin/rn-control-center.js +52 -0
- package/ios/ControlStoreRuntime.swift +81 -0
- package/ios/RNControlCenter.mm +28 -0
- package/ios/RNControlCenter.swift +195 -0
- package/lib/commonjs/cli/runGenerate.d.ts +22 -0
- package/lib/commonjs/cli/runGenerate.js +173 -0
- package/lib/commonjs/core/generate/entitlements.d.ts +17 -0
- package/lib/commonjs/core/generate/entitlements.js +31 -0
- package/lib/commonjs/core/generate/index.d.ts +30 -0
- package/lib/commonjs/core/generate/index.js +58 -0
- package/lib/commonjs/core/generate/plist.d.ts +13 -0
- package/lib/commonjs/core/generate/plist.js +37 -0
- package/lib/commonjs/core/generate/swift.d.ts +22 -0
- package/lib/commonjs/core/generate/swift.js +140 -0
- package/lib/commonjs/core/parseControls.d.ts +9 -0
- package/lib/commonjs/core/parseControls.js +206 -0
- package/lib/commonjs/core/sf-symbols-data.d.ts +3 -0
- package/lib/commonjs/core/sf-symbols-data.js +5373 -0
- package/lib/commonjs/core/templates/ButtonControl.swift.hbs +28 -0
- package/lib/commonjs/core/templates/ButtonIntent.swift.hbs +39 -0
- package/lib/commonjs/core/templates/ControlBundle.swift.hbs +17 -0
- package/lib/commonjs/core/templates/ControlStore.swift.hbs +149 -0
- package/lib/commonjs/core/templates/ToggleControl.swift.hbs +60 -0
- package/lib/commonjs/core/templates/ToggleIntent.swift.hbs +49 -0
- package/lib/commonjs/core/types.d.ts +14 -0
- package/lib/commonjs/core/types.js +17 -0
- package/lib/commonjs/core/validateSymbols.d.ts +15 -0
- package/lib/commonjs/core/validateSymbols.js +43 -0
- package/lib/commonjs/core/xcode/addSyncedFolder.d.ts +28 -0
- package/lib/commonjs/core/xcode/addSyncedFolder.js +71 -0
- package/lib/commonjs/core/xcode/addTarget.d.ts +25 -0
- package/lib/commonjs/core/xcode/addTarget.js +34 -0
- package/lib/commonjs/core/xcode/buildSettings.d.ts +14 -0
- package/lib/commonjs/core/xcode/buildSettings.js +57 -0
- package/lib/commonjs/core/xcode/embed.d.ts +16 -0
- package/lib/commonjs/core/xcode/embed.js +74 -0
- package/lib/commonjs/core/xcode/inspect.d.ts +29 -0
- package/lib/commonjs/core/xcode/inspect.js +87 -0
- package/lib/commonjs/core/xcode/linkFrameworks.d.ts +18 -0
- package/lib/commonjs/core/xcode/linkFrameworks.js +80 -0
- package/lib/commonjs/core/xcode/types.d.ts +121 -0
- package/lib/commonjs/core/xcode/types.js +7 -0
- package/lib/commonjs/core/xcode/wire.d.ts +27 -0
- package/lib/commonjs/core/xcode/wire.js +142 -0
- package/lib/commonjs/plugin/index.d.ts +43 -0
- package/lib/commonjs/plugin/index.js +177 -0
- package/lib/commonjs/src/ControlCenter.d.ts +34 -0
- package/lib/commonjs/src/ControlCenter.js +91 -0
- package/lib/commonjs/src/defineControls.d.ts +6 -0
- package/lib/commonjs/src/defineControls.js +10 -0
- package/lib/commonjs/src/hooks.d.ts +8 -0
- package/lib/commonjs/src/hooks.js +38 -0
- package/lib/commonjs/src/index.d.ts +5 -0
- package/lib/commonjs/src/index.js +9 -0
- package/lib/commonjs/src/sf-symbols.d.ts +8 -0
- package/lib/commonjs/src/sf-symbols.js +2 -0
- package/lib/commonjs/src/stateCache.d.ts +8 -0
- package/lib/commonjs/src/stateCache.js +36 -0
- package/lib/commonjs/src/types.d.ts +36 -0
- package/lib/commonjs/src/types.js +2 -0
- package/package.json +75 -0
- package/src/ControlCenter.ts +122 -0
- package/src/defineControls.ts +9 -0
- package/src/hooks.ts +42 -0
- package/src/index.ts +12 -0
- package/src/sf-symbols.ts +251 -0
- package/src/stateCache.ts +34 -0
- package/src/types.ts +36 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.defaultAppGroupId = defaultAppGroupId;
|
|
40
|
+
exports.collectStateKeys = collectStateKeys;
|
|
41
|
+
exports.pascalCase = pascalCase;
|
|
42
|
+
exports.generateSwiftFiles = generateSwiftFiles;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
46
|
+
function defaultAppGroupId(bundleId) {
|
|
47
|
+
return `group.${bundleId}.controls`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 토글 컨트롤들의 stateKey 목록.
|
|
51
|
+
* - 생성된 ControlStore.swift의 정적 화이트리스트로 박힘
|
|
52
|
+
* - Native Module의 snapshot() 정제를 위해 메인 앱 Info.plist
|
|
53
|
+
* ("RNControlCenterStateKeys")로도 주입됨
|
|
54
|
+
*/
|
|
55
|
+
function collectStateKeys(controls) {
|
|
56
|
+
return controls
|
|
57
|
+
.filter((c) => c.type === 'toggle')
|
|
58
|
+
.map((c) => c.stateKey);
|
|
59
|
+
}
|
|
60
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
61
|
+
const templateCache = {};
|
|
62
|
+
function loadTemplate(name) {
|
|
63
|
+
if (!templateCache[name]) {
|
|
64
|
+
const filePath = path.join(TEMPLATES_DIR, `${name}.hbs`);
|
|
65
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
66
|
+
templateCache[name] = handlebars_1.default.compile(source, { noEscape: true });
|
|
67
|
+
}
|
|
68
|
+
return templateCache[name];
|
|
69
|
+
}
|
|
70
|
+
// Handlebars 헬퍼 등록 (한 번만)
|
|
71
|
+
let helpersRegistered = false;
|
|
72
|
+
function registerHelpers() {
|
|
73
|
+
if (helpersRegistered)
|
|
74
|
+
return;
|
|
75
|
+
handlebars_1.default.registerHelper('pascalCase', (str) => pascalCase(str));
|
|
76
|
+
helpersRegistered = true;
|
|
77
|
+
}
|
|
78
|
+
function pascalCase(str) {
|
|
79
|
+
return str
|
|
80
|
+
.replace(/[-_]/g, ' ')
|
|
81
|
+
.replace(/(^|\s)(\w)/g, (_, __, c) => c.toUpperCase())
|
|
82
|
+
.replace(/\s/g, '');
|
|
83
|
+
}
|
|
84
|
+
function generateSwiftFiles(opts) {
|
|
85
|
+
registerHelpers();
|
|
86
|
+
const bundleStructName = opts.bundleStructName ?? 'ControlCenterBundle';
|
|
87
|
+
const appGroupId = opts.appGroupId ?? defaultAppGroupId(opts.bundleId);
|
|
88
|
+
const files = [];
|
|
89
|
+
// 1. Bundle
|
|
90
|
+
files.push({
|
|
91
|
+
path: 'ControlBundle.swift',
|
|
92
|
+
content: loadTemplate('ControlBundle.swift')({
|
|
93
|
+
bundleStructName,
|
|
94
|
+
controls: opts.controls,
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
// 1b. ControlStore (shared between targets)
|
|
98
|
+
// 토글의 stateKey 목록을 정적으로 박아넣어, snapshot()이 시스템 전역 키를
|
|
99
|
+
// 긁지 않고 우리가 관리하는 상태 키만 정확히 반환하게 한다. (Week 6)
|
|
100
|
+
const stateKeys = collectStateKeys(opts.controls);
|
|
101
|
+
files.push({
|
|
102
|
+
path: 'ControlStore.swift',
|
|
103
|
+
content: loadTemplate('ControlStore.swift')({ appGroupId, stateKeys }),
|
|
104
|
+
});
|
|
105
|
+
// 2. Controls + Intents (컨트롤당 2파일)
|
|
106
|
+
for (const control of opts.controls) {
|
|
107
|
+
if (control.type === 'button') {
|
|
108
|
+
files.push({
|
|
109
|
+
path: `Controls/${pascalCase(control.id)}Control.swift`,
|
|
110
|
+
content: loadTemplate('ButtonControl.swift')({
|
|
111
|
+
...control,
|
|
112
|
+
bundleId: opts.bundleId,
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
files.push({
|
|
116
|
+
path: `Intents/${pascalCase(control.id)}Intent.swift`,
|
|
117
|
+
content: loadTemplate('ButtonIntent.swift')({
|
|
118
|
+
...control,
|
|
119
|
+
deepLink: control.deepLink ?? `${opts.urlScheme}://control/${control.id}`,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (control.type === 'toggle') {
|
|
124
|
+
files.push({
|
|
125
|
+
path: `Controls/${pascalCase(control.id)}Control.swift`,
|
|
126
|
+
content: loadTemplate('ToggleControl.swift')({
|
|
127
|
+
...control,
|
|
128
|
+
bundleId: opts.bundleId,
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
files.push({
|
|
132
|
+
path: `Intents/${pascalCase(control.id)}Intent.swift`,
|
|
133
|
+
content: loadTemplate('ToggleIntent.swift')({
|
|
134
|
+
...control,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return files;
|
|
140
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ParsedControl } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* TS 파일을 읽어 defineControls({...}) 호출을 찾고 컨트롤 배열을 추출.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseControlsFile(filePath: string): ParsedControl[];
|
|
6
|
+
/**
|
|
7
|
+
* 소스 문자열을 받아 파싱 (테스트에 편리).
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseControlsSource(source: string, filePath?: string): ParsedControl[];
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.parseControlsFile = parseControlsFile;
|
|
40
|
+
exports.parseControlsSource = parseControlsSource;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const parser_1 = require("@babel/parser");
|
|
43
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
44
|
+
const types_1 = require("./types");
|
|
45
|
+
// @babel/traverse는 ESM/CJS 혼합 때문에 .default가 있을 수 있음
|
|
46
|
+
const traverse = typeof traverse_1.default === 'function' ? traverse_1.default : traverse_1.default.default;
|
|
47
|
+
/**
|
|
48
|
+
* TS 파일을 읽어 defineControls({...}) 호출을 찾고 컨트롤 배열을 추출.
|
|
49
|
+
*/
|
|
50
|
+
function parseControlsFile(filePath) {
|
|
51
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
52
|
+
return parseControlsSource(source, filePath);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 소스 문자열을 받아 파싱 (테스트에 편리).
|
|
56
|
+
*/
|
|
57
|
+
function parseControlsSource(source, filePath = '<source>') {
|
|
58
|
+
let ast;
|
|
59
|
+
try {
|
|
60
|
+
ast = (0, parser_1.parse)(source, {
|
|
61
|
+
sourceType: 'module',
|
|
62
|
+
plugins: ['typescript'],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new types_1.ParseError(`Failed to parse TypeScript: ${err.message}`, filePath);
|
|
67
|
+
}
|
|
68
|
+
let defineCallArg = null;
|
|
69
|
+
traverse(ast, {
|
|
70
|
+
CallExpression(path) {
|
|
71
|
+
const callee = path.node.callee;
|
|
72
|
+
if (callee.type !== 'Identifier' || callee.name !== 'defineControls')
|
|
73
|
+
return;
|
|
74
|
+
const firstArg = path.node.arguments[0];
|
|
75
|
+
if (!firstArg || firstArg.type !== 'ObjectExpression') {
|
|
76
|
+
throw new types_1.ParseError('defineControls() must receive an object literal as its first argument.', filePath, path.node.loc?.start.line, path.node.loc?.start.column);
|
|
77
|
+
}
|
|
78
|
+
defineCallArg = firstArg;
|
|
79
|
+
path.stop();
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
if (!defineCallArg) {
|
|
83
|
+
throw new types_1.ParseError('No defineControls({...}) call found in file.', filePath);
|
|
84
|
+
}
|
|
85
|
+
return extractControls(defineCallArg, filePath);
|
|
86
|
+
}
|
|
87
|
+
function extractControls(obj, filePath) {
|
|
88
|
+
const controls = [];
|
|
89
|
+
for (const prop of obj.properties) {
|
|
90
|
+
if (prop.type !== 'ObjectProperty') {
|
|
91
|
+
throw new types_1.ParseError(`Unsupported property kind "${prop.type}" inside defineControls. Use plain key: value pairs only.`, filePath, prop.loc?.start.line, prop.loc?.start.column);
|
|
92
|
+
}
|
|
93
|
+
const id = getKeyName(prop, filePath);
|
|
94
|
+
if (prop.value.type !== 'ObjectExpression') {
|
|
95
|
+
throw new types_1.ParseError(`Control "${id}" must be an object literal.`, filePath, prop.value.loc?.start.line, prop.value.loc?.start.column);
|
|
96
|
+
}
|
|
97
|
+
const configRaw = objectExpressionToLiteral(prop.value, filePath);
|
|
98
|
+
controls.push(validateControl(id, configRaw, filePath));
|
|
99
|
+
}
|
|
100
|
+
return controls;
|
|
101
|
+
}
|
|
102
|
+
function getKeyName(prop, filePath) {
|
|
103
|
+
if (prop.key.type === 'Identifier')
|
|
104
|
+
return prop.key.name;
|
|
105
|
+
if (prop.key.type === 'StringLiteral')
|
|
106
|
+
return prop.key.value;
|
|
107
|
+
throw new types_1.ParseError(`Control key must be a plain identifier or string literal.`, filePath, prop.key.loc?.start.line, prop.key.loc?.start.column);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* ObjectExpression / ArrayExpression / Literal을 일반 JS 값으로 재귀 변환.
|
|
111
|
+
* literal 값만 허용 — 변수 참조/함수 호출 발견 시 에러.
|
|
112
|
+
*/
|
|
113
|
+
function objectExpressionToLiteral(node, filePath) {
|
|
114
|
+
switch (node.type) {
|
|
115
|
+
case 'StringLiteral':
|
|
116
|
+
case 'NumericLiteral':
|
|
117
|
+
case 'BooleanLiteral':
|
|
118
|
+
return node.value;
|
|
119
|
+
case 'NullLiteral':
|
|
120
|
+
return null;
|
|
121
|
+
case 'TemplateLiteral':
|
|
122
|
+
if (node.expressions.length > 0) {
|
|
123
|
+
throw literalOnly(node, filePath, 'template string with ${} interpolation');
|
|
124
|
+
}
|
|
125
|
+
return node.quasis.map((q) => q.value.cooked).join('');
|
|
126
|
+
case 'ArrayExpression':
|
|
127
|
+
return node.elements.map((el) => {
|
|
128
|
+
if (el === null)
|
|
129
|
+
return null;
|
|
130
|
+
if (el.type === 'SpreadElement')
|
|
131
|
+
throw literalOnly(el, filePath, 'spread element');
|
|
132
|
+
return objectExpressionToLiteral(el, filePath);
|
|
133
|
+
});
|
|
134
|
+
case 'ObjectExpression': {
|
|
135
|
+
const out = {};
|
|
136
|
+
for (const prop of node.properties) {
|
|
137
|
+
if (prop.type !== 'ObjectProperty') {
|
|
138
|
+
throw literalOnly(prop, filePath, prop.type);
|
|
139
|
+
}
|
|
140
|
+
const key = prop.key.type === 'Identifier'
|
|
141
|
+
? prop.key.name
|
|
142
|
+
: prop.key.type === 'StringLiteral'
|
|
143
|
+
? prop.key.value
|
|
144
|
+
: null;
|
|
145
|
+
if (key === null) {
|
|
146
|
+
throw literalOnly(prop.key, filePath, 'computed key');
|
|
147
|
+
}
|
|
148
|
+
out[key] = objectExpressionToLiteral(prop.value, filePath);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
case 'Identifier':
|
|
153
|
+
throw literalOnly(node, filePath, `variable reference "${node.name}"`);
|
|
154
|
+
case 'CallExpression':
|
|
155
|
+
throw literalOnly(node, filePath, 'function call');
|
|
156
|
+
case 'MemberExpression':
|
|
157
|
+
throw literalOnly(node, filePath, 'member access');
|
|
158
|
+
default:
|
|
159
|
+
throw literalOnly(node, filePath, node.type);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function literalOnly(node, filePath, kind) {
|
|
163
|
+
return new types_1.ParseError(`Only literal values allowed — found ${kind}.`, filePath, node.loc?.start.line, node.loc?.start.column);
|
|
164
|
+
}
|
|
165
|
+
// 최소 검증 — 더 엄격한 검증은 validateControls 단계에서
|
|
166
|
+
function validateControl(id, raw, filePath) {
|
|
167
|
+
const type = raw['type'];
|
|
168
|
+
if (type === 'button') {
|
|
169
|
+
if (typeof raw['title'] !== 'string')
|
|
170
|
+
throw missingField(id, 'title', filePath);
|
|
171
|
+
if (typeof raw['icon'] !== 'string')
|
|
172
|
+
throw missingField(id, 'icon', filePath);
|
|
173
|
+
return {
|
|
174
|
+
id,
|
|
175
|
+
type: 'button',
|
|
176
|
+
title: raw['title'],
|
|
177
|
+
icon: raw['icon'],
|
|
178
|
+
...(raw['tint'] !== undefined && { tint: raw['tint'] }),
|
|
179
|
+
...(raw['description'] !== undefined && { description: raw['description'] }),
|
|
180
|
+
...(raw['deepLink'] !== undefined && { deepLink: raw['deepLink'] }),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (type === 'toggle') {
|
|
184
|
+
if (typeof raw['title'] !== 'string')
|
|
185
|
+
throw missingField(id, 'title', filePath);
|
|
186
|
+
if (typeof raw['stateKey'] !== 'string')
|
|
187
|
+
throw missingField(id, 'stateKey', filePath);
|
|
188
|
+
const icons = raw['icons'];
|
|
189
|
+
if (!icons || typeof icons.on !== 'string' || typeof icons.off !== 'string') {
|
|
190
|
+
throw new types_1.ParseError(`Toggle "${id}" requires icons.on and icons.off as strings.`, filePath);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
id,
|
|
194
|
+
type: 'toggle',
|
|
195
|
+
title: raw['title'],
|
|
196
|
+
icons: { on: icons.on, off: icons.off },
|
|
197
|
+
stateKey: raw['stateKey'],
|
|
198
|
+
...(raw['tint'] !== undefined && { tint: raw['tint'] }),
|
|
199
|
+
...(raw['description'] !== undefined && { description: raw['description'] }),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
throw new types_1.ParseError(`Control "${id}" has invalid type "${String(type)}". Expected "button" or "toggle".`, filePath);
|
|
203
|
+
}
|
|
204
|
+
function missingField(id, field, filePath) {
|
|
205
|
+
return new types_1.ParseError(`Control "${id}" is missing required field "${field}".`, filePath);
|
|
206
|
+
}
|