@datagrok/sequence-translator 1.3.9 → 1.3.10

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.3.9",
4
+ "version": "1.3.10",
5
5
  "author": {
6
6
  "name": "Alexey Choposky",
7
7
  "email": "achopovsky@datagrok.ai"
@@ -57,6 +57,12 @@ export namespace LEGEND_SETTINGS_KEYS {
57
57
  export namespace PATTERN_RECORD_KEYS {
58
58
  export const PATTERN_CONFIG = 'patternConfig';
59
59
  export const AUTHOR_ID = 'authorID';
60
+ export const DATE = 'date';
61
+ }
62
+
63
+ export namespace DATE_KEYS {
64
+ export const CREATE = 'create';
65
+ export const MODIFY = 'modify';
60
66
  }
61
67
 
62
68
  export const GRAPH_SETTINGS_KEY_LIST = [
@@ -72,11 +78,6 @@ export const LEGEND_SETTINGS_KEY_LIST = [
72
78
  LEGEND_SETTINGS_KEYS.NUCLEOTIDES_WITH_NUMERIC_LABELS
73
79
  ];
74
80
 
75
- export const PATTERN_RECORD_KEY_LIST = [
76
- PATTERN_RECORD_KEYS.PATTERN_CONFIG,
77
- PATTERN_RECORD_KEYS.AUTHOR_ID
78
- ];
79
-
80
81
  export const EXAMPLE_PATTERN_CONFIG =
81
82
  {
82
83
  'patternConfig': {
@@ -119,3 +120,5 @@ export const EXAMPLE_PATTERN_CONFIG =
119
120
  },
120
121
  'authorID': ''
121
122
  };
123
+
124
+ export const DEFAULT_DATE = '2024-01-01T18:00:00.000Z';
@@ -3,7 +3,8 @@ import * as grok from 'datagrok-api/grok';
3
3
 
4
4
  import {
5
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
6
+ GRAPH_SETTINGS_KEY_LIST as GKL, LEGEND_SETTINGS_KEYS as L,
7
+ OTHER_USERS, PATTERN_RECORD_KEYS as R, STORAGE_NAME, DATE_KEYS as D
7
8
  } from './const';
8
9
  import {
9
10
  PatternConfigRecord, PatternConfiguration, PatternExistsError, PatternNameExistsError, RawPatternRecords
@@ -76,7 +77,7 @@ export class DataManager {
76
77
  return patternHash;
77
78
  }
78
79
 
79
- async getPatternRecord(hash: string): Promise<PatternConfigRecord | null> {
80
+ async getPatternRecordByHash(hash: string): Promise<PatternConfigRecord | null> {
80
81
  if (hash === null || hash === '')
81
82
  return null;
82
83
  try {
@@ -91,7 +92,7 @@ export class DataManager {
91
92
  async getPatternConfig(hash: string | null): Promise<PatternConfiguration | null> {
92
93
  if (hash === '' || hash === null)
93
94
  return null;
94
- const record = await this.getPatternRecord(hash);
95
+ const record = await this.getPatternRecordByHash(hash);
95
96
  const config = record === null ? null : record[R.PATTERN_CONFIG] as PatternConfiguration;
96
97
  return config;
97
98
  }
@@ -144,13 +145,13 @@ export class DataManager {
144
145
  return hash;
145
146
  }
146
147
 
147
- private async getRecordFromPattern(patternConfig: PatternConfiguration): Promise<string> {
148
- const record = {
148
+ private async getRecordFromPattern(
149
+ patternConfig: PatternConfiguration
150
+ ): Promise<PatternConfigRecord> {
151
+ return {
149
152
  [R.PATTERN_CONFIG]: patternConfig,
150
153
  [R.AUTHOR_ID]: await grok.dapi.users.current().then((u) => u.id),
151
154
  };
152
- const stringifiedRecord = JSON.stringify(record);
153
- return stringifiedRecord;
154
155
  }
155
156
 
156
157
  private getHash(patternConfig: PatternConfiguration): string {
@@ -173,13 +174,21 @@ export class DataManager {
173
174
  const patternName = patternConfig[L.PATTERN_NAME];
174
175
  this.validatePatternNameUniqueness(patternName);
175
176
 
176
- const record = await this.getRecordFromPattern(patternConfig);
177
+ const recordObj = await this.getRecordFromPattern(patternConfig);
178
+ const timestamp = new Date().toISOString();
179
+ recordObj[R.DATE] = {
180
+ [D.CREATE]: timestamp,
181
+ [D.MODIFY]: timestamp,
182
+ };
183
+ const record = JSON.stringify(recordObj);
177
184
  await grok.dapi.userDataStorage.postValue(STORAGE_NAME, hash, record, false);
178
185
  this.currentUserPatternNameToHash.set(patternName, hash);
179
186
 
180
187
  eventBus.selectAuthor(this.getCurrentUserAuthorshipCategory());
181
188
 
182
189
  eventBus.updatePatternList();
190
+ eventBus.requestPatternLoad(hash);
191
+ eventBus.updateUrlState(hash);
183
192
  } catch (e) {
184
193
  if (e instanceof PatternNameExistsError || e instanceof PatternExistsError)
185
194
  throw e;
@@ -188,17 +197,33 @@ export class DataManager {
188
197
  }
189
198
  }
190
199
 
191
- async overwritePatternInUserStorage(
200
+ async overwriteExistingPatternInUserStorage(
192
201
  eventBus: EventBus
193
202
  ): Promise<void> {
194
203
  const patternConfig = eventBus.getPatternConfig();
195
- const hash = this.getHash(patternConfig);
196
204
  const patternName = patternConfig[L.PATTERN_NAME];
197
- const record = await this.getRecordFromPattern(patternConfig);
198
- await grok.dapi.userDataStorage.postValue(STORAGE_NAME, hash, record, false);
199
- this.currentUserPatternNameToHash.set(patternName, hash);
200
- // eventBus.selectUser(this.getCurrentUserAuthorshipCategory());
201
- eventBus.updatePatternList();
205
+ const oldHash = this.currentUserPatternNameToHash.get(patternName);
206
+
207
+ if (oldHash === undefined)
208
+ throw new Error(`Old hash is undefined`);
209
+ const newHash = this.getHash(patternConfig);
210
+ const newRecordObj = await this.getRecordFromPattern(patternConfig);
211
+ const timestamp = new Date().toISOString();
212
+ newRecordObj[R.DATE] = {
213
+ [D.MODIFY]: timestamp,
214
+ };
215
+ const oldPattern = await grok.dapi.userDataStorage.getValue(STORAGE_NAME, oldHash, false);
216
+ const oldPatternsRecord = JSON.parse(oldPattern) as PatternConfigRecord;
217
+ if (oldPatternsRecord[R.DATE] !== undefined && oldPatternsRecord[R.DATE][D.CREATE] != undefined)
218
+ newRecordObj[R.DATE][D.CREATE] = oldPatternsRecord[R.DATE][D.CREATE];
219
+
220
+ const newRecord = JSON.stringify(newRecordObj);
221
+ await grok.dapi.userDataStorage.postValue(STORAGE_NAME, newHash, newRecord, false);
222
+ await grok.dapi.userDataStorage.remove(STORAGE_NAME, oldHash, false);
223
+
224
+ this.currentUserPatternNameToHash.set(patternName, newHash);
225
+ eventBus.requestPatternLoad(newHash);
226
+ eventBus.updateUrlState(newHash);
202
227
  }
203
228
 
204
229
  async deletePattern(
@@ -434,6 +434,8 @@ export class EventBus {
434
434
  }
435
435
 
436
436
  selectAuthor(username: string) {
437
+ if (typeof username !== 'string')
438
+ throw new Error('Selected user must be defined');
437
439
  this._patternAuthorSelection$.next(username);
438
440
  }
439
441
 
@@ -1,11 +1,7 @@
1
- /* Do not change these import lines to match external modules in webpack configuration */
2
- import * as grok from 'datagrok-api/grok';
3
- import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
-
6
1
  import {
7
2
  TERMINI, STRANDS,
8
- GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R
3
+ GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R,
4
+ DATE_KEYS as D
9
5
  } from './const';
10
6
 
11
7
  export type StrandType = typeof STRANDS[number];
@@ -30,9 +26,15 @@ export type PatternLegendSettings = {
30
26
 
31
27
  export type PatternConfiguration = PatternGraphSettings & PatternLegendSettings;
32
28
 
29
+ type DateRecord = {
30
+ [D.CREATE]?: string,
31
+ [D.MODIFY]: string;
32
+ }
33
+
33
34
  export type PatternConfigRecord = {
34
35
  [R.PATTERN_CONFIG]: PatternConfiguration,
35
36
  [R.AUTHOR_ID]: string,
37
+ [R.DATE]?: DateRecord
36
38
  }
37
39
 
38
40
  export class PatternNameExistsError extends Error {
@@ -94,12 +94,15 @@ export class PatternLoadControlsManager {
94
94
  possibleValues.push(this.dataManager.getOtherUsersAuthorshipCategory());
95
95
 
96
96
  const authorChoiceInput = ui.input.choice(
97
- 'Author', {value: this.eventBus.getSelectedAuthor(), items: possibleValues,
98
- onValueChanged: (input) => {
99
- this.authorSelectedByUser = true;
100
- this.eventBus.selectAuthor(input.value);
101
- }}
102
- );
97
+ 'Author', {value: this.eventBus.getSelectedAuthor(), items: possibleValues});
98
+
99
+ authorChoiceInput.onInput(() => {
100
+ this.authorSelectedByUser = true;
101
+ if (authorChoiceInput.value === null)
102
+ throw new Error('author choice must be non-null');
103
+ this.eventBus.selectAuthor(authorChoiceInput.value);
104
+ });
105
+
103
106
  this.setAuthorChoiceInputStyle(authorChoiceInput);
104
107
  authorChoiceInput.setTooltip('Select pattern author');
105
108
 
@@ -7,6 +7,7 @@ import {PatternExistsError, PatternNameExistsError} from '../../model/types';
7
7
  import {SvgDisplayManager} from '../svg-utils/svg-display-manager';
8
8
  import {NumericLabelVisibilityControls} from './numeric-label-visibility-controls';
9
9
  import {TranslationExamplesBlock} from './translation-examples-block';
10
+ import {DEFAULT_DATE, PATTERN_RECORD_KEYS as P, DATE_KEYS as D, LEGEND_SETTINGS_KEYS as L} from '../../model/const';
10
11
 
11
12
  export class PatternAppRightSection {
12
13
  private svgDisplay: HTMLDivElement;
@@ -44,6 +45,7 @@ export class PatternAppRightSection {
44
45
  this.createSavePatternButton(),
45
46
  this.createDownloadPngButton(),
46
47
  this.createShareLinkButton(),
48
+ this.createInfoButton(),
47
49
  ], {style: {gap: '12px', marginTop: '12px'}});
48
50
  }
49
51
 
@@ -55,6 +57,53 @@ export class PatternAppRightSection {
55
57
  return svgDownloadButton;
56
58
  }
57
59
 
60
+ private createInfoButton(): HTMLButtonElement {
61
+ const shareLinkButton = ui.button(
62
+ ui.iconFA('info-circle'),
63
+ () => this.openInfoDialog()
64
+ );
65
+
66
+ this.eventBus.patternHasUnsavedChanges$.subscribe((hasUnsavedChanges: boolean) => {
67
+ shareLinkButton.disabled = hasUnsavedChanges;
68
+ });
69
+
70
+ ui.tooltip.bind(shareLinkButton, 'View pattern metadata');
71
+ return shareLinkButton;
72
+ }
73
+
74
+ private async openInfoDialog() {
75
+ let author = this.dataManager.getCurrentUserName();
76
+ let patternName = this.dataManager.getDefaultPatternName();
77
+ let createDate = DEFAULT_DATE;
78
+ let modifyDate = DEFAULT_DATE;
79
+ const hash = new URLSearchParams(window.location.search).get('pattern');
80
+ if (hash !== null) {
81
+ const record = await this.dataManager.getPatternRecordByHash(hash);
82
+ if (record !== null) {
83
+ const authorID = record[P.AUTHOR_ID];
84
+ const userFriendlyName = (await grok.dapi.users.find(authorID)).friendlyName;
85
+ author = userFriendlyName;
86
+ patternName = record[P.PATTERN_CONFIG][L.PATTERN_NAME];
87
+ if (record[P.DATE] !== undefined) {
88
+ const create = record[P.DATE][D.CREATE];
89
+ if (create !== undefined)
90
+ createDate = create;
91
+ const modify = record[P.DATE][D.MODIFY];
92
+ if (modify !== undefined)
93
+ modifyDate = modify;
94
+ }
95
+ }
96
+ }
97
+
98
+ const message = ui.divV([
99
+ ui.divText(`Author: ${author}`),
100
+ ui.divText(`Pattern Name: ${patternName}`),
101
+ ui.divText(`Created: ${new Date(createDate).toLocaleString()}`),
102
+ ui.divText(`Modified: ${new Date(modifyDate).toLocaleString()}`),
103
+ ]);
104
+ grok.shell.info(message);
105
+ }
106
+
58
107
  private createShareLinkButton(): HTMLButtonElement {
59
108
  const shareLinkButton = ui.button(
60
109
  ui.iconFA('link'),
@@ -135,7 +184,7 @@ class OverwritePatternDialog {
135
184
 
136
185
  private processOverwriteNamesakePattern(): void {
137
186
  const patternName = this.eventBus.getPatternName();
138
- this.dataManager.overwritePatternInUserStorage(this.eventBus)
187
+ this.dataManager.overwriteExistingPatternInUserStorage(this.eventBus)
139
188
  .then(() => {
140
189
  grok.shell.info(`Pattern ${patternName} overwritten`);
141
190
  })
@@ -55,7 +55,7 @@ async function getInitialPatternRecord(
55
55
  return dataManager.getDefaultPatternRecord();
56
56
  }
57
57
 
58
- let initialPatternRecord = await dataManager.getPatternRecord(patternHash);
58
+ let initialPatternRecord = await dataManager.getPatternRecordByHash(patternHash);
59
59
  if (!initialPatternRecord) {
60
60
  urlRouter.clearPatternURL();
61
61
  initialPatternRecord = dataManager.getDefaultPatternRecord();