@zcomponent/core 0.0.31-beta → 0.1.0-beta

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/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { ValuesManager } from './values/values';
1
2
  export * from './component';
2
3
  export * from './behavior';
3
4
  export * from './actionbehavior';
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { ValuesManager } from './values/values';
1
2
  export * from './component';
2
3
  export * from './behavior';
3
4
  export * from './actionbehavior';
@@ -0,0 +1,13 @@
1
+ export declare enum DeviceType {
2
+ DESKTOP = "desktop",
3
+ MOBILE = "mobile"
4
+ }
5
+ export declare class Profile {
6
+ isDesktop: boolean;
7
+ muteByDefault: boolean;
8
+ clientLanguage: string;
9
+ urlSearchParams: URLSearchParams;
10
+ debugMode: boolean;
11
+ constructor();
12
+ get userAgent(): DeviceType;
13
+ }
package/lib/profile.js ADDED
@@ -0,0 +1,26 @@
1
+ export var DeviceType;
2
+ (function (DeviceType) {
3
+ DeviceType["DESKTOP"] = "desktop";
4
+ DeviceType["MOBILE"] = "mobile";
5
+ })(DeviceType || (DeviceType = {}));
6
+ function getDeviceType() {
7
+ const userAgent = window.navigator.userAgent;
8
+ const mobileKeywords = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
9
+ if (mobileKeywords.test(userAgent)) {
10
+ return DeviceType.MOBILE;
11
+ }
12
+ return DeviceType.DESKTOP;
13
+ }
14
+ export class Profile {
15
+ constructor() {
16
+ this.muteByDefault = false;
17
+ this.clientLanguage = window.navigator.language;
18
+ this.debugMode = false;
19
+ this.urlSearchParams = new URLSearchParams(window.location.search);
20
+ this.debugMode = this.urlSearchParams.has('debug') && this.urlSearchParams.get('debug') === 'true';
21
+ this.isDesktop = getDeviceType() === DeviceType.DESKTOP;
22
+ }
23
+ get userAgent() {
24
+ return this.isDesktop ? DeviceType.DESKTOP : DeviceType.MOBILE;
25
+ }
26
+ }
@@ -1,6 +1,7 @@
1
1
  import { BehaviorByID, EntityPropOverride, Import, NodeByID, ParsedImport, Props, ZComponentData } from './data/core';
2
2
  import * as Data from './data';
3
3
  import { Layer } from './animation';
4
+ import { ZValues } from './values/values';
4
5
  export declare function parseImport(i: Import): ParsedImport;
5
6
  export declare function constructImport(from: string, destFile: string, imp: string): string;
