@openforis/arena-core 2.18.8 → 2.18.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.
@@ -10,9 +10,12 @@ const isSystemAdmin = (user) => getAuthGroups(user).some(authGroups_1.AuthGroups
10
10
  const isSurveyManager = (user) => getAuthGroups(user).some(authGroups_1.AuthGroups.isSurveyManager);
11
11
  const getAuthGroupBySurveyUuid = (surveyUuid, includeSystemAdmin = true) => (user) => {
12
12
  const authGroups = getAuthGroups(user);
13
- return includeSystemAdmin && isSystemAdmin(user)
14
- ? authGroups.find(authGroups_1.AuthGroups.isSystemAdmin)
15
- : authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid);
13
+ if (includeSystemAdmin && isSystemAdmin(user))
14
+ return authGroups.find(authGroups_1.AuthGroups.isSystemAdmin);
15
+ // without a survey uuid no survey group can match (groups not associated to a survey, e.g. survey manager, have no survey uuid)
16
+ if (!surveyUuid)
17
+ return undefined;
18
+ return authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid);
16
19
  };
17
20
  const getAuthGroupByName = (groupName) => (user) => {
18
21
  const authGroups = getAuthGroups(user);
@@ -1 +1 @@
1
- {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/auth/users.ts"],"names":[],"mappings":";;;AAEA,6CAAyC;AACzC,iCAAwD;AAExD,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAE3B,MAAM,WAAW,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,MAAK,iBAAU,CAAC,QAAQ,CAAA;AAEjF,MAAM,aAAa,GAAG,CAAC,IAAU,EAAmB,EAAE,wBAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,mCAAI,EAAE,GAAA,CAAA;AAE7E,MAAM,aAAa,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,uBAAU,CAAC,aAAa,CAAC,CAAA;AAEjG,MAAM,eAAe,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,uBAAU,CAAC,eAAe,CAAC,CAAA;AAErG,MAAM,wBAAwB,GAC5B,CAAC,UAAmB,EAAE,kBAAkB,GAAG,IAAI,EAAE,EAAE,CACnD,CAAC,IAAU,EAA6B,EAAE;IACxC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,kBAAkB,IAAI,aAAa,CAAC,IAAI,CAAC;QAC9C,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,uBAAU,CAAC,aAAa,CAAC;QAC3C,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,KAAK,UAAU,CAAC,CAAA;AACzE,CAAC,CAAA;AAEH,MAAM,kBAAkB,GAAG,CAAC,SAAwB,EAAE,EAAE,CAAC,CAAC,IAAU,EAAE,EAAE;IACtE,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AAC7D,CAAC,CAAA;AAED,MAAM,qBAAqB,GACzB,CAAC,UAAkB,EAAE,EAAE,CACvB,CAAC,IAAU,EAAmB,EAAE;;;IAC9B,MAAM,mBAAmB,GAAG,IAAI,CAAC,CAAC,CAAC,QAAA,KAAK,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1F,MAAM,uBAAuB,SAAG,MAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,KAAK,0CAAE,KAAK,mCAAI,EAAE,CAAA;IACvE,MAAM,cAAc,SAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAC,KAAK,mCAAI,EAAE,CAAA;IAC9C,yDAAyD;IACzD,uCAAY,cAAc,GAAK,uBAAuB,EAAE;AAC1D,CAAC,CAAA;AAEH,MAAM,aAAa,GAAG,CAAC,IAAU,EAAE,EAAE,gCAAC,MAAA,IAAI,CAAC,KAAK,0CAAE,UAAU,mCAAI,iBAAiB,GAAA,CAAA;AAEjF,MAAM,OAAO,GACX,CAAC,aAAmB,EAAE,EAAE,CACxB,CAAC,IAAU,EAAW,EAAE,CACtB,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;AAEjF,QAAA,KAAK,GAAG;IACnB,WAAW;IACX,aAAa;IACb,aAAa;IACb,eAAe;IACf,wBAAwB;IACxB,kBAAkB;IAClB,qBAAqB;IACrB,aAAa;IACb,OAAO;CACR,CAAA","sourcesContent":["import { Dictionary } from '../common'\nimport { AuthGroupName } from './authGroup'\nimport { AuthGroups } from './authGroups'\nimport { User, UserAuthGroup, UserStatus } from './user'\n\nconst defaultMaxSurveys = 5\n\nconst hasAccepted = (user: User): boolean => user?.status === UserStatus.ACCEPTED\n\nconst getAuthGroups = (user: User): UserAuthGroup[] => user?.authGroups ?? []\n\nconst isSystemAdmin = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSystemAdmin)\n\nconst isSurveyManager = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSurveyManager)\n\nconst getAuthGroupBySurveyUuid =\n (surveyUuid?: string, includeSystemAdmin = true) =>\n (user: User): UserAuthGroup | undefined => {\n const authGroups = getAuthGroups(user)\n return includeSystemAdmin && isSystemAdmin(user)\n ? authGroups.find(AuthGroups.isSystemAdmin)\n : authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid)\n }\n\nconst getAuthGroupByName = (groupName: AuthGroupName) => (user: User) => {\n const authGroups = getAuthGroups(user)\n return authGroups.find((group) => group.name === groupName)\n}\n\nconst getCombinedExtraProps =\n (surveyUuid: string) =>\n (user: User): Dictionary<any> => {\n const userSurveyAuthGroup = user ? Users.getAuthGroupBySurveyUuid(surveyUuid)(user) : null\n const userAuthGroupExtraProps = userSurveyAuthGroup?.props?.extra ?? {}\n const userExtraProps = user?.props.extra ?? {}\n // user auth group extra props overwrite user extra props\n return { ...userExtraProps, ...userAuthGroupExtraProps }\n }\n\nconst getMaxSurveys = (user: User) => user.props?.maxSurveys ?? defaultMaxSurveys\n\nconst isEqual =\n (userToCompare: User) =>\n (user: User): boolean =>\n user && userToCompare ? user === userToCompare || user.uuid === userToCompare.uuid : false\n\nexport const Users = {\n hasAccepted,\n getAuthGroups,\n isSystemAdmin,\n isSurveyManager,\n getAuthGroupBySurveyUuid,\n getAuthGroupByName,\n getCombinedExtraProps,\n getMaxSurveys,\n isEqual,\n}\n"]}
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/auth/users.ts"],"names":[],"mappings":";;;AAEA,6CAAyC;AACzC,iCAAwD;AAExD,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAE3B,MAAM,WAAW,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,MAAK,iBAAU,CAAC,QAAQ,CAAA;AAEjF,MAAM,aAAa,GAAG,CAAC,IAAU,EAAmB,EAAE,wBAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,mCAAI,EAAE,GAAA,CAAA;AAE7E,MAAM,aAAa,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,uBAAU,CAAC,aAAa,CAAC,CAAA;AAEjG,MAAM,eAAe,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,uBAAU,CAAC,eAAe,CAAC,CAAA;AAErG,MAAM,wBAAwB,GAC5B,CAAC,UAAmB,EAAE,kBAAkB,GAAG,IAAI,EAAE,EAAE,CACnD,CAAC,IAAU,EAA6B,EAAE;IACxC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,kBAAkB,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC,IAAI,CAAC,uBAAU,CAAC,aAAa,CAAC,CAAA;IAC/F,gIAAgI;IAChI,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IACjC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,KAAK,UAAU,CAAC,CAAA;AAC5E,CAAC,CAAA;AAEH,MAAM,kBAAkB,GAAG,CAAC,SAAwB,EAAE,EAAE,CAAC,CAAC,IAAU,EAAE,EAAE;IACtE,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AAC7D,CAAC,CAAA;AAED,MAAM,qBAAqB,GACzB,CAAC,UAAkB,EAAE,EAAE,CACvB,CAAC,IAAU,EAAmB,EAAE;;;IAC9B,MAAM,mBAAmB,GAAG,IAAI,CAAC,CAAC,CAAC,QAAA,KAAK,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1F,MAAM,uBAAuB,SAAG,MAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,KAAK,0CAAE,KAAK,mCAAI,EAAE,CAAA;IACvE,MAAM,cAAc,SAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAC,KAAK,mCAAI,EAAE,CAAA;IAC9C,yDAAyD;IACzD,uCAAY,cAAc,GAAK,uBAAuB,EAAE;AAC1D,CAAC,CAAA;AAEH,MAAM,aAAa,GAAG,CAAC,IAAU,EAAE,EAAE,gCAAC,MAAA,IAAI,CAAC,KAAK,0CAAE,UAAU,mCAAI,iBAAiB,GAAA,CAAA;AAEjF,MAAM,OAAO,GACX,CAAC,aAAmB,EAAE,EAAE,CACxB,CAAC,IAAU,EAAW,EAAE,CACtB,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;AAEjF,QAAA,KAAK,GAAG;IACnB,WAAW;IACX,aAAa;IACb,aAAa;IACb,eAAe;IACf,wBAAwB;IACxB,kBAAkB;IAClB,qBAAqB;IACrB,aAAa;IACb,OAAO;CACR,CAAA","sourcesContent":["import { Dictionary } from '../common'\nimport { AuthGroupName } from './authGroup'\nimport { AuthGroups } from './authGroups'\nimport { User, UserAuthGroup, UserStatus } from './user'\n\nconst defaultMaxSurveys = 5\n\nconst hasAccepted = (user: User): boolean => user?.status === UserStatus.ACCEPTED\n\nconst getAuthGroups = (user: User): UserAuthGroup[] => user?.authGroups ?? []\n\nconst isSystemAdmin = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSystemAdmin)\n\nconst isSurveyManager = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSurveyManager)\n\nconst getAuthGroupBySurveyUuid =\n (surveyUuid?: string, includeSystemAdmin = true) =>\n (user: User): UserAuthGroup | undefined => {\n const authGroups = getAuthGroups(user)\n if (includeSystemAdmin && isSystemAdmin(user)) return authGroups.find(AuthGroups.isSystemAdmin)\n // without a survey uuid no survey group can match (groups not associated to a survey, e.g. survey manager, have no survey uuid)\n if (!surveyUuid) return undefined\n return authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid)\n }\n\nconst getAuthGroupByName = (groupName: AuthGroupName) => (user: User) => {\n const authGroups = getAuthGroups(user)\n return authGroups.find((group) => group.name === groupName)\n}\n\nconst getCombinedExtraProps =\n (surveyUuid: string) =>\n (user: User): Dictionary<any> => {\n const userSurveyAuthGroup = user ? Users.getAuthGroupBySurveyUuid(surveyUuid)(user) : null\n const userAuthGroupExtraProps = userSurveyAuthGroup?.props?.extra ?? {}\n const userExtraProps = user?.props.extra ?? {}\n // user auth group extra props overwrite user extra props\n return { ...userExtraProps, ...userAuthGroupExtraProps }\n }\n\nconst getMaxSurveys = (user: User) => user.props?.maxSurveys ?? defaultMaxSurveys\n\nconst isEqual =\n (userToCompare: User) =>\n (user: User): boolean =>\n user && userToCompare ? user === userToCompare || user.uuid === userToCompare.uuid : false\n\nexport const Users = {\n hasAccepted,\n getAuthGroups,\n isSystemAdmin,\n isSurveyManager,\n getAuthGroupBySurveyUuid,\n getAuthGroupByName,\n getCombinedExtraProps,\n getMaxSurveys,\n isEqual,\n}\n"]}
@@ -3,6 +3,7 @@ export declare class UniqueFileNamesGenerator {
3
3
  private _fileNamesByKey;
4
4
  private _keysByFileName;
5
5
  private readonly _fileNamesLowerCase;
6
+ private readonly _lastGeneratedFileNameByFirstCandidate;
6
7
  constructor();
7
8
  private isFileNameUsed;
8
9
  private assignFileName;
@@ -8,6 +8,7 @@ class UniqueFileNamesGenerator {
8
8
  this._fileNamesByKey = {};
9
9
  this._keysByFileName = {};
10
10
  this._fileNamesLowerCase = new Set();
11
+ this._lastGeneratedFileNameByFirstCandidate = new Map();
11
12
  }
12
13
  isFileNameUsed(fileName) {
13
14
  return this._fileNamesLowerCase.has(fileName.toLowerCase());
@@ -35,6 +36,7 @@ class UniqueFileNamesGenerator {
35
36
  * @returns The unique file name associated with the given key.
36
37
  */
37
38
  generateUniqueFileName(inputFileName, key) {
39
+ var _a;
38
40
  const existingFileName = this._fileNamesByKey[key];
39
41
  if (existingFileName) {
40
42
  return existingFileName;
@@ -43,11 +45,13 @@ class UniqueFileNamesGenerator {
43
45
  // file name not used yet
44
46
  return this.assignFileName(inputFileName, key);
45
47
  }
46
- let generatedFileName = this.generateNextFileName(inputFileName);
48
+ const firstCandidate = this.generateNextFileName(inputFileName);
49
+ let generatedFileName = (_a = this._lastGeneratedFileNameByFirstCandidate.get(firstCandidate)) !== null && _a !== void 0 ? _a : firstCandidate;
47
50
  while (this.isFileNameUsed(generatedFileName)) {
48
51
  // file name already exists, generate the next one
49
52
  generatedFileName = this.generateNextFileName(generatedFileName);
50
53
  }
54
+ this._lastGeneratedFileNameByFirstCandidate.set(firstCandidate, generatedFileName);
51
55
  return this.assignFileName(generatedFileName, key);
52
56
  }
53
57
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"UniqueFileNamesGenerator.js","sourceRoot":"","sources":["../../src/dataExport/UniqueFileNamesGenerator.ts"],"names":[],"mappings":";;;AACA,oCAAoC;AAEpC,MAAM,kBAAkB,GAAG,kBAAkB,CAAA,CAAC,mCAAmC;AAEjF;IAME;QACE,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,mBAAmB,GAAG,IAAI,GAAG,EAAE,CAAA;IACtC,CAAC;IAEO,cAAc,CAAC,QAAgB;QACrC,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IAC7D,CAAC;IAEO,cAAc,CAAC,QAAgB,EAAE,GAAW;QAClD,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAA;QACpC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAA;QACpC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;QACpD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,sBAAsB,CAAC,aAAqB,EAAE,GAAW;QACvD,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;QAClD,IAAI,gBAAgB,EAAE,CAAC;YACrB,OAAO,gBAAgB,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC;YACxC,yBAAyB;YACzB,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,EAAE,GAAG,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAA;QAChE,OAAO,IAAI,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC9C,kDAAkD;YAClD,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAA;QAClE,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAA;IACpD,CAAC;IAED;;;;;;OAMG;IACO,oBAAoB,CAAC,aAAqB;QAClD,MAAM,IAAI,GAAG,iBAAS,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;QAC7C,MAAM,SAAS,GAAG,iBAAS,CAAC,YAAY,CAAC,aAAa,CAAC,CAAA;QACvD,IAAI,WAAW,CAAA;QACf,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAA;YAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACnC,WAAW,GAAG,GAAG,gBAAgB,KAAK,SAAS,GAAG,CAAA;QACpD,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,GAAG,IAAI,MAAM,CAAA;QAC7B,CAAC;QACD,OAAO,SAAS,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;IAChE,CAAC;IAED,IAAI,cAAc;QAChB,yBAAY,IAAI,CAAC,eAAe,EAAE;IACpC,CAAC;IAED,IAAI,cAAc;QAChB,yBAAY,IAAI,CAAC,eAAe,EAAE;IACpC,CAAC;CACF","sourcesContent":["import { Dictionary } from '../common'\nimport { FileNames } from '../utils'\n\nconst nameWithIndexRegEx = /^(.*) \\((\\d+)\\)$/ // file name like \"example (1).txt\"\n\nexport class UniqueFileNamesGenerator {\n private _fileNamesByKey: Dictionary<string>\n private _keysByFileName: Dictionary<string>\n // file names are compared ignoring case: names differing only in case overwrite each other on Windows/macOS\n private readonly _fileNamesLowerCase: Set<string>\n\n constructor() {\n this._fileNamesByKey = {}\n this._keysByFileName = {}\n this._fileNamesLowerCase = new Set()\n }\n\n private isFileNameUsed(fileName: string): boolean {\n return this._fileNamesLowerCase.has(fileName.toLowerCase())\n }\n\n private assignFileName(fileName: string, key: string): string {\n this._keysByFileName[fileName] = key\n this._fileNamesByKey[key] = fileName\n this._fileNamesLowerCase.add(fileName.toLowerCase())\n return fileName\n }\n\n /**\n * Generates a unique file name for the given key.\n *\n * If the key has been used before, the previously generated file name\n * associated with that key is returned, regardless of the current\n * {@link inputFileName} value.\n *\n * If the key has not been used before, the method either:\n * - returns {@link inputFileName} if it has not been used for another key, or\n * - generates the next available non-conflicting name by appending or\n * incrementing a numeric index in parentheses.\n *\n * @param inputFileName The desired base file name, including extension.\n * @param key A logical identifier whose associated file name should remain stable across calls.\n * @returns The unique file name associated with the given key.\n */\n generateUniqueFileName(inputFileName: string, key: string): string {\n const existingFileName = this._fileNamesByKey[key]\n if (existingFileName) {\n return existingFileName\n }\n if (!this.isFileNameUsed(inputFileName)) {\n // file name not used yet\n return this.assignFileName(inputFileName, key)\n }\n let generatedFileName = this.generateNextFileName(inputFileName)\n while (this.isFileNameUsed(generatedFileName)) {\n // file name already exists, generate the next one\n generatedFileName = this.generateNextFileName(generatedFileName)\n }\n return this.assignFileName(generatedFileName, key)\n }\n\n /**\n * Generates a new file name by incrementing an existing numeric suffix\n * of the form \"name (n)\" or appending \" (1)\" if no such suffix exists.\n *\n * @param inputFileName The original file name, including its extension.\n * @returns The updated file name with an incremented or newly added numeric suffix.\n */\n protected generateNextFileName(inputFileName: string): string {\n const name = FileNames.getName(inputFileName)\n const extension = FileNames.getExtension(inputFileName)\n let nameUpdated\n const matchRes = nameWithIndexRegEx.exec(name)\n if (matchRes) {\n const [, nameWithoutIndex, index] = matchRes\n const nextIndex = Number(index) + 1\n nameUpdated = `${nameWithoutIndex} (${nextIndex})`\n } else {\n nameUpdated = `${name} (1)`\n }\n return extension ? `${nameUpdated}.${extension}` : nameUpdated\n }\n\n get fileNamesByKey(): Readonly<Dictionary<string>> {\n return { ...this._fileNamesByKey }\n }\n\n get keysByFileName(): Readonly<Dictionary<string>> {\n return { ...this._keysByFileName }\n }\n}\n"]}
1
+ {"version":3,"file":"UniqueFileNamesGenerator.js","sourceRoot":"","sources":["../../src/dataExport/UniqueFileNamesGenerator.ts"],"names":[],"mappings":"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","sourcesContent":["import { Dictionary } from '../common'\nimport { FileNames } from '../utils'\n\nconst nameWithIndexRegEx = /^(.*) \\((\\d+)\\)$/ // file name like \"example (1).txt\"\n\nexport class UniqueFileNamesGenerator {\n private _fileNamesByKey: Dictionary<string>\n private _keysByFileName: Dictionary<string>\n // file names are compared ignoring case: names differing only in case overwrite each other on Windows/macOS\n private readonly _fileNamesLowerCase: Set<string>\n // last file name generated starting from a given first candidate file name (e.g. \"image (1).jpg\" => \"image (25).jpg\"):\n // the names before it in the sequence are all used already, so the search can start from it\n private readonly _lastGeneratedFileNameByFirstCandidate: Map<string, string>\n\n constructor() {\n this._fileNamesByKey = {}\n this._keysByFileName = {}\n this._fileNamesLowerCase = new Set()\n this._lastGeneratedFileNameByFirstCandidate = new Map()\n }\n\n private isFileNameUsed(fileName: string): boolean {\n return this._fileNamesLowerCase.has(fileName.toLowerCase())\n }\n\n private assignFileName(fileName: string, key: string): string {\n this._keysByFileName[fileName] = key\n this._fileNamesByKey[key] = fileName\n this._fileNamesLowerCase.add(fileName.toLowerCase())\n return fileName\n }\n\n /**\n * Generates a unique file name for the given key.\n *\n * If the key has been used before, the previously generated file name\n * associated with that key is returned, regardless of the current\n * {@link inputFileName} value.\n *\n * If the key has not been used before, the method either:\n * - returns {@link inputFileName} if it has not been used for another key, or\n * - generates the next available non-conflicting name by appending or\n * incrementing a numeric index in parentheses.\n *\n * @param inputFileName The desired base file name, including extension.\n * @param key A logical identifier whose associated file name should remain stable across calls.\n * @returns The unique file name associated with the given key.\n */\n generateUniqueFileName(inputFileName: string, key: string): string {\n const existingFileName = this._fileNamesByKey[key]\n if (existingFileName) {\n return existingFileName\n }\n if (!this.isFileNameUsed(inputFileName)) {\n // file name not used yet\n return this.assignFileName(inputFileName, key)\n }\n const firstCandidate = this.generateNextFileName(inputFileName)\n let generatedFileName = this._lastGeneratedFileNameByFirstCandidate.get(firstCandidate) ?? firstCandidate\n while (this.isFileNameUsed(generatedFileName)) {\n // file name already exists, generate the next one\n generatedFileName = this.generateNextFileName(generatedFileName)\n }\n this._lastGeneratedFileNameByFirstCandidate.set(firstCandidate, generatedFileName)\n return this.assignFileName(generatedFileName, key)\n }\n\n /**\n * Generates a new file name by incrementing an existing numeric suffix\n * of the form \"name (n)\" or appending \" (1)\" if no such suffix exists.\n *\n * @param inputFileName The original file name, including its extension.\n * @returns The updated file name with an incremented or newly added numeric suffix.\n */\n protected generateNextFileName(inputFileName: string): string {\n const name = FileNames.getName(inputFileName)\n const extension = FileNames.getExtension(inputFileName)\n let nameUpdated\n const matchRes = nameWithIndexRegEx.exec(name)\n if (matchRes) {\n const [, nameWithoutIndex, index] = matchRes\n const nextIndex = Number(index) + 1\n nameUpdated = `${nameWithoutIndex} (${nextIndex})`\n } else {\n nameUpdated = `${name} (1)`\n }\n return extension ? `${nameUpdated}.${extension}` : nameUpdated\n }\n\n get fileNamesByKey(): Readonly<Dictionary<string>> {\n return { ...this._fileNamesByKey }\n }\n\n get keysByFileName(): Readonly<Dictionary<string>> {\n return { ...this._keysByFileName }\n }\n}\n"]}
@@ -7,9 +7,12 @@ const isSystemAdmin = (user) => getAuthGroups(user).some(AuthGroups.isSystemAdmi
7
7
  const isSurveyManager = (user) => getAuthGroups(user).some(AuthGroups.isSurveyManager);
8
8
  const getAuthGroupBySurveyUuid = (surveyUuid, includeSystemAdmin = true) => (user) => {
9
9
  const authGroups = getAuthGroups(user);
10
- return includeSystemAdmin && isSystemAdmin(user)
11
- ? authGroups.find(AuthGroups.isSystemAdmin)
12
- : authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid);
10
+ if (includeSystemAdmin && isSystemAdmin(user))
11
+ return authGroups.find(AuthGroups.isSystemAdmin);
12
+ // without a survey uuid no survey group can match (groups not associated to a survey, e.g. survey manager, have no survey uuid)
13
+ if (!surveyUuid)
14
+ return undefined;
15
+ return authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid);
13
16
  };
14
17
  const getAuthGroupByName = (groupName) => (user) => {
15
18
  const authGroups = getAuthGroups(user);
@@ -1 +1 @@
1
- {"version":3,"file":"users.js","sourceRoot":"","sources":["../../../src/auth/users.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAuB,UAAU,EAAE,MAAM,QAAQ,CAAA;AAExD,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAE3B,MAAM,WAAW,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,MAAK,UAAU,CAAC,QAAQ,CAAA;AAEjF,MAAM,aAAa,GAAG,CAAC,IAAU,EAAmB,EAAE,wBAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,mCAAI,EAAE,GAAA,CAAA;AAE7E,MAAM,aAAa,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AAEjG,MAAM,eAAe,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAA;AAErG,MAAM,wBAAwB,GAC5B,CAAC,UAAmB,EAAE,kBAAkB,GAAG,IAAI,EAAE,EAAE,CACnD,CAAC,IAAU,EAA6B,EAAE;IACxC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,kBAAkB,IAAI,aAAa,CAAC,IAAI,CAAC;QAC9C,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAC3C,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,KAAK,UAAU,CAAC,CAAA;AACzE,CAAC,CAAA;AAEH,MAAM,kBAAkB,GAAG,CAAC,SAAwB,EAAE,EAAE,CAAC,CAAC,IAAU,EAAE,EAAE;IACtE,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AAC7D,CAAC,CAAA;AAED,MAAM,qBAAqB,GACzB,CAAC,UAAkB,EAAE,EAAE,CACvB,CAAC,IAAU,EAAmB,EAAE;;;IAC9B,MAAM,mBAAmB,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1F,MAAM,uBAAuB,SAAG,MAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,KAAK,0CAAE,KAAK,mCAAI,EAAE,CAAA;IACvE,MAAM,cAAc,SAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAC,KAAK,mCAAI,EAAE,CAAA;IAC9C,yDAAyD;IACzD,uCAAY,cAAc,GAAK,uBAAuB,EAAE;AAC1D,CAAC,CAAA;AAEH,MAAM,aAAa,GAAG,CAAC,IAAU,EAAE,EAAE,gCAAC,MAAA,IAAI,CAAC,KAAK,0CAAE,UAAU,mCAAI,iBAAiB,GAAA,CAAA;AAEjF,MAAM,OAAO,GACX,CAAC,aAAmB,EAAE,EAAE,CACxB,CAAC,IAAU,EAAW,EAAE,CACtB,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;AAE9F,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,WAAW;IACX,aAAa;IACb,aAAa;IACb,eAAe;IACf,wBAAwB;IACxB,kBAAkB;IAClB,qBAAqB;IACrB,aAAa;IACb,OAAO;CACR,CAAA","sourcesContent":["import { Dictionary } from '../common'\nimport { AuthGroupName } from './authGroup'\nimport { AuthGroups } from './authGroups'\nimport { User, UserAuthGroup, UserStatus } from './user'\n\nconst defaultMaxSurveys = 5\n\nconst hasAccepted = (user: User): boolean => user?.status === UserStatus.ACCEPTED\n\nconst getAuthGroups = (user: User): UserAuthGroup[] => user?.authGroups ?? []\n\nconst isSystemAdmin = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSystemAdmin)\n\nconst isSurveyManager = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSurveyManager)\n\nconst getAuthGroupBySurveyUuid =\n (surveyUuid?: string, includeSystemAdmin = true) =>\n (user: User): UserAuthGroup | undefined => {\n const authGroups = getAuthGroups(user)\n return includeSystemAdmin && isSystemAdmin(user)\n ? authGroups.find(AuthGroups.isSystemAdmin)\n : authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid)\n }\n\nconst getAuthGroupByName = (groupName: AuthGroupName) => (user: User) => {\n const authGroups = getAuthGroups(user)\n return authGroups.find((group) => group.name === groupName)\n}\n\nconst getCombinedExtraProps =\n (surveyUuid: string) =>\n (user: User): Dictionary<any> => {\n const userSurveyAuthGroup = user ? Users.getAuthGroupBySurveyUuid(surveyUuid)(user) : null\n const userAuthGroupExtraProps = userSurveyAuthGroup?.props?.extra ?? {}\n const userExtraProps = user?.props.extra ?? {}\n // user auth group extra props overwrite user extra props\n return { ...userExtraProps, ...userAuthGroupExtraProps }\n }\n\nconst getMaxSurveys = (user: User) => user.props?.maxSurveys ?? defaultMaxSurveys\n\nconst isEqual =\n (userToCompare: User) =>\n (user: User): boolean =>\n user && userToCompare ? user === userToCompare || user.uuid === userToCompare.uuid : false\n\nexport const Users = {\n hasAccepted,\n getAuthGroups,\n isSystemAdmin,\n isSurveyManager,\n getAuthGroupBySurveyUuid,\n getAuthGroupByName,\n getCombinedExtraProps,\n getMaxSurveys,\n isEqual,\n}\n"]}
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../../src/auth/users.ts"],"names":[],"mappings":"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","sourcesContent":["import { Dictionary } from '../common'\nimport { AuthGroupName } from './authGroup'\nimport { AuthGroups } from './authGroups'\nimport { User, UserAuthGroup, UserStatus } from './user'\n\nconst defaultMaxSurveys = 5\n\nconst hasAccepted = (user: User): boolean => user?.status === UserStatus.ACCEPTED\n\nconst getAuthGroups = (user: User): UserAuthGroup[] => user?.authGroups ?? []\n\nconst isSystemAdmin = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSystemAdmin)\n\nconst isSurveyManager = (user: User): boolean => getAuthGroups(user).some(AuthGroups.isSurveyManager)\n\nconst getAuthGroupBySurveyUuid =\n (surveyUuid?: string, includeSystemAdmin = true) =>\n (user: User): UserAuthGroup | undefined => {\n const authGroups = getAuthGroups(user)\n if (includeSystemAdmin && isSystemAdmin(user)) return authGroups.find(AuthGroups.isSystemAdmin)\n // without a survey uuid no survey group can match (groups not associated to a survey, e.g. survey manager, have no survey uuid)\n if (!surveyUuid) return undefined\n return authGroups.find((authGroup) => authGroup.surveyUuid === surveyUuid)\n }\n\nconst getAuthGroupByName = (groupName: AuthGroupName) => (user: User) => {\n const authGroups = getAuthGroups(user)\n return authGroups.find((group) => group.name === groupName)\n}\n\nconst getCombinedExtraProps =\n (surveyUuid: string) =>\n (user: User): Dictionary<any> => {\n const userSurveyAuthGroup = user ? Users.getAuthGroupBySurveyUuid(surveyUuid)(user) : null\n const userAuthGroupExtraProps = userSurveyAuthGroup?.props?.extra ?? {}\n const userExtraProps = user?.props.extra ?? {}\n // user auth group extra props overwrite user extra props\n return { ...userExtraProps, ...userAuthGroupExtraProps }\n }\n\nconst getMaxSurveys = (user: User) => user.props?.maxSurveys ?? defaultMaxSurveys\n\nconst isEqual =\n (userToCompare: User) =>\n (user: User): boolean =>\n user && userToCompare ? user === userToCompare || user.uuid === userToCompare.uuid : false\n\nexport const Users = {\n hasAccepted,\n getAuthGroups,\n isSystemAdmin,\n isSurveyManager,\n getAuthGroupBySurveyUuid,\n getAuthGroupByName,\n getCombinedExtraProps,\n getMaxSurveys,\n isEqual,\n}\n"]}
@@ -5,6 +5,7 @@ export class UniqueFileNamesGenerator {
5
5
  this._fileNamesByKey = {};
6
6
  this._keysByFileName = {};
7
7
  this._fileNamesLowerCase = new Set();
8
+ this._lastGeneratedFileNameByFirstCandidate = new Map();
8
9
  }
9
10
  isFileNameUsed(fileName) {
10
11
  return this._fileNamesLowerCase.has(fileName.toLowerCase());
@@ -32,6 +33,7 @@ export class UniqueFileNamesGenerator {
32
33
  * @returns The unique file name associated with the given key.
33
34
  */
34
35
  generateUniqueFileName(inputFileName, key) {
36
+ var _a;
35
37
  const existingFileName = this._fileNamesByKey[key];
36
38
  if (existingFileName) {
37
39
  return existingFileName;
@@ -40,11 +42,13 @@ export class UniqueFileNamesGenerator {
40
42
  // file name not used yet
41
43
  return this.assignFileName(inputFileName, key);
42
44
  }
43
- let generatedFileName = this.generateNextFileName(inputFileName);
45
+ const firstCandidate = this.generateNextFileName(inputFileName);
46
+ let generatedFileName = (_a = this._lastGeneratedFileNameByFirstCandidate.get(firstCandidate)) !== null && _a !== void 0 ? _a : firstCandidate;
44
47
  while (this.isFileNameUsed(generatedFileName)) {
45
48
  // file name already exists, generate the next one
46
49
  generatedFileName = this.generateNextFileName(generatedFileName);
47
50
  }
51
+ this._lastGeneratedFileNameByFirstCandidate.set(firstCandidate, generatedFileName);
48
52
  return this.assignFileName(generatedFileName, key);
49
53
  }
50
54
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"UniqueFileNamesGenerator.js","sourceRoot":"","sources":["../../../src/dataExport/UniqueFileNamesGenerator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEpC,MAAM,kBAAkB,GAAG,kBAAkB,CAAA,CAAC,mCAAmC;AAEjF,MAAM,OAAO,wBAAwB;IAMnC;QACE,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,mBAAmB,GAAG,IAAI,GAAG,EAAE,CAAA;IACtC,CAAC;IAEO,cAAc,CAAC,QAAgB;QACrC,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IAC7D,CAAC;IAEO,cAAc,CAAC,QAAgB,EAAE,GAAW;QAClD,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAA;QACpC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAA;QACpC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;QACpD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,sBAAsB,CAAC,aAAqB,EAAE,GAAW;QACvD,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;QAClD,IAAI,gBAAgB,EAAE,CAAC;YACrB,OAAO,gBAAgB,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC;YACxC,yBAAyB;YACzB,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,EAAE,GAAG,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAA;QAChE,OAAO,IAAI,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC9C,kDAAkD;YAClD,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAA;QAClE,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAA;IACpD,CAAC;IAED;;;;;;OAMG;IACO,oBAAoB,CAAC,aAAqB;QAClD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;QAC7C,MAAM,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC,aAAa,CAAC,CAAA;QACvD,IAAI,WAAW,CAAA;QACf,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAA;YAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACnC,WAAW,GAAG,GAAG,gBAAgB,KAAK,SAAS,GAAG,CAAA;QACpD,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,GAAG,IAAI,MAAM,CAAA;QAC7B,CAAC;QACD,OAAO,SAAS,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;IAChE,CAAC;IAED,IAAI,cAAc;QAChB,yBAAY,IAAI,CAAC,eAAe,EAAE;IACpC,CAAC;IAED,IAAI,cAAc;QAChB,yBAAY,IAAI,CAAC,eAAe,EAAE;IACpC,CAAC;CACF","sourcesContent":["import { Dictionary } from '../common'\nimport { FileNames } from '../utils'\n\nconst nameWithIndexRegEx = /^(.*) \\((\\d+)\\)$/ // file name like \"example (1).txt\"\n\nexport class UniqueFileNamesGenerator {\n private _fileNamesByKey: Dictionary<string>\n private _keysByFileName: Dictionary<string>\n // file names are compared ignoring case: names differing only in case overwrite each other on Windows/macOS\n private readonly _fileNamesLowerCase: Set<string>\n\n constructor() {\n this._fileNamesByKey = {}\n this._keysByFileName = {}\n this._fileNamesLowerCase = new Set()\n }\n\n private isFileNameUsed(fileName: string): boolean {\n return this._fileNamesLowerCase.has(fileName.toLowerCase())\n }\n\n private assignFileName(fileName: string, key: string): string {\n this._keysByFileName[fileName] = key\n this._fileNamesByKey[key] = fileName\n this._fileNamesLowerCase.add(fileName.toLowerCase())\n return fileName\n }\n\n /**\n * Generates a unique file name for the given key.\n *\n * If the key has been used before, the previously generated file name\n * associated with that key is returned, regardless of the current\n * {@link inputFileName} value.\n *\n * If the key has not been used before, the method either:\n * - returns {@link inputFileName} if it has not been used for another key, or\n * - generates the next available non-conflicting name by appending or\n * incrementing a numeric index in parentheses.\n *\n * @param inputFileName The desired base file name, including extension.\n * @param key A logical identifier whose associated file name should remain stable across calls.\n * @returns The unique file name associated with the given key.\n */\n generateUniqueFileName(inputFileName: string, key: string): string {\n const existingFileName = this._fileNamesByKey[key]\n if (existingFileName) {\n return existingFileName\n }\n if (!this.isFileNameUsed(inputFileName)) {\n // file name not used yet\n return this.assignFileName(inputFileName, key)\n }\n let generatedFileName = this.generateNextFileName(inputFileName)\n while (this.isFileNameUsed(generatedFileName)) {\n // file name already exists, generate the next one\n generatedFileName = this.generateNextFileName(generatedFileName)\n }\n return this.assignFileName(generatedFileName, key)\n }\n\n /**\n * Generates a new file name by incrementing an existing numeric suffix\n * of the form \"name (n)\" or appending \" (1)\" if no such suffix exists.\n *\n * @param inputFileName The original file name, including its extension.\n * @returns The updated file name with an incremented or newly added numeric suffix.\n */\n protected generateNextFileName(inputFileName: string): string {\n const name = FileNames.getName(inputFileName)\n const extension = FileNames.getExtension(inputFileName)\n let nameUpdated\n const matchRes = nameWithIndexRegEx.exec(name)\n if (matchRes) {\n const [, nameWithoutIndex, index] = matchRes\n const nextIndex = Number(index) + 1\n nameUpdated = `${nameWithoutIndex} (${nextIndex})`\n } else {\n nameUpdated = `${name} (1)`\n }\n return extension ? `${nameUpdated}.${extension}` : nameUpdated\n }\n\n get fileNamesByKey(): Readonly<Dictionary<string>> {\n return { ...this._fileNamesByKey }\n }\n\n get keysByFileName(): Readonly<Dictionary<string>> {\n return { ...this._keysByFileName }\n }\n}\n"]}
1
+ {"version":3,"file":"UniqueFileNamesGenerator.js","sourceRoot":"","sources":["../../../src/dataExport/UniqueFileNamesGenerator.ts"],"names":[],"mappings":"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","sourcesContent":["import { Dictionary } from '../common'\nimport { FileNames } from '../utils'\n\nconst nameWithIndexRegEx = /^(.*) \\((\\d+)\\)$/ // file name like \"example (1).txt\"\n\nexport class UniqueFileNamesGenerator {\n private _fileNamesByKey: Dictionary<string>\n private _keysByFileName: Dictionary<string>\n // file names are compared ignoring case: names differing only in case overwrite each other on Windows/macOS\n private readonly _fileNamesLowerCase: Set<string>\n // last file name generated starting from a given first candidate file name (e.g. \"image (1).jpg\" => \"image (25).jpg\"):\n // the names before it in the sequence are all used already, so the search can start from it\n private readonly _lastGeneratedFileNameByFirstCandidate: Map<string, string>\n\n constructor() {\n this._fileNamesByKey = {}\n this._keysByFileName = {}\n this._fileNamesLowerCase = new Set()\n this._lastGeneratedFileNameByFirstCandidate = new Map()\n }\n\n private isFileNameUsed(fileName: string): boolean {\n return this._fileNamesLowerCase.has(fileName.toLowerCase())\n }\n\n private assignFileName(fileName: string, key: string): string {\n this._keysByFileName[fileName] = key\n this._fileNamesByKey[key] = fileName\n this._fileNamesLowerCase.add(fileName.toLowerCase())\n return fileName\n }\n\n /**\n * Generates a unique file name for the given key.\n *\n * If the key has been used before, the previously generated file name\n * associated with that key is returned, regardless of the current\n * {@link inputFileName} value.\n *\n * If the key has not been used before, the method either:\n * - returns {@link inputFileName} if it has not been used for another key, or\n * - generates the next available non-conflicting name by appending or\n * incrementing a numeric index in parentheses.\n *\n * @param inputFileName The desired base file name, including extension.\n * @param key A logical identifier whose associated file name should remain stable across calls.\n * @returns The unique file name associated with the given key.\n */\n generateUniqueFileName(inputFileName: string, key: string): string {\n const existingFileName = this._fileNamesByKey[key]\n if (existingFileName) {\n return existingFileName\n }\n if (!this.isFileNameUsed(inputFileName)) {\n // file name not used yet\n return this.assignFileName(inputFileName, key)\n }\n const firstCandidate = this.generateNextFileName(inputFileName)\n let generatedFileName = this._lastGeneratedFileNameByFirstCandidate.get(firstCandidate) ?? firstCandidate\n while (this.isFileNameUsed(generatedFileName)) {\n // file name already exists, generate the next one\n generatedFileName = this.generateNextFileName(generatedFileName)\n }\n this._lastGeneratedFileNameByFirstCandidate.set(firstCandidate, generatedFileName)\n return this.assignFileName(generatedFileName, key)\n }\n\n /**\n * Generates a new file name by incrementing an existing numeric suffix\n * of the form \"name (n)\" or appending \" (1)\" if no such suffix exists.\n *\n * @param inputFileName The original file name, including its extension.\n * @returns The updated file name with an incremented or newly added numeric suffix.\n */\n protected generateNextFileName(inputFileName: string): string {\n const name = FileNames.getName(inputFileName)\n const extension = FileNames.getExtension(inputFileName)\n let nameUpdated\n const matchRes = nameWithIndexRegEx.exec(name)\n if (matchRes) {\n const [, nameWithoutIndex, index] = matchRes\n const nextIndex = Number(index) + 1\n nameUpdated = `${nameWithoutIndex} (${nextIndex})`\n } else {\n nameUpdated = `${name} (1)`\n }\n return extension ? `${nameUpdated}.${extension}` : nameUpdated\n }\n\n get fileNamesByKey(): Readonly<Dictionary<string>> {\n return { ...this._fileNamesByKey }\n }\n\n get keysByFileName(): Readonly<Dictionary<string>> {\n return { ...this._keysByFileName }\n }\n}\n"]}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "OpenForis",
5
5
  "email": "openforis.arena@gmail.com"
6
6
  },
7
- "version": "2.18.8",
7
+ "version": "2.18.10",
8
8
  "description": "Arena core module",
9
9
  "repository": {
10
10
  "type": "git",