@datagrok/sequence-translator 1.2.7 → 1.3.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/.eslintrc.json +5 -5
- package/CHANGELOG.md +14 -0
- package/dist/package-test.js +2 -1
- package/dist/package-test.js.LICENSE.txt +8 -0
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -1
- package/dist/package.js.LICENSE.txt +8 -0
- package/dist/package.js.map +1 -1
- package/files/pattern-app-data.json +80 -0
- package/package.json +22 -14
- package/src/{model → apps/common/model}/const.ts +1 -1
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
- package/src/apps/common/model/data-loader/json-loader.ts +48 -0
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
- package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
- package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
- package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
- package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
- package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
- package/src/apps/common/view/app-ui-base.ts +28 -0
- package/src/apps/common/view/combined-app-ui.ts +66 -0
- package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
- package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
- package/src/apps/common/view/isolated-app-ui.ts +43 -0
- package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
- package/src/apps/common/view/utils.ts +29 -0
- package/src/apps/pattern/model/const.ts +121 -0
- package/src/apps/pattern/model/data-manager.ts +297 -0
- package/src/apps/pattern/model/event-bus.ts +487 -0
- package/src/apps/pattern/model/router.ts +46 -0
- package/src/apps/pattern/model/subscription-manager.ts +21 -0
- package/src/apps/pattern/model/translator.ts +94 -0
- package/src/apps/pattern/model/types.ts +52 -0
- package/src/apps/pattern/model/utils.ts +110 -0
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +79 -0
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +38 -0
- package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
- package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
- package/src/apps/pattern/view/components/left-section.ts +44 -0
- package/src/apps/pattern/view/components/load-block-controls.ts +200 -0
- package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
- package/src/apps/pattern/view/components/right-section.ts +148 -0
- package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
- package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
- package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
- package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
- package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
- package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
- package/src/apps/pattern/view/svg-utils/const.ts +77 -0
- package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
- package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
- package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +44 -0
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +94 -0
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +51 -0
- package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
- package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
- package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
- package/src/apps/pattern/view/types.ts +14 -0
- package/src/apps/pattern/view/ui.ts +61 -0
- package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
- package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
- package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
- package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
- package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
- package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
- package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
- package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +1 -1
- package/src/{view/apps/oligo-translator.ts → apps/translator/view/ui.ts} +88 -42
- package/src/demo/demo-st-ui.ts +12 -32
- package/src/package.ts +91 -55
- package/src/plugins/mermade.ts +9 -9
- package/src/polytool/const.ts +28 -0
- package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
- package/src/polytool/cyclized.ts +56 -0
- package/src/polytool/monomer-lib-handler.ts +115 -0
- package/src/polytool/pt-conversion.ts +307 -0
- package/src/polytool/pt-dialog.ts +115 -0
- package/src/polytool/pt-enumeration.ts +127 -0
- package/src/polytool/pt-rules.ts +73 -0
- package/src/polytool/utils.ts +27 -0
- package/src/tests/const.ts +5 -5
- package/src/tests/formats-support.ts +6 -6
- package/src/tests/formats-to-helm.ts +5 -5
- package/src/tests/helm-to-nucleotides.ts +5 -10
- package/tsconfig.json +3 -9
- package/webpack.config.js +3 -0
- package/files/axolabs-style.json +0 -97
- package/src/model/data-loading-utils/json-loader.ts +0 -38
- package/src/model/pattern-app/const.ts +0 -33
- package/src/model/pattern-app/draw-svg.ts +0 -193
- package/src/model/pattern-app/helpers.ts +0 -96
- package/src/model/pattern-app/oligo-pattern.ts +0 -111
- package/src/view/app-ui.ts +0 -193
- package/src/view/apps/oligo-pattern.ts +0 -759
- /package/src/{model → apps/common/model}/helpers.ts +0 -0
- /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
- /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
- /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
- /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
- /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
- /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
- /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
import * as grok from 'datagrok-api/grok';
|
|
4
|
+
|
|
5
|
+
import {_package} from '../../../package';
|
|
6
|
+
import {AppUIBase} from './app-ui-base';
|
|
7
|
+
|
|
8
|
+
export abstract class IsolatedAppUIBase extends AppUIBase {
|
|
9
|
+
constructor(appName: string) {
|
|
10
|
+
super(appName);
|
|
11
|
+
this.view = DG.View.create();
|
|
12
|
+
this.configureView();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
protected view: DG.View;
|
|
16
|
+
async constructView(): Promise<DG.ViewBase> {
|
|
17
|
+
await this.initView();
|
|
18
|
+
const name = this.parentAppName ? this.parentAppName + '/' + this.appName : this.appName;
|
|
19
|
+
this.view.path = `/apps/${_package.name}/${name.replace(/\s/g, '')}/`;
|
|
20
|
+
return this.view;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected abstract getContent(): Promise<HTMLDivElement>;
|
|
24
|
+
async initView(): Promise<void> {
|
|
25
|
+
const content = await this.getContent();
|
|
26
|
+
this.view.append(content);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected configureView(): void {
|
|
30
|
+
this.view.box = true;
|
|
31
|
+
this.view.name = this.appName;
|
|
32
|
+
|
|
33
|
+
const windows = grok.shell.windows;
|
|
34
|
+
windows.showProperties = false;
|
|
35
|
+
windows.showToolbox = false;
|
|
36
|
+
windows.showHelp = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getView(): DG.View {
|
|
40
|
+
return this.view;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -3,8 +3,8 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import {drawZoomedInMolecule} from '
|
|
7
|
-
import {MonomerLibWrapper} from '
|
|
6
|
+
import {drawZoomedInMolecule} from './components/draw-molecule';
|
|
7
|
+
import {MonomerLibWrapper} from '../model/monomer-lib/lib-wrapper';
|
|
8
8
|
|
|
9
9
|
export class MonomerLibViewer {
|
|
10
10
|
static async view(): Promise<void> {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {OligoPatternUI} from '../../pattern/view/ui';
|
|
2
|
+
import {OligoStructureUI} from '../../structure/view/ui';
|
|
3
|
+
import {OligoTranslatorUI} from '../../translator/view/ui';
|
|
4
|
+
import {IsolatedAppUIBase} from './isolated-app-ui';
|
|
5
|
+
import {APP_NAME} from './const';
|
|
6
|
+
|
|
7
|
+
/** For plugins from external packages */
|
|
8
|
+
export class ExternalPluginUI extends IsolatedAppUIBase {
|
|
9
|
+
constructor(viewName: string, private content: HTMLDivElement) {
|
|
10
|
+
super(viewName);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected getContent(): Promise<HTMLDivElement> {
|
|
14
|
+
return Promise.resolve(this.content);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getSpecifiedAppUI(appName: string): IsolatedAppUIBase {
|
|
19
|
+
switch (appName) {
|
|
20
|
+
case APP_NAME.TRANSLATOR:
|
|
21
|
+
return new OligoTranslatorUI();
|
|
22
|
+
case APP_NAME.PATTERN:
|
|
23
|
+
return new OligoPatternUI();
|
|
24
|
+
case APP_NAME.STRUCTURE:
|
|
25
|
+
return new OligoStructureUI();
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unknown app name: ${appName}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {StrandType} from './types';
|
|
2
|
+
|
|
3
|
+
export const enum STRAND {
|
|
4
|
+
SENSE = 'SS',
|
|
5
|
+
ANTISENSE = 'AS',
|
|
6
|
+
};
|
|
7
|
+
export const STRANDS = [STRAND.SENSE, STRAND.ANTISENSE] as const;
|
|
8
|
+
export const STRAND_LABEL: Record<StrandType, string> = {
|
|
9
|
+
[STRAND.SENSE]: 'Sense strand',
|
|
10
|
+
[STRAND.ANTISENSE]: 'Anti sense',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const enum STRAND_END {
|
|
14
|
+
LEFT,
|
|
15
|
+
RIGHT,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const STRAND_ENDS = [STRAND_END.LEFT, STRAND_END.RIGHT] as const;
|
|
19
|
+
|
|
20
|
+
export const enum TERMINUS {
|
|
21
|
+
FIVE_PRIME = '5\'',
|
|
22
|
+
THREE_PRIME = '3\'',
|
|
23
|
+
};
|
|
24
|
+
export const TERMINI = [TERMINUS.THREE_PRIME, TERMINUS.FIVE_PRIME] as const;
|
|
25
|
+
|
|
26
|
+
export const STRAND_TO_END_TERMINUS_MAP = {
|
|
27
|
+
[STRAND.SENSE]: {
|
|
28
|
+
[STRAND_END.LEFT]: TERMINUS.THREE_PRIME,
|
|
29
|
+
[STRAND_END.RIGHT]: TERMINUS.FIVE_PRIME
|
|
30
|
+
},
|
|
31
|
+
[STRAND.ANTISENSE]: {
|
|
32
|
+
[STRAND_END.LEFT]: TERMINUS.FIVE_PRIME,
|
|
33
|
+
[STRAND_END.RIGHT]: TERMINUS.THREE_PRIME
|
|
34
|
+
}
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
export const MAX_SEQUENCE_LENGTH = 34;
|
|
38
|
+
|
|
39
|
+
export const STORAGE_NAME: string = 'OligoToolkit';
|
|
40
|
+
export const EXAMPLE_MIN_WIDTH: string = '400px';
|
|
41
|
+
|
|
42
|
+
export const OTHER_USERS = 'Other users';
|
|
43
|
+
|
|
44
|
+
export namespace GRAPH_SETTINGS_KEYS {
|
|
45
|
+
export const IS_ANTISENSE_STRAND_INCLUDED = 'isAntisenseStrandIncluded';
|
|
46
|
+
export const NUCLEOTIDE_SEQUENCES = 'nucleotideSequences';
|
|
47
|
+
export const PHOSPHOROTHIOATE_LINKAGE_FLAGS = 'phosphorothioateLinkageFlags';
|
|
48
|
+
export const STRAND_TERMINUS_MODIFICATIONS = 'strandTerminusModifications';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export namespace LEGEND_SETTINGS_KEYS {
|
|
52
|
+
export const PATTERN_NAME = 'patternName';
|
|
53
|
+
export const PATTERN_COMMENT = 'patternComment';
|
|
54
|
+
export const NUCLEOTIDES_WITH_NUMERIC_LABELS = 'nucleotidesWithNumericLabels';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export namespace PATTERN_RECORD_KEYS {
|
|
58
|
+
export const PATTERN_CONFIG = 'patternConfig';
|
|
59
|
+
export const AUTHOR_ID = 'authorID';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const GRAPH_SETTINGS_KEY_LIST = [
|
|
63
|
+
GRAPH_SETTINGS_KEYS.IS_ANTISENSE_STRAND_INCLUDED,
|
|
64
|
+
GRAPH_SETTINGS_KEYS.NUCLEOTIDE_SEQUENCES,
|
|
65
|
+
GRAPH_SETTINGS_KEYS.PHOSPHOROTHIOATE_LINKAGE_FLAGS,
|
|
66
|
+
GRAPH_SETTINGS_KEYS.STRAND_TERMINUS_MODIFICATIONS
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export const LEGEND_SETTINGS_KEY_LIST = [
|
|
70
|
+
LEGEND_SETTINGS_KEYS.PATTERN_NAME,
|
|
71
|
+
LEGEND_SETTINGS_KEYS.PATTERN_COMMENT,
|
|
72
|
+
LEGEND_SETTINGS_KEYS.NUCLEOTIDES_WITH_NUMERIC_LABELS
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
export const PATTERN_RECORD_KEY_LIST = [
|
|
76
|
+
PATTERN_RECORD_KEYS.PATTERN_CONFIG,
|
|
77
|
+
PATTERN_RECORD_KEYS.AUTHOR_ID
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export const EXAMPLE_PATTERN_CONFIG =
|
|
81
|
+
{
|
|
82
|
+
'patternConfig': {
|
|
83
|
+
'patternName': '<default example>',
|
|
84
|
+
'isAntisenseStrandIncluded': true,
|
|
85
|
+
'nucleotideSequences': {
|
|
86
|
+
'SS': [
|
|
87
|
+
'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA',
|
|
88
|
+
'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA'
|
|
89
|
+
],
|
|
90
|
+
'AS': [
|
|
91
|
+
'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA',
|
|
92
|
+
'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA', 'RNA'
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
'phosphorothioateLinkageFlags': {
|
|
96
|
+
'SS': [
|
|
97
|
+
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
98
|
+
true, true, true, true, true, true, true, true
|
|
99
|
+
],
|
|
100
|
+
'AS': [
|
|
101
|
+
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
102
|
+
true, true, true, true, true, true, true, true
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
'strandTerminusModifications': {
|
|
106
|
+
'SS': {
|
|
107
|
+
'3\'': '',
|
|
108
|
+
'5\'': ''
|
|
109
|
+
},
|
|
110
|
+
'AS': {
|
|
111
|
+
'3\'': '',
|
|
112
|
+
'5\'': ''
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
'patternComment': '',
|
|
116
|
+
'nucleotidesWithNumericLabels': [
|
|
117
|
+
'RNA'
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
'authorID': ''
|
|
121
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
EXAMPLE_PATTERN_CONFIG,
|
|
6
|
+
GRAPH_SETTINGS_KEY_LIST as GKL, LEGEND_SETTINGS_KEYS as L, OTHER_USERS, PATTERN_RECORD_KEYS as R, STORAGE_NAME
|
|
7
|
+
} from './const';
|
|
8
|
+
import {
|
|
9
|
+
PatternConfigRecord, PatternConfiguration, PatternExistsError, PatternNameExistsError, RawPatternRecords
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
import objectHash from 'object-hash';
|
|
13
|
+
import {EventBus} from './event-bus';
|
|
14
|
+
import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class DataManager {
|
|
18
|
+
private currentUserName: string;
|
|
19
|
+
private currentUserId: string;
|
|
20
|
+
private otherUsersPatternNameToHash = new Map<string, string>();
|
|
21
|
+
private currentUserPatternNameToHash = new Map<string, string>();
|
|
22
|
+
|
|
23
|
+
// WARNING: init logic encapsulated
|
|
24
|
+
private constructor( ) { }
|
|
25
|
+
|
|
26
|
+
static async getInstance(): Promise<DataManager> {
|
|
27
|
+
const instance = new DataManager();
|
|
28
|
+
|
|
29
|
+
instance.currentUserName = await instance.fetchCurrentUserName();
|
|
30
|
+
instance.currentUserId = await instance.fetchCurrentUserId();
|
|
31
|
+
|
|
32
|
+
const patternRecords = await instance.fetchPatterns();
|
|
33
|
+
await instance.initializePatternMaps(patternRecords);
|
|
34
|
+
|
|
35
|
+
return instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getCurrentUserPatternNames(): string[] {
|
|
39
|
+
return Array.from(this.currentUserPatternNameToHash.keys())
|
|
40
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getOtherUsersPatternNames(): string[] {
|
|
44
|
+
return Array.from(this.otherUsersPatternNameToHash.keys())
|
|
45
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getCurrentUserName(): string {
|
|
49
|
+
return this.currentUserName;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private validatePatternNameUniqueness(patternName: string): void {
|
|
53
|
+
if (this.currentUserPatternNameToHash.has(patternName))
|
|
54
|
+
throw new PatternNameExistsError(`Pattern with name ${patternName} already exists`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private validatePatternUniqueness(hash: string): void {
|
|
58
|
+
const existingHashes = Array.from(this.currentUserPatternNameToHash.values())
|
|
59
|
+
.concat(Array.from(this.otherUsersPatternNameToHash.values()));
|
|
60
|
+
|
|
61
|
+
if (existingHashes.includes(hash))
|
|
62
|
+
throw new PatternExistsError(hash);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getPatternHash(patternName: string, isCurrentUserPattern: boolean): string {
|
|
66
|
+
const patternHash = isCurrentUserPattern ?
|
|
67
|
+
this.currentUserPatternNameToHash.get(patternName) :
|
|
68
|
+
this.otherUsersPatternNameToHash.get(patternName);
|
|
69
|
+
|
|
70
|
+
if (patternHash === undefined)
|
|
71
|
+
throw new Error(`Pattern with name ${patternName} not found`);
|
|
72
|
+
|
|
73
|
+
return patternHash;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getPatternRecord(hash: string): Promise<PatternConfigRecord | null> {
|
|
77
|
+
if (hash === null || hash === '')
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const patternConfig = await grok.dapi.userDataStorage.getValue(STORAGE_NAME, hash, false);
|
|
81
|
+
const config = JSON.parse(patternConfig) as PatternConfigRecord;
|
|
82
|
+
return config;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getPatternConfig(hash: string | null): Promise<PatternConfiguration | null> {
|
|
89
|
+
if (hash === '' || hash === null)
|
|
90
|
+
return null;
|
|
91
|
+
const record = await this.getPatternRecord(hash);
|
|
92
|
+
const config = record === null ? null : record[R.PATTERN_CONFIG] as PatternConfiguration;
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getDefaultPatternRecord(): PatternConfigRecord {
|
|
97
|
+
const record = EXAMPLE_PATTERN_CONFIG as PatternConfigRecord;
|
|
98
|
+
record[R.AUTHOR_ID] = this.currentUserId;
|
|
99
|
+
return record;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getDefaultPatternConfig(): PatternConfiguration {
|
|
103
|
+
return EXAMPLE_PATTERN_CONFIG[R.PATTERN_CONFIG] as PatternConfiguration;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private getAuthorCategoryByHash(hash: string): string {
|
|
107
|
+
if (this.isCurrentUserPattern(hash))
|
|
108
|
+
return this.getCurrentUserAuthorshipCategory();
|
|
109
|
+
else if (this.isOtherUserPattern(hash))
|
|
110
|
+
return this.getOtherUsersAuthorshipCategory();
|
|
111
|
+
else
|
|
112
|
+
throw new Error(`Pattern with hash ${hash} not found`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private isCurrentUserPattern(hash: string): boolean {
|
|
116
|
+
return Array.from(this.currentUserPatternNameToHash.values()).includes(hash);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private isOtherUserPattern(hash: string): boolean {
|
|
120
|
+
return Array.from(this.otherUsersPatternNameToHash.values()).includes(hash);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getPatternNameByHash(hash: string): string {
|
|
124
|
+
const maps = [this.currentUserPatternNameToHash, this.otherUsersPatternNameToHash];
|
|
125
|
+
for (const map of maps) {
|
|
126
|
+
for (const [patternName, patternHash] of map.entries()) {
|
|
127
|
+
if (patternHash === hash)
|
|
128
|
+
return patternName;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw new Error(`Pattern with hash ${hash} not found`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private getHashOfPatternToBeLoadedAfterDeletion(): string {
|
|
135
|
+
const nameOfPatternToBeLoaded = this.getCurrentUserPatternNames()[0];
|
|
136
|
+
if (!nameOfPatternToBeLoaded)
|
|
137
|
+
throw new Error('Cannot load pattern after deletion, as there are no patterns left');
|
|
138
|
+
const hash = this.currentUserPatternNameToHash.get(nameOfPatternToBeLoaded);
|
|
139
|
+
if (hash === undefined)
|
|
140
|
+
throw new Error(`Pattern with name ${nameOfPatternToBeLoaded} not found`);
|
|
141
|
+
return hash;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async getRecordFromPattern(patternConfig: PatternConfiguration): Promise<string> {
|
|
145
|
+
const record = {
|
|
146
|
+
[R.PATTERN_CONFIG]: patternConfig,
|
|
147
|
+
[R.AUTHOR_ID]: await grok.dapi.users.current().then((u) => u.id),
|
|
148
|
+
};
|
|
149
|
+
const stringifiedRecord = JSON.stringify(record);
|
|
150
|
+
return stringifiedRecord;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private getHash(patternConfig: PatternConfiguration): string {
|
|
154
|
+
// WARNING: hash is computed over the graph settings only, as the ones defining the pattern's identity
|
|
155
|
+
const graphSettings = GKL.reduce((acc, key) => {
|
|
156
|
+
acc[key] = patternConfig[key as keyof PatternConfiguration];
|
|
157
|
+
return acc;
|
|
158
|
+
}, {} as any);
|
|
159
|
+
return objectHash.sha1(graphSettings);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async savePatternToUserStorage(
|
|
163
|
+
eventBus: EventBus
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const patternConfig = eventBus.getPatternConfig();
|
|
166
|
+
try {
|
|
167
|
+
const hash = this.getHash(patternConfig);
|
|
168
|
+
this.validatePatternUniqueness(hash);
|
|
169
|
+
|
|
170
|
+
const patternName = patternConfig[L.PATTERN_NAME];
|
|
171
|
+
this.validatePatternNameUniqueness(patternName);
|
|
172
|
+
|
|
173
|
+
const record = await this.getRecordFromPattern(patternConfig);
|
|
174
|
+
await grok.dapi.userDataStorage.postValue(STORAGE_NAME, hash, record, false);
|
|
175
|
+
this.currentUserPatternNameToHash.set(patternName, hash);
|
|
176
|
+
|
|
177
|
+
eventBus.selectAuthor(this.getCurrentUserAuthorshipCategory());
|
|
178
|
+
|
|
179
|
+
eventBus.updatePatternList();
|
|
180
|
+
} catch (e) {
|
|
181
|
+
if (e instanceof PatternNameExistsError || e instanceof PatternExistsError)
|
|
182
|
+
throw e;
|
|
183
|
+
else
|
|
184
|
+
console.error('Error while saving pattern to user storage', e);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async overwritePatternInUserStorage(
|
|
189
|
+
eventBus: EventBus
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
const patternConfig = eventBus.getPatternConfig();
|
|
192
|
+
const hash = this.getHash(patternConfig);
|
|
193
|
+
const patternName = patternConfig[L.PATTERN_NAME];
|
|
194
|
+
const record = await this.getRecordFromPattern(patternConfig);
|
|
195
|
+
await grok.dapi.userDataStorage.postValue(STORAGE_NAME, hash, record, false);
|
|
196
|
+
this.currentUserPatternNameToHash.set(patternName, hash);
|
|
197
|
+
// eventBus.selectUser(this.getCurrentUserAuthorshipCategory());
|
|
198
|
+
eventBus.updatePatternList();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async deletePattern(
|
|
202
|
+
patternName: string,
|
|
203
|
+
eventBus: EventBus
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
const hash = this.currentUserPatternNameToHash.get(patternName);
|
|
206
|
+
if (patternName === this.getDefaultPatternName()) {
|
|
207
|
+
grok.shell.warning(`Cannot delete default pattern`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (hash === undefined)
|
|
211
|
+
throw new Error(`Pattern with name ${patternName} not found`);
|
|
212
|
+
await grok.dapi.userDataStorage.remove(STORAGE_NAME, hash, false);
|
|
213
|
+
this.currentUserPatternNameToHash.delete(patternName);
|
|
214
|
+
eventBus.updatePatternList();
|
|
215
|
+
|
|
216
|
+
const hashOfPatternToBeLoaded = this.getHashOfPatternToBeLoadedAfterDeletion();
|
|
217
|
+
eventBus.requestPatternLoad(hashOfPatternToBeLoaded);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fetchDefaultNucleobase(): string {
|
|
221
|
+
return this.fetchAvailableNucleotideBases()[0];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fetchAvailableNucleotideBases(): string[] {
|
|
225
|
+
const format = Object.keys(PATTERN_APP_DATA)[0];
|
|
226
|
+
const nucleotideBases: string[] = Object.keys(PATTERN_APP_DATA[format]);
|
|
227
|
+
return nucleotideBases;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
private async fetchCurrentUserName(): Promise<string> {
|
|
232
|
+
return (await grok.dapi.users.current()).friendlyName;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async fetchCurrentUserId(): Promise<string> {
|
|
236
|
+
return (await grok.dapi.users.current()).id;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private async fetchPatterns() {
|
|
240
|
+
const patternsRecord = await grok.dapi.userDataStorage.get(STORAGE_NAME, false) as RawPatternRecords;
|
|
241
|
+
return patternsRecord;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private async initializePatternMaps(patternRecords: RawPatternRecords) {
|
|
245
|
+
if (!this.currentUserId)
|
|
246
|
+
throw new Error('Current user ID is not set');
|
|
247
|
+
|
|
248
|
+
const userIdsToUserNames = new Map<string, string>();
|
|
249
|
+
|
|
250
|
+
for (const [patternHash, stringifiedRecord] of Object.entries(patternRecords))
|
|
251
|
+
await this.extractDataFromRecordToMaps(patternHash, stringifiedRecord, userIdsToUserNames);
|
|
252
|
+
|
|
253
|
+
this.setDefaultPattern();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private setDefaultPattern(): void {
|
|
257
|
+
const defaultPatternConfig = EXAMPLE_PATTERN_CONFIG[R.PATTERN_CONFIG] as PatternConfiguration;
|
|
258
|
+
this.currentUserPatternNameToHash.set(defaultPatternConfig[L.PATTERN_NAME], '');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async extractDataFromRecordToMaps(
|
|
262
|
+
patternHash: string,
|
|
263
|
+
stringifiedRecord: string,
|
|
264
|
+
userIdsToUserNames: Map<string, string>
|
|
265
|
+
) {
|
|
266
|
+
const record = JSON.parse(stringifiedRecord);
|
|
267
|
+
const patternConfig = record[R.PATTERN_CONFIG] as PatternConfiguration;
|
|
268
|
+
const patternName = patternConfig.patternName;
|
|
269
|
+
const authorID = record[R.AUTHOR_ID];
|
|
270
|
+
if (this.isCurrentUserId(authorID)) {
|
|
271
|
+
this.currentUserPatternNameToHash.set(patternName, patternHash);
|
|
272
|
+
} else {
|
|
273
|
+
if (!userIdsToUserNames.has(authorID)) {
|
|
274
|
+
const userFriendlyName = (await grok.dapi.users.find(authorID)).friendlyName;
|
|
275
|
+
userIdsToUserNames.set(authorID, userFriendlyName);
|
|
276
|
+
}
|
|
277
|
+
const fullPatternName = patternName + ` (created by ${userIdsToUserNames.get(authorID)})`;
|
|
278
|
+
this.otherUsersPatternNameToHash.set(fullPatternName, patternHash);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getDefaultPatternName(): string {
|
|
283
|
+
return EXAMPLE_PATTERN_CONFIG[R.PATTERN_CONFIG][L.PATTERN_NAME];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
getCurrentUserAuthorshipCategory(): string {
|
|
287
|
+
return this.currentUserName + ' (me)';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
getOtherUsersAuthorshipCategory(): string {
|
|
291
|
+
return OTHER_USERS;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
isCurrentUserId(userId: string): boolean {
|
|
295
|
+
return userId === this.currentUserId;
|
|
296
|
+
}
|
|
297
|
+
}
|