6
7
  export declare function getScriptName(n: string, requireUniqueIn: string[] | {
@@ -32,3 +33,4 @@ export declare function propertyTracksByEntityID(clip: Data.Clip): {
32
33
  };
33
34
  export declare function getBehaviorBlockForNode(behaviors: BehaviorByID, behaviorIDs: string[], importMapping: Map<string, string>): string;
34
35
  export declare function importNameWithoutExt(imp: string): string;
36
+ export declare const typeDefinitionForZValues: (values: ZValues, url: string) => string;
package/lib/selectors.js CHANGED
@@ -2,6 +2,7 @@ import { EntityPropOverrideType } from './data/core';
2
2
  import * as path from 'path';
3
3
  import { outputForType } from './types';
4
4
  import * as Data from './data';
5
+ import * as VALUES from './values/valuesmutator';
5
6
  import { computeBehaviorHierarchy } from './data';
6
7
  export function parseImport(i) {
7
8
  if (typeof i !== 'string')
@@ -221,7 +222,9 @@ export const typeDefinitionForComponent = (nodes, props, constructorProps, scrip
221
222
  ${importStrings.join('\n')}
222
223
 
223
224
  interface ConstructorProps {
224
- ${Object.values(constructorProps ?? {}).map(typeOutputForConstructorProp).join('\n\n')}
225
+ ${Object.values(constructorProps ?? {})
226
+ .map(typeOutputForConstructorProp)
227
+ .join('\n\n')}
225
228
  }
226
229
 
227
230
  /**
@@ -255,12 +258,16 @@ export default Comp;
255
258
  function typeForAnimation(layers) {
256
259
  const layersWithScriptNames = Object.values(layers).filter(l => l?.scriptName !== undefined);
257
260
  return `Animation & { layers: {
258
- ${Object.values(layersWithScriptNames).map(layer => {
261
+ ${Object.values(layersWithScriptNames)
262
+ .map(layer => {
259
263
  const clipsWithScriptNames = Object.values(layer.clips).filter(lc => lc?.scriptName !== undefined);
260
264
  return `\t\t${layer.scriptName ?? '""'}: Layer & { clips: {
261
- ${Object.values(clipsWithScriptNames).map(lc => `\t\t\t${lc.scriptName ?? '""'}: LayerClip;`).join('\n')}
265
+ ${Object.values(clipsWithScriptNames)
266
+ .map(lc => `\t\t\t${lc.scriptName ?? '""'}: LayerClip;`)
267
+ .join('\n')}
262
268
  }};`;
263
- }).join('\n')}
269
+ })
270
+ .join('\n')}
264
271
  }};`;
265
272
  }
266
273
  function makeCommentSafe(c) {
@@ -268,7 +275,7 @@ function makeCommentSafe(c) {
268
275
  }
269
276
  function getJSDocForProp(prop) {
270
277
  let lines = [];
271
- for (const comment of (prop.comments ?? [])) {
278
+ for (const comment of prop.comments ?? []) {
272
279
  lines.push(...comment.split('\n'));
273
280
  }
274
281
  if (lines.length > 0)
@@ -345,3 +352,24 @@ export function importNameWithoutExt(imp) {
345
352
  return imp.substring(0, imp.length - 3);
346
353
  return imp;
347
354
  }
355
+ export const typeDefinitionForZValues = (values, url) => {
356
+ const code = [];
357
+ const defaults = VALUES.getTypesAndDefaultsByKey(values);
358
+ defaults.forEach((def, keyName) => {
359
+ const type = def[0];
360
+ const defaultValue = def[1];
361
+ code.push(typeOutputForZValueKey(keyName, type, defaultValue));
362
+ });
363
+ // const _values = values.map(typeOutputForZValueKey).join('\n');
364
+ return `import { Observable } from "@zcomponent/core";
365
+ ${code.join('\n')}`;
366
+ };
367
+ function typeOutputForZValueKey(keyName, _type, defaultValue) {
368
+ // TODO: Description
369
+ // const description = value.description ? `@description ${JSON.stringify(value.description)}` : '*';
370
+ return `/**
371
+ * ${defaultValue && `@default ${JSON.stringify(defaultValue)}`}
372
+ * @zui
373
+ */
374
+ export declare const ${keyName}: Observable<${outputForType(_type)}${defaultValue === undefined ? '| undefined' : ''}>;`;
375
+ }
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EntityPropOverride } from "./data/core";
1
+ import { EntityPropOverride } from './data/core';
2
2
  export interface BaseType {
3
3
  comments?: string[];
4
4
  typeHint?: TypeHint;
@@ -110,7 +110,7 @@ export interface DefaultChild {
110
110
  [id: string]: any;
111
111
  };
112
112
  }
113
- export type ComponentInfoType = "component" | "behavior" | "context";
113
+ export type ComponentInfoType = 'component' | 'behavior' | 'context';
114
114
  export interface ComponentInfo {
115
115
  name: string;
116
116
  file: string;
@@ -180,3 +180,4 @@ export declare function overridesAreCompatible(a: {
180
180
  }, b: {
181
181
  [path: string]: EntityPropOverride;
182
182
  }): boolean;
183
+ export declare function defaultValueForType(t: Type): any;
package/lib/types.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EntityPropOverrideType } from "./data/core";
2
- import { getSafeKeyName } from "./selectors";
1
+ import { EntityPropOverrideType } from './data/core';
2
+ import { getSafeKeyName } from './selectors';
3
3
  export var TypeName;
4
4
  (function (TypeName) {
5
5
  TypeName["String"] = "string";
@@ -66,9 +66,9 @@ export function areTypesCompatible(a, b) {
66
66
  }
67
67
  case 'union': {
68
68
  const bt = b;
69
- for (let achild of a.children) {
69
+ for (const achild of a.children) {
70
70
  let foundCompatible = false;
71
- for (let bchild of bt.children) {
71
+ for (const bchild of bt.children) {
72
72
  if (areTypesCompatible(achild, bchild)) {
73
73
  foundCompatible = true;
74
74
  break;
@@ -411,3 +411,19 @@ function overrideAreCompatible(a, b) {
411
411
  return false;
412
412
  return true;
413
413
  }
414
+ export function defaultValueForType(t) {
415
+ switch (t.name) {
416
+ case 'array':
417
+ return [];
418
+ case 'tuple':
419
+ return t.children.map(c => defaultValueForType(c));
420
+ case 'unknown':
421
+ return '{}';
422
+ case 'number':
423
+ return 0;
424
+ case 'string':
425
+ return '';
426
+ case 'boolean':
427
+ return false;
428
+ }
429
+ }
@@ -1,8 +1,10 @@
1
1
  import { Import, NodeData, ZComponentData } from './data/core';
2
+ import type { ZValues } from './values/values';
2
3
  interface Issue {
3
4
  text: string;
4
5
  }
5
6
  export declare const validateImport: (i: Import) => Issue[];
7
+ export declare const validatorZValuesTopLevel: (url: string, values: ZValues) => Issue[];
6
8
  export declare const validatorZcomponentTopLevel: (url: string, component: ZComponentData) => Issue[];
7
9
  export declare const validatorZcomponentComponent: (node: NodeData, nodeId: string) => Issue[];
8
10
  export {};
package/lib/validators.js CHANGED
@@ -5,6 +5,12 @@ export const validateImport = (i) => {
5
5
  return [{ text: 'Import structure is invalid' }];
6
6
  return [];
7
7
  };
8
+ export const validatorZValuesTopLevel = (url, values) => {
9
+ if (typeof values !== 'object' || typeof values.keys !== 'object' || typeof values.values !== 'object' || typeof values.variantsByID !== 'object') {
10
+ return [{ text: 'values structure is invalid' }];
11
+ }
12
+ return [];
13
+ };
8
14
  export const validatorZcomponentTopLevel = (url, component) => {
9
15
  if (typeof component !== 'object' ||
10
16
  typeof component.nodes !== 'object' ||
@@ -0,0 +1,61 @@
1
+ import { DeviceType } from '../profile';
2
+ import type { Type } from '../types';
3
+ export declare enum ConditionType {
4
+ 'Locale' = "Locale",
5
+ 'QueryString' = "Query String",
6
+ 'UserAgent' = "User Agent",
7
+ 'Debug' = "Debug",
8
+ 'Default' = "Default"
9
+ }
10
+ export declare function getTypeForVariant(variant: ConditionType): Type;
11
+ export interface BaseVariant {
12
+ conditionType: ConditionType;
13
+ priority: number;
14
+ }
15
+ export interface DefaultVariant {
16
+ conditionType: ConditionType.Default;
17
+ priority: number;
18
+ }
19
+ export interface LocaleVariant extends BaseVariant {
20
+ conditionType: ConditionType.Locale;
21
+ code?: string;
22
+ }
23
+ export interface QueryStringVariant extends BaseVariant {
24
+ conditionType: ConditionType.QueryString;
25
+ param?: string;
26
+ value?: string;
27
+ }
28
+ export interface UserAgentVariant extends BaseVariant {
29
+ conditionType: ConditionType.UserAgent;
30
+ agent?: DeviceType;
31
+ }
32
+ export interface DebugVariant extends BaseVariant {
33
+ conditionType: ConditionType.Debug;
34
+ isEnabled?: boolean;
35
+ }
36
+ export type ZVariant = LocaleVariant | QueryStringVariant | UserAgentVariant | DebugVariant | DefaultVariant;
37
+ export declare class ValuesManager {
38
+ private valuesString;
39
+ private profile;
40
+ constructor(valuesString: string, states: Map<string, any>);
41
+ private getValueForKey;
42
+ private getEnabledStateOfVariant;
43
+ }
44
+ export type ZValuesKey = {
45
+ type: Type;
46
+ };
47
+ export type ZValuesKeys = {
48
+ [key: string]: ZValuesKey;
49
+ };
50
+ export type ZValuesValues = {
51
+ [key: string]: {
52
+ [key: string]: any;
53
+ };
54
+ };
55
+ export type ZValues = {
56
+ keys: ZValuesKeys;
57
+ variantsByID: {
58
+ [id: string]: ZVariant;
59
+ };
60
+ values: ZValuesValues;
61
+ };
@@ -0,0 +1,91 @@
1
+ import { Profile } from '../profile';
2
+ export var ConditionType;
3
+ (function (ConditionType) {
4
+ ConditionType["Locale"] = "Locale";
5
+ ConditionType["QueryString"] = "Query String";
6
+ ConditionType["UserAgent"] = "User Agent";
7
+ ConditionType["Debug"] = "Debug";
8
+ ConditionType["Default"] = "Default";
9
+ })(ConditionType || (ConditionType = {}));
10
+ export function getTypeForVariant(variant) {
11
+ switch (variant) {
12
+ case ConditionType.Locale:
13
+ return {
14
+ name: 'string',
15
+ };
16
+ case ConditionType.QueryString:
17
+ return {
18
+ name: 'unknown',
19
+ };
20
+ case ConditionType.UserAgent:
21
+ return {
22
+ name: 'unknown',
23
+ };
24
+ case ConditionType.Debug:
25
+ return {
26
+ name: 'boolean',
27
+ };
28
+ default:
29
+ throw new Error(`Unknown variant type: ${variant}`);
30
+ }
31
+ }
32
+ export class ValuesManager {
33
+ // TODO: Fix this any
34
+ constructor(valuesString, states) {
35
+ this.valuesString = valuesString;
36
+ this.profile = new Profile();
37
+ if (valuesString === undefined)
38
+ throw new Error('Values are not defined');
39
+ if (states === undefined)
40
+ throw new Error('States are not defined');
41
+ const values = JSON.parse(valuesString);
42
+ states.forEach((state, keyName) => {
43
+ const key = Object.keys(values.keys).find(v => v === keyName);
44
+ if (!key)
45
+ throw new Error(`No key found for state ${keyName}`);
46
+ const keyState = states.get(keyName);
47
+ if (!keyState)
48
+ throw new Error(`No state found for key ${keyName}`);
49
+ keyState.value = this.getValueForKey(key, values);
50
+ });
51
+ }
52
+ getValueForKey(key, values) {
53
+ const enableVariantIDs = new Set();
54
+ for (const variantID in values.variantsByID) {
55
+ const enabledState = this.getEnabledStateOfVariant(values.variantsByID[variantID]);
56
+ if (enabledState)
57
+ enableVariantIDs.add(variantID);
58
+ }
59
+ const highestPriorityVariantID = Array.from(enableVariantIDs).reduce((prev, curr) => {
60
+ const prevPriority = values.variantsByID[prev].priority;
61
+ const currPriority = values.variantsByID[curr].priority;
62
+ return prevPriority > currPriority ? prev : curr;
63
+ });
64
+ const value = values.values[key][highestPriorityVariantID];
65
+ return value;
66
+ }
67
+ getEnabledStateOfVariant(variant) {
68
+ let enabled = false;
69
+ switch (variant.conditionType) {
70
+ case ConditionType.Default:
71
+ enabled = true;
72
+ break;
73
+ case ConditionType.Locale:
74
+ enabled = variant.code === this.profile.clientLanguage;
75
+ break;
76
+ case ConditionType.QueryString:
77
+ if (variant.param) {
78
+ const queryValue = this.profile.urlSearchParams.get(variant.param);
79
+ enabled = (queryValue && queryValue === variant.value) || (queryValue === undefined && variant.value === undefined);
80
+ }
81
+ break;
82
+ case ConditionType.UserAgent:
83
+ enabled = variant.agent === this.profile.userAgent;
84
+ break;
85
+ case ConditionType.Debug:
86
+ enabled = variant.isEnabled === this.profile.debugMode;
87
+ break;
88
+ }
89
+ return enabled;
90
+ }
91
+ }
@@ -0,0 +1,127 @@
1
+ import type { Type } from '../types';
2
+ import type { ZValues, ZVariant } from './values';
3
+ export declare function changeKeyAndType(values: ZValues, oldKeyName: string, newKeyName: string, type: Type): ZValues;
4
+ type ValuesByType = {
5
+ value: any;
6
+ uuid: string;
7
+ priority: number;
8
+ };
9
+ export declare function getSortedValuesByType(values: ZValues): Map<string, {
10
+ values: ValuesByType[];
11
+ type: Type;
12
+ }>;
13
+ /**
14
+ * Given a uuid of an existing variant alongside a direction, shifts the priority of the variant in the direction.
15
+ * @param values The values to mutate
16
+ * @param uuid The uuid of the variant to shift
17
+ * @param direction The direction to shift the variant in
18
+ * @returns The mutated values
19
+ * @note The variant must exist in the values.
20
+ * @note The variant's priority will never be less than 1 as this is reserved for the default variant.
21
+ * @note the priority of the default variant will never change from 0.
22
+ */
23
+ export declare function shiftPriorityLeftOrRightByUUID(values: ZValues, uuid: string, direction: 'left' | 'right'): ZValues;
24
+ /**
25
+ *
26
+ * @param values The values to mutate.
27
+ * @param keyName The key to delete.
28
+ * @returns The mutated values.
29
+ */
30
+ export declare function deleteKeyFromValues(values: ZValues, keyName: string): ZValues;
31
+ /**
32
+ *
33
+ * @param values The values to mutate.
34
+ * @param uuid The uuid of the variant to shift.
35
+ * @param direction The direction to shift the variant in.
36
+ * @returns The mutated values.
37
+ */
38
+ export declare function canShiftPriorityLeftOrRightByUUID(values: ZValues, uuid: string, direction: 'left' | 'right'): boolean;
39
+ /**
40
+ *
41
+ * @param values The values to mutate.
42
+ * @param value The value to set.
43
+ * @param key The key to add the value to.
44
+ * @returns The mutated values.
45
+ */
46
+ export declare function setDefaultValueForKey(values: ZValues, value: any, key: string): ZValues;
47
+ /**
48
+ * Get the default value for a key.
49
+ * @param values The values to get the default value from.
50
+ * @param key The key to get the default value for.
51
+ * @returns The default value for the key, or undefined if no default value is set.
52
+ */
53
+ export declare function getDefaultValueForKey(values: ZValues, key: string): any;
54
+ /**
55
+ * Get a set of default values by key.
56
+ * @param values The values to get the default values from.
57
+ * @returns The default values by key.
58
+ */
59
+ export declare function getDefaultValuesByKey(values: ZValues): Map<string, any>;
60
+ /**
61
+ * @param values The values to parse.
62
+ * @returns A map of key names to their type representations.
63
+ */
64
+ export declare function getTypesByKey(values: ZValues): Map<string, Type>;
65
+ /**
66
+ *
67
+ * @param values The values to check.
68
+ * @returns Returns a map of key names to their [type representations, default values].
69
+ */
70
+ export declare function getTypesAndDefaultsByKey(values: ZValues): Map<string, [Type, any]>;
71
+ /**
72
+ * Adds a new key to the values.
73
+ * @param keyname The name of the key.
74
+ * @param type The type of the key.
75
+ * @param values The values to add the key to.
76
+ * @returns The new values.
77
+ */
78
+ export declare function addNewKeyToValues(keyname: string, type: Type, values: ZValues): ZValues;
79
+ /**
80
+ * Inserts a new key containing a default value into the values.
81
+ * @param keyname The name of the key.
82
+ * @param type The type of the key.
83
+ * @param values The values to add the key to.
84
+ * @param defaultValue The default value to set for the key.
85
+ * @returns The new values.
86
+ */
87
+ export declare function addNewKeyToValuesWithDefaultValue(keyname: string, type: Type, values: ZValues, defaultValue: any): ZValues;
88
+ /**
89
+ * Add a new variant to the values.
90
+ * @param uuid The uuid of the new variant.
91
+ * @param variant The variant to add.
92
+ * @param values The values to add the variant to.
93
+ * @returns The new values.
94
+ */
95
+ export declare function addNewVariantToValues(values: ZValues, uuid: string, variant: ZVariant): ZValues;
96
+ /**
97
+ * Remove a variant from the values
98
+ * @param uuid The uuid of the variant to remove
99
+ * @param values The values to remove the variant from
100
+ * @returns The new values object
101
+ */
102
+ export declare function removeVariantFromValuesByUUID(uuid: string, values: ZValues): ZValues;
103
+ /**
104
+ * Returns a new values object with the value overridden.
105
+ * @param keyname The keyname of the value to override.
106
+ * @param variantID The variantID of the value to override.
107
+ * @param value The new value.
108
+ * @param values The values object to override.
109
+ * @returns
110
+ */
111
+ export declare function changeValueInValues(keyname: string, uuid: string, value: any, values: ZValues): ZValues;
112
+ /**
113
+ * Given a values object, returns a CSV string.
114
+ * @param values The values to convert to CSV.
115
+ * @returns A CSV string.
116
+ */
117
+ export declare function ZValuesToCSVString(values: ZValues): string;
118
+ /**
119
+ * Merges a provided csv string into the provided values object.
120
+ * @param csvString The csv string to merge (source)
121
+ * @param currentValues The values object to merge into (destination)
122
+ * @returns The new values object.
123
+ * @notes This can very likely be improved.
124
+ * @notes I couldn't quite figure out how to do it in a way that was cleaner than this.
125
+ */
126
+ export declare function mergeCSVStringIntoTarget(csvString: string, currentValues: ZValues): ZValues;
127
+ export {};
@@ -0,0 +1,513 @@
1
+ import { ConditionType } from './values';
2
+ export function changeKeyAndType(values, oldKeyName, newKeyName, type) {
3
+ if (!values.keys.hasOwnProperty(oldKeyName)) {
4
+ throw new Error(`The old key name '${oldKeyName}' does not exist in the 'keys' object.`);
5
+ }
6
+ if (values.keys.hasOwnProperty(newKeyName)) {
7
+ throw new Error(`The new key name '${newKeyName}' already exists in the 'keys' object.`);
8
+ }
9
+ // Clone the old key and value properties
10
+ values.keys[newKeyName] = JSON.parse(JSON.stringify(values.keys[oldKeyName]));
11
+ values.values[newKeyName] = JSON.parse(JSON.stringify(values.values[oldKeyName]));
12
+ // Update the type of the new key
13
+ values.keys[newKeyName].type = JSON.parse(JSON.stringify(type));
14
+ // Delete the old key and value properties
15
+ delete values.keys[oldKeyName];
16
+ delete values.values[oldKeyName];
17
+ return values;
18
+ }
19
+ export function getSortedValuesByType(values) {
20
+ const valuesByType = new Map();
21
+ // There's a possibility that there are no variantsByID. So we prevent errors by using || {}
22
+ // to return an empty array, breaking out of the loop.
23
+ let valuesByTypeForKey = [];
24
+ for (const keyName of Object.keys(values?.keys || {})) {
25
+ const type = values.keys[keyName].type;
26
+ for (const uuid of Object.keys(values?.variantsByID || {})) {
27
+ // Possible for the value to not exist.
28
+ const value = values.values?.[keyName]?.[uuid];
29
+ const priority = values.variantsByID[uuid].priority;
30
+ valuesByTypeForKey.push({
31
+ value,
32
+ uuid,
33
+ priority,
34
+ });
35
+ }
36
+ valuesByTypeForKey.sort((a, b) => a.priority - b.priority);
37
+ valuesByType.set(keyName, {
38
+ values: valuesByTypeForKey,
39
+ type,
40
+ });
41
+ valuesByTypeForKey = [];
42
+ }
43
+ return valuesByType;
44
+ }
45
+ /**
46
+ * Given a uuid of an existing variant alongside a direction, shifts the priority of the variant in the direction.
47
+ * @param values The values to mutate
48
+ * @param uuid The uuid of the variant to shift
49
+ * @param direction The direction to shift the variant in
50
+ * @returns The mutated values
51
+ * @note The variant must exist in the values.
52
+ * @note The variant's priority will never be less than 1 as this is reserved for the default variant.
53
+ * @note the priority of the default variant will never change from 0.
54
+ */
55
+ export function shiftPriorityLeftOrRightByUUID(values, uuid, direction) {
56
+ const currentPriority = values.variantsByID[uuid].priority;
57
+ if (!canShiftPriorityLeftOrRightByUUID(values, uuid, direction))
58
+ return values;
59
+ const [higherNeighbourUUID, lowerNeighbourUUID] = [1, -1].map(v => getPriorityNeighbourUUID(values, uuid, v));
60
+ if (!lowerNeighbourUUID) {
61
+ throw new Error('No lower neighbour found, this should not happen as default is always at priority 0.');
62
+ }
63
+ if (direction === 'left' && values.variantsByID[lowerNeighbourUUID].priority === 0)
64
+ return values;
65
+ if (direction === 'left') {
66
+ values.variantsByID[uuid].priority = values.variantsByID[lowerNeighbourUUID].priority;
67
+ values.variantsByID[lowerNeighbourUUID].priority = currentPriority;
68
+ }
69
+ else {
70
+ values.variantsByID[uuid].priority = values.variantsByID[higherNeighbourUUID].priority;
71
+ values.variantsByID[higherNeighbourUUID].priority = currentPriority;
72
+ }
73
+ return values;
74
+ }
75
+ /**
76
+ *
77
+ * @param values The values to mutate.
78
+ * @param keyName The key to delete.
79
+ * @returns The mutated values.
80
+ */
81
+ export function deleteKeyFromValues(values, keyName) {
82
+ delete values.keys[keyName];
83
+ delete values.values[keyName];
84
+ return values;
85
+ }
86
+ /**
87
+ *
88
+ * @param values The values to mutate.
89
+ * @param uuid The uuid of the variant to shift.
90
+ * @param direction The direction to shift the variant in.
91
+ * @returns The mutated values.
92
+ */
93
+ export function canShiftPriorityLeftOrRightByUUID(values, uuid, direction) {
94
+ const currentPriority = values.variantsByID[uuid].priority;
95
+ if (currentPriority === 1 && direction === 'left')
96
+ return false;
97
+ if (uuidIsHighestPriority(values, uuid) && direction === 'right')
98
+ return false;
99
+ return true;
100
+ }
101
+ /**
102
+ *
103
+ * @param values The values to check.
104
+ * @param uuid The uuid of the variant to check.
105
+ * @returns Whether the variant is the highest priority variant.
106
+ */
107
+ function uuidIsHighestPriority(values, uuid) {
108
+ const variants = values.variantsByID;
109
+ const priority = variants[uuid].priority;
110
+ const highestPriority = Object.keys(variants).reduce((highest, v) => {
111
+ return variants[v].priority > highest ? variants[v].priority : highest;
112
+ }, 0);
113
+ return priority === highestPriority;
114
+ }
115
+ /**
116
+ *
117
+ * @param values The values to check.
118
+ * @param uuid The uuid of the variant to check.
119
+ * @param direction The direction to check.
120
+ * @returns The uuid of the neighbour variant.
121
+ */
122
+ function getPriorityNeighbourUUID(values, uuid, direction) {
123
+ const variants = values.variantsByID;
124
+ const priority = variants[uuid].priority;
125
+ const neighbourPriority = priority + direction;
126
+ const neighbourUUID = Object.keys(variants).find(v => variants[v].priority === neighbourPriority);
127
+ return neighbourUUID;
128
+ }
129
+ /**
130
+ *
131
+ * @param values The values to mutate.
132
+ * @param value The value to set.
133
+ * @param key The key to add the value to.
134
+ * @returns The mutated values.
135
+ */
136
+ export function setDefaultValueForKey(values, value, key) {
137
+ if (key === undefined || value === undefined)
138
+ return values;
139
+ const defaultUUID = Object.keys(values.variantsByID).find(uuid => values.variantsByID[uuid].priority === 0);
140
+ if (!defaultUUID) {
141
+ throw new Error('No default variant found');
142
+ }
143
+ values.values[key][defaultUUID] = value;
144
+ return values;
145
+ }
146
+ /**
147
+ * Get the default value for a key.
148
+ * @param values The values to get the default value from.
149
+ * @param key The key to get the default value for.
150
+ * @returns The default value for the key, or undefined if no default value is set.
151
+ */
152
+ export function getDefaultValueForKey(values, key) {
153
+ const defaultVariantID = Object.keys(values.variantsByID).find(id => values.variantsByID[id].conditionType === ConditionType.Default);
154
+ const defaultValue = values.values[key]?.[defaultVariantID];
155
+ return defaultValue;
156
+ }
157
+ /**
158
+ * Get a set of default values by key.
159
+ * @param values The values to get the default values from.
160
+ * @returns The default values by key.
161
+ */
162
+ export function getDefaultValuesByKey(values) {
163
+ const defaultValuesByKey = new Map();
164
+ for (const key of Object.keys(values.keys)) {
165
+ defaultValuesByKey.set(key, getDefaultValueForKey(values, key));
166
+ }
167
+ return defaultValuesByKey;
168
+ }
169
+ /**
170
+ * @param values The values to parse.
171
+ * @returns A map of key names to their type representations.
172
+ */
173
+ export function getTypesByKey(values) {
174
+ const typesByKey = new Map();
175
+ for (const key of Object.keys(values.keys)) {
176
+ typesByKey.set(key, values.keys[key].type);
177
+ }
178
+ return typesByKey;
179
+ }
180
+ /**
181
+ *
182
+ * @param values The values to check.
183
+ * @returns Returns a map of key names to their [type representations, default values].
184
+ */
185
+ export function getTypesAndDefaultsByKey(values) {
186
+ const typesAndDefaultsByKey = new Map();
187
+ for (const key of Object.keys(values.keys)) {
188
+ typesAndDefaultsByKey.set(key, [values.keys[key].type, getDefaultValueForKey(values, key)]);
189
+ }
190
+ return typesAndDefaultsByKey;
191
+ }
192
+ /**
193
+ * Adds a new key to the values.
194
+ * @param keyname The name of the key.
195
+ * @param type The type of the key.
196
+ * @param values The values to add the key to.
197
+ * @returns The new values.
198
+ */
199
+ export function addNewKeyToValues(keyname, type, values) {
200
+ values.keys[keyname] = { type };
201
+ values.values[keyname] = {};
202
+ return values;
203
+ }
204
+ /**
205
+ * Inserts a new key containing a default value into the values.
206
+ * @param keyname The name of the key.
207
+ * @param type The type of the key.
208
+ * @param values The values to add the key to.
209
+ * @param defaultValue The default value to set for the key.
210
+ * @returns The new values.
211
+ */
212
+ export function addNewKeyToValuesWithDefaultValue(keyname, type, values, defaultValue) {
213
+ const variantWithNewKey = addNewKeyToValues(keyname, type, values);
214
+ return setDefaultValueForKey(variantWithNewKey, defaultValue, keyname);
215
+ }
216
+ /**
217
+ * Add a new variant to the values.
218
+ * @param uuid The uuid of the new variant.
219
+ * @param variant The variant to add.
220
+ * @param values The values to add the variant to.
221
+ * @returns The new values.
222
+ */
223
+ export function addNewVariantToValues(values, uuid, variant) {
224
+ const currentHighestPriority = Object.keys(values.variantsByID).reduce((highest, v) => {
225
+ return values.variantsByID[v].priority > highest ? values.variantsByID[v].priority : highest;
226
+ }, 0);
227
+ variant.priority = currentHighestPriority + 1;
228
+ values.variantsByID[uuid] = variant;
229
+ return values;
230
+ }
231
+ /**
232
+ * Remove a variant from the values
233
+ * @param uuid The uuid of the variant to remove
234
+ * @param values The values to remove the variant from
235
+ * @returns The new values object
236
+ */
237
+ export function removeVariantFromValuesByUUID(uuid, values) {
238
+ delete values.variantsByID[uuid];
239
+ Object.keys(values.values).forEach(key => {
240
+ delete values.values[key][uuid];
241
+ });
242
+ return values;
243
+ }
244
+ /**
245
+ * Returns a new values object with the value overridden.
246
+ * @param keyname The keyname of the value to override.
247
+ * @param variantID The variantID of the value to override.
248
+ * @param value The new value.
249
+ * @param values The values object to override.
250
+ * @returns
251
+ */
252
+ export function changeValueInValues(keyname, uuid, value, values) {
253
+ if (value === undefined) {
254
+ // delete it
255
+ delete values.values[keyname][uuid];
256
+ }
257
+ else {
258
+ values.values[keyname][uuid] = value;
259
+ }
260
+ return values;
261
+ }
262
+ /**
263
+ * Gets the value for a key in a variant.
264
+ * @param values The values to check.
265
+ * @param key The key to get the value for.
266
+ * @param uuid The uuid of the variant to get the value for.
267
+ * @returns The value for the key in the variant.
268
+ */
269
+ function getValueByKeyAndUUID(values, key, uuid) {
270
+ return values.values[key][uuid];
271
+ }
272
+ /**
273
+ * Gets the values for a key.
274
+ * @param values The values to check.
275
+ * @param keyName The key to get the value for.
276
+ * @returns An array of values for the key.
277
+ */
278
+ function getValuesByKeyname(values, keyName) {
279
+ const row = new Array(Object.keys(values.variantsByID).length);
280
+ const sortedUUIDs = getSortedVariantUUIDs(values);
281
+ sortedUUIDs.forEach((uuid, i) => {
282
+ let value = getValueByKeyAndUUID(values, keyName, uuid);
283
+ if (typeof value === 'string' && value.includes(',')) {
284
+ value = `"${value}"`;
285
+ }
286
+ row[i] = value;
287
+ });
288
+ return row;
289
+ }
290
+ /**
291
+ * Gets the sorted uuids of the variants in the values.
292
+ * @param values The values to check.
293
+ * @returns A sorted array of uuids.
294
+ */
295
+ function getSortedVariantUUIDs(values) {
296
+ const sortedUUIDs = Object.keys(values.variantsByID).sort((a, b) => {
297
+ const aPriority = values.variantsByID[a].priority;
298
+ const bPriority = values.variantsByID[b].priority;
299
+ if (aPriority < bPriority) {
300
+ return -1;
301
+ }
302
+ else if (aPriority > bPriority) {
303
+ return 1;
304
+ }
305
+ else {
306
+ return 0;
307
+ }
308
+ });
309
+ return sortedUUIDs;
310
+ }
311
+ /**
312
+ * Given a values object, returns a CSV string.
313
+ * @param values The values to convert to CSV.
314
+ * @returns A CSV string.
315
+ */
316
+ export function ZValuesToCSVString(values) {
317
+ let csvString = 'key';
318
+ const sortedUUIDS = getSortedVariantUUIDs(values);
319
+ sortedUUIDS.forEach((uuid) => {
320
+ const variantTitle = variantToString(values.variantsByID[uuid]);
321
+ csvString += `,${variantTitle}`;
322
+ });
323
+ Object.keys(values.keys).forEach((key) => {
324
+ csvString += `\n${key}`;
325
+ const row = getValuesByKeyname(values, key);
326
+ row.forEach((value) => {
327
+ if (value === undefined)
328
+ value = '';
329
+ csvString += `,${value}`;
330
+ });
331
+ });
332
+ return csvString;
333
+ }
334
+ /**
335
+ * Given a variant object, returns a string.
336
+ * @param variant The variant to convert to a string.
337
+ * @returns A string.
338
+ */
339
+ function variantToString(variant) {
340
+ switch (variant.conditionType) {
341
+ case ConditionType.Default:
342
+ return 'default';
343
+ case ConditionType.QueryString:
344
+ return `qs:${variant.param}=${variant.value}`;
345
+ case ConditionType.UserAgent:
346
+ return `ua:${variant.agent}`;
347
+ case ConditionType.Locale:
348
+ return `locale:${variant.code}`;
349
+ case ConditionType.Debug:
350
+ return `debug:${variant.isEnabled}`;
351
+ default:
352
+ return 'unknown';
353
+ }
354
+ }
355
+ function isObject(item) {
356
+ return item && typeof item === 'object' && !Array.isArray(item);
357
+ }
358
+ function deepMerge(target, source) {
359
+ const output = { ...target };
360
+ for (const key in source) {
361
+ if (isObject(source[key])) {
362
+ if (isObject(target[key])) {
363
+ output[key] = deepMerge(target[key], source[key]);
364
+ }
365
+ else {
366
+ output[key] = deepMerge({}, source[key]);
367
+ }
368
+ }
369
+ else {
370
+ output[key] = source[key];
371
+ }
372
+ }
373
+ return output;
374
+ }
375
+ /**
376
+ * Merges a provided csv string into the provided values object.
377
+ * @param csvString The csv string to merge (source)
378
+ * @param currentValues The values object to merge into (destination)
379
+ * @returns The new values object.
380
+ * @notes This can very likely be improved.
381
+ * @notes I couldn't quite figure out how to do it in a way that was cleaner than this.
382
+ */
383
+ export function mergeCSVStringIntoTarget(csvString, currentValues) {
384
+ // lose the ref
385
+ currentValues = JSON.parse(JSON.stringify(currentValues));
386
+ const values = {
387
+ keys: {},
388
+ values: {},
389
+ variantsByID: {},
390
+ };
391
+ const lines = csvString.split('\n');
392
+ const variants = lines[0].split(',');
393
+ const timestamp = new Date().getTime();
394
+ // remove "key" from variants
395
+ variants.shift();
396
+ // Remove variants from lines
397
+ lines.shift();
398
+ // Build the variants
399
+ variants.forEach((variantString, i) => {
400
+ const variant = parseStringToVariant(variantString, i);
401
+ // Nothing happens fast enough to justify a uuid
402
+ // Most of these are gonna get overwritten.
403
+ const uuid = i + timestamp.toString();
404
+ values.variantsByID[uuid] = variant;
405
+ return parseStringToVariant(variantString, i);
406
+ });
407
+ // Build the values
408
+ lines.forEach((line, i) => {
409
+ // Split, but ignore commas in quotes
410
+ const [keyname, ..._values] = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
411
+ values.values[keyname] = {};
412
+ _values.forEach((value, i) => {
413
+ if (value.length === 0)
414
+ return;
415
+ const uuid = i + timestamp.toString();
416
+ values.values[keyname][uuid] = value;
417
+ });
418
+ });
419
+ // Currently typeinfo is not being saved in the csv as
420
+ // I could not figure out a clean way to present it.
421
+ Object.keys(values.values).forEach(key => {
422
+ values.keys[key] = {
423
+ type: {
424
+ name: 'string',
425
+ },
426
+ };
427
+ });
428
+ // merge values with currentValues
429
+ // Figure out what UUID of currentValues maps to what UUID of values
430
+ const lookup = new Map();
431
+ Object.keys(currentValues.variantsByID).forEach(uuid => {
432
+ Object.keys(values.variantsByID).forEach(tempUUID => {
433
+ // Check if content matches
434
+ if (variantToString(currentValues.variantsByID[uuid]) === variantToString(values.variantsByID[tempUUID])) {
435
+ lookup.set(tempUUID, uuid);
436
+ }
437
+ });
438
+ });
439
+ lookup.forEach((uuid, tempUUID) => {
440
+ // change keys of variantsByID
441
+ if (values.variantsByID[tempUUID]) {
442
+ values.variantsByID[uuid] = values.variantsByID[tempUUID];
443
+ delete values.variantsByID[tempUUID];
444
+ }
445
+ // Change keys of values
446
+ Object.keys(values.values).forEach(key => {
447
+ // Avoid adding any unexisting values
448
+ if (values.values[key][tempUUID]) {
449
+ values.values[key][uuid] = values.values[key][tempUUID];
450
+ delete values.values[key][tempUUID];
451
+ }
452
+ });
453
+ });
454
+ // At this stage the two values objects can be merged.
455
+ // Any matching UUIDS will be overwritten with the incoming values
456
+ // And any non-existing keys/uuids will be added.
457
+ // TODO: Post merge structure validation,
458
+ // returning an error to the user if the structure is invalid.
459
+ // Attempt to not lose ref
460
+ // return _.assign({}, _.merge(currentValues, values));
461
+ return deepMerge(currentValues, values);
462
+ }
463
+ /**
464
+ * Given a string, returns a variant object.
465
+ * @param str The string to convert to a variant.
466
+ * @param priority The priority of the variant.
467
+ * @returns A variant object.
468
+ */
469
+ function parseStringToVariant(str, priority) {
470
+ const [param, ...value] = str.split(':');
471
+ switch (param) {
472
+ case 'qs':
473
+ const qsVariant = {
474
+ conditionType: ConditionType.QueryString,
475
+ param: value[0].split('=')[0],
476
+ value: value[0].split('=')[1],
477
+ priority,
478
+ };
479
+ return qsVariant;
480
+ case 'ua':
481
+ const uaVariant = {
482
+ conditionType: ConditionType.UserAgent,
483
+ agent: value[0],
484
+ priority,
485
+ };
486
+ return uaVariant;
487
+ case 'locale':
488
+ const localeVariant = {
489
+ conditionType: ConditionType.Locale,
490
+ code: value[0],
491
+ priority,
492
+ };
493
+ return localeVariant;
494
+ case 'debug':
495
+ const debugVariant = {
496
+ conditionType: ConditionType.Debug,
497
+ isEnabled: value[0] === 'true',
498
+ priority,
499
+ };
500
+ return debugVariant;
501
+ case 'default': {
502
+ const defaultVariant = {
503
+ conditionType: ConditionType.Default,
504
+ priority,
505
+ };
506
+ return defaultVariant;
507
+ }
508
+ // TODO: This error should be fed back to the user.
509
+ default:
510
+ throw new Error(`Unknown variant type: ${param}`);
511
+ break;
512
+ }
513
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zcomponent/core",
3
- "version": "0.0.31-beta",
3
+ "version": "0.1.0-beta",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,8 @@
28
28
  "license": "Proprietary",
29
29
  "devDependencies": {
30
30
  "@playwright/test": "^1.25.0",
31
- "@types/ua-parser-js": "^0.7.36",
32
31
  "parcel": "^2.8.2",
33
- "typescript": "^4.9.4",
34
- "ua-parser-js": "^1.0.33"
32
+ "typescript": "^4.9.4"
35
33
  },
36
34
  "zexports": [
37
35
  "./lib/components/**/*",