@iebh/tera-fy 1.0.16 → 1.0.17

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/hints.md ADDED
@@ -0,0 +1,26 @@
1
+ TERA-fy Hints
2
+ =============
3
+ Hints are a single string attached to file uploads to provide some context as to where it fits within the overall process. They are usually past-tense single-words indicating the output from each stage.
4
+
5
+ When attached to files they indicate that the generated file is an output from a stage. e.g. if a file is noted with the hint 'deduped' it indicates it is the product of a de-duplication stage.
6
+
7
+ They are used in multiple places:
8
+
9
+ * Calls to most project library handling functions - `selectProjectLibrary()`, `setProjectLibrary()`
10
+ * Within the UI to indicate what library is used during what process
11
+
12
+
13
+ Hint Reference
14
+ --------------
15
+ The following is a non-definitive list of hints that can be associated with files. In all cases the hint should be a lower-case single, past-tense word. Upper-case indicates a variable.
16
+
17
+ This list should also be mirrored in the main tera-tools.com/api/tools.json meta-list.
18
+
19
+
20
+ | Hint | Description |
21
+ |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
22
+ | `search` | The product of a search design stage |
23
+ | `deduped` | One or more files, that are the product of a deduplication stage |
24
+ | `screened-X` | One or more files from the screening stage, generally there will be one file per reviewer where `X` indicates the reviewer number starting with the number 1 |
25
+ | `disputed` | Generally a single file which is the product of a resolved dispute stage which merges in multiple `screened-X` files |
26
+ | `snowballed` | A file which has been through the snowballing stage |
@@ -87,10 +87,10 @@ export default class TeraFy {
87
87
  // For bindProjectState() - See individual plugins
88
88
 
89
89
  // Project files
90
- 'getProjectFiles',
90
+ 'selectProjectFile', 'getProjectFiles',
91
91
 
92
92
  // Project Libraries
93
- 'getProjectLibrary', 'setProjectLibrary',
93
+ 'selectProjectLibrary', 'parseProjectLibrary', 'setProjectLibrary',
94
94
 
95
95
  // UI
96
96
  'uiAlert',
@@ -798,6 +798,35 @@ export default class TeraFy {
798
798
  * @property {String} mime The associated mime type for the file
799
799
  */
800
800
 
801
+ /**
802
+ * Data structure for a file filter
803
+ * @name FileFilters
804
+ *
805
+ * @property {Boolean} [library=false] Restrict to library files only
806
+ * @property {String} [filename] CSV of @momsfriendlydevco/match expressions to filter the filename by (filenames are the basename sans extension)
807
+ * @property {String} [basename] CSV of @momsfriendlydevco/match expressions to filter the basename by
808
+ * @property {String} [ext] CSV of @momsfriendlydevco/match expressions to filter the file extension by
809
+ */
810
+
811
+
812
+ /**
813
+ * Prompt the user to select a library to operate on
814
+ *
815
+ * @function selectProjectFile
816
+ * @param {Object} [options] Additional options to mutate behaviour
817
+ * @param {String} [options.title="Select a file"] The title of the dialog to display
818
+ * @param {String|Array<String>} [options.hint] Hints to identify the file to select in array order of preference
819
+ * @param {FileFilters} [options.filters] Optional file filters
820
+ * @param {Boolean} [options.allowUpload=true] Allow uploading new files
821
+ * @param {Boolean} [options.allowRefresh=true] Allow the user to manually refresh the file list
822
+ * @param {Boolean} [options.allowDownloadZip=true] Allow the user to download a Zip of all files
823
+ * @param {Boolean} [options.allowCancel=true] Allow cancelling the operation. Will throw `'CANCEL'` as the promise rejection if acationed
824
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
825
+ * @param {FileFilters} [options.filter] Optional file filters
826
+ *
827
+ * @returns {Promise<ProjectFile>} The eventually selected file
828
+ */
829
+
801
830
 
802
831
  /**
803
832
  * Fetch the files associated with a given project
@@ -807,25 +836,60 @@ export default class TeraFy {
807
836
  * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
808
837
  * @param {Boolean} [options.meta=true] Pull meta information for each file entity
809
838
  *
810
- * @returns {Promise<ProjectFile>} A collection of project files for the given project
839
+ * @returns {Promise<Array<ProjectFile>>} A collection of project files for the given project
811
840
  */
812
841
 
813
842
 
814
843
  /**
815
- * Fetch the active projects citation library
844
+ * Prompt the user to select a library to operate on and return a array of references in a given format
816
845
  *
817
- * @function getProjectLibrary
818
- * @param {String} [path] Optional file path to use, if omitted the contents of `options` are used to guess at a suitable file
846
+ * @function selectProjectLibrary
847
+ * @param {Object} [options] Additional options to mutate behaviour
848
+ * @param {String} [options.title="Select a citation library"] The title of the dialog to display
849
+ * @param {String|Array<String>} [options.hint] Hints to identify the library to select in array order of preference. Generally corresponds to the previous stage - e.g. 'deduped', 'review1', 'review2', 'dedisputed'
850
+ * @param {Boolean} [options.allowUpload=true] Allow uploading new files
851
+ * @param {Boolean} [options.allowRefresh=true] Allow the user to manually refresh the file list
852
+ * @param {Boolean} [options.allowDownloadZip=true] Allow the user to download a Zip of all files
853
+ * @param {Boolean} [options.allowCancel=true] Allow cancelling the operation. Will throw `'CANCEL'` as the promise rejection if acationed
854
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
855
+ * @param {FileFilters} [options.filters] Optional file filters, defaults to citation library selection only
856
+ * @param {*} [options.*] Additional options - see `parseProjectLibrary()`
857
+ *
858
+ * @returns {Promise<Array<Ref>>} A collection of references from the selected file
859
+ */
860
+
861
+
862
+ /**
863
+ * Convert a project file into a library of citations
864
+ *
865
+ * @function parseProjectLibrary
866
+ * @param {String} path File path to read, if omitted the contents of `options` are used to guess at a suitable file
819
867
  *
820
868
  * @param {Object} [options] Additional options to mutate behaviour
821
869
  * @param {String} [options.format='json'] Format for the file. ENUM: 'pojo' (return a parsed JS collection), 'blob' (raw JS Blob object), 'file' (named JS File object)
822
870
  * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
823
- * @param {Boolean} [options.multiple=false] Allow selection of multiple libraries
824
871
  * @param {Function} [options.filter] Optional async file filter, called each time as `(File:ProjectFile)`
825
872
  * @param {Function} [options.find] Optional async final stage file filter to reduce all candidates down to one subject file
826
- * @param {String|Array<String>} [options.hint] Hints to identify the library to select in array order of preference. Generally corresponds to the previous stage - e.g. 'deduped', 'review1', 'review2', 'dedisputed'
827
873
  *
828
- * @returns {Promise<Array<ProjectFile>>} Collection of references for the selected library matching the given hint + filter, this could be a zero length array
874
+ * @returns {Promise<Array<Ref>>|Promise<*>} A collection of references (default bevahiour) or a whatever format was requested
875
+ */
876
+
877
+
878
+ /**
879
+ * Save back a citation library from some input
880
+ *
881
+ * @function setProjectLibrary
882
+ * @param {String} [path] File path to save back to
883
+ * @param {Array<RefLibRef>} Collection of references for the selected library
884
+ *
885
+ * @param {Object} [options] Additional options to mutate behaviour
886
+ * @param {String} [options.format='json'] Input format used. ENUM: 'pojo' (return a parsed JS collection), 'blob' (raw JS Blob object), 'file' (named JS File object)
887
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
888
+ * @param {String} [options.hint] Hint to store against the library. Generally corresponds to the current operation being performed - e.g. 'deduped'
889
+ * @param {Boolean} [options.overwrite=true] Allow existing file upsert
890
+ * @param {Object} [options.meta] Optional meta data to merge into the file data
891
+ *
892
+ * @returns {Promise} A promise which resolves when the save operation has completed
829
893
  */
830
894
 
831
895
 
@@ -495,7 +495,7 @@ export default class TeraFyServer {
495
495
  *
496
496
  * @param {Object} [options] Additional options to mutate behaviour
497
497
  * @param {String} [options.title="Select a project to work with"] The title of the dialog to display
498
- * @param {Boolean} [options.allowCancel=true] Advertise cancelling the operation, the dialog can still be cancelled by closing it
498
+ * @param {Boolean} [options.allowCancel=true] Allow cancelling the operation, will throw `'CANCEL'` if actioned
499
499
  * @param {Boolean} [options.setActive=false] Also set the project as active when selected
500
500
  *
501
501
  * @returns {Promise<Project>} The active project
@@ -526,7 +526,7 @@ export default class TeraFyServer {
526
526
 
527
527
  // }}}
528
528
 
529
- // Project State - getProjectState(), setProjectState(), saveProjectState(), replaceProjectState(), applyProjectStatePatch {{{
529
+ // Project State - getProjectState(), setProjectState(), saveProjectState(), replaceProjectState(), applyProjectStatePatch() {{{
530
530
 
531
531
  /**
532
532
  * Return the current, full snapshot state of the active project
@@ -654,7 +654,7 @@ export default class TeraFyServer {
654
654
  }
655
655
  // }}}
656
656
 
657
- // Project files - getProjectFiles() {{{
657
+ // Project files - selectProjectFile(), getProjectFiles() {{{
658
658
 
659
659
  /**
660
660
  * Data structure for a project file
@@ -674,6 +674,72 @@ export default class TeraFyServer {
674
674
  * @property {String} mime The associated mime type for the file
675
675
  */
676
676
 
677
+ /**
678
+ * Data structure for a file filter
679
+ * @class FileFilters
680
+ *
681
+ * @property {Boolean} [library=false] Restrict to library files only
682
+ * @property {String} [filename] CSV of @momsfriendlydevco/match expressions to filter the filename by (filenames are the basename sans extension)
683
+ * @property {String} [basename] CSV of @momsfriendlydevco/match expressions to filter the basename by
684
+ * @property {String} [ext] CSV of @momsfriendlydevco/match expressions to filter the file extension by
685
+ */
686
+
687
+ /**
688
+ * Prompt the user to select a library to operate on
689
+ *
690
+ * @param {Object} [options] Additional options to mutate behaviour
691
+ * @param {String} [options.title="Select a file"] The title of the dialog to display
692
+ * @param {String|Array<String>} [options.hint] Hints to identify the file to select in array order of preference
693
+ * @param {FileFilters} [options.filters] Optional file filters
694
+ * @param {Boolean} [options.allowUpload=true] Allow uploading new files
695
+ * @param {Boolean} [options.allowRefresh=true] Allow the user to manually refresh the file list
696
+ * @param {Boolean} [options.allowDownloadZip=true] Allow the user to download a Zip of all files
697
+ * @param {Boolean} [options.allowCancel=true] Allow cancelling the operation. Will throw `'CANCEL'` as the promise rejection if acationed
698
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
699
+ * @param {FileFilters} [options.filter] Optional file filters
700
+ *
701
+ * @returns {Promise<ProjectFile>} The eventually selected file
702
+ */
703
+ selectProjectFile(options) {
704
+ let settings = {
705
+ title: 'Select a file',
706
+ hint: null,
707
+ filters: {},
708
+ allowUpload: true,
709
+ allowRefresh: true,
710
+ allowDownloadZip: true,
711
+ allowCancel: true,
712
+ autoRequire: true,
713
+ ...options,
714
+ };
715
+
716
+ return app.service('$projects').promise()
717
+ .then(()=> settings.autoRequire && this.requireProject())
718
+ .then(files => this.requestFocus(()=>
719
+ app.service('$prompt').dialog({
720
+ title: settings.title,
721
+ component: 'filesSelect',
722
+ componentProps: {
723
+ hint: settings.hint,
724
+ allowNavigate: false,
725
+ allowUpload: settings.allowUpload,
726
+ allowRefresh: settings.allowRefresh,
727
+ allowDownloadZip: settings.allowDownloadZip,
728
+ allowVerbs: false,
729
+ cardStyle: false,
730
+ filters: settings.filters,
731
+ },
732
+ componentEvents: {
733
+ fileSelect(file) {
734
+ app.service('$prompt').close(true, file);
735
+ },
736
+ },
737
+ modalDialogClass: 'modal-dialog-lg',
738
+ buttons: settings.allowCancel && ['cancel'],
739
+ })
740
+ ))
741
+ }
742
+
677
743
 
678
744
  /**
679
745
  * Fetch the files associated with a given project
@@ -682,7 +748,7 @@ export default class TeraFyServer {
682
748
  * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
683
749
  * @param {Boolean} [options.meta=true] Pull meta information for each file entity
684
750
  *
685
- * @returns {Promise<ProjectFile>} A collection of project files for the given project
751
+ * @returns {Promise<Array<ProjectFile>>} A collection of project files for the given project
686
752
  */
687
753
  getProjectFiles(options) {
688
754
  let settings = {
@@ -700,61 +766,81 @@ export default class TeraFyServer {
700
766
  }
701
767
  // }}}
702
768
 
703
- // Project Libraries - getProjectLibrary(), setProjectLibrary() {{{
769
+ // Project Libraries - selectProjectLibrary(), parseProjectLibrary(), setProjectLibrary() {{{
770
+
771
+ /**
772
+ * Prompt the user to select a library to operate on and return a array of references in a given format
773
+ *
774
+ * @param {Object} [options] Additional options to mutate behaviour
775
+ * @param {String} [options.title="Select a citation library"] The title of the dialog to display
776
+ * @param {String|Array<String>} [options.hint] Hints to identify the library to select in array order of preference. Generally corresponds to the previous stage - e.g. 'deduped', 'review1', 'review2', 'dedisputed'
777
+ * @param {Boolean} [options.allowUpload=true] Allow uploading new files
778
+ * @param {Boolean} [options.allowRefresh=true] Allow the user to manually refresh the file list
779
+ * @param {Boolean} [options.allowDownloadZip=true] Allow the user to download a Zip of all files
780
+ * @param {Boolean} [options.allowCancel=true] Allow cancelling the operation. Will throw `'CANCEL'` as the promise rejection if acationed
781
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
782
+ * @param {FileFilters} [options.filters] Optional file filters, defaults to citation library selection only
783
+ * @param {*} [options.*] Additional options - see `parseProjectLibrary()`
784
+ *
785
+ * @returns {Promise<Array<Ref>>} A collection of references from the selected file
786
+ */
787
+ selectProjectLibrary(options) {
788
+ let settings = {
789
+ title: 'Select a citation library',
790
+ hint: null,
791
+ allowUpload: true,
792
+ allowRefresh: true,
793
+ allowDownloadZip: true,
794
+ allowCancel: true,
795
+ autoRequire: true,
796
+ filters: {
797
+ library: true,
798
+ ...options?.filter,
799
+ },
800
+ ...options,
801
+ };
802
+
803
+ return app.service('$projects').promise()
804
+ .then(()=> this.selectProjectFile(settings))
805
+ .then(selectedFile => this.parseProjectLibrary(selectedFile.path, settings))
806
+ }
807
+
704
808
 
705
809
  /**
706
- * Fetch the active projects citation library
810
+ * Convert a project file into a library of citations
707
811
  *
708
- * @param {String} [path] Optional file path to use, if omitted the contents of `options` are used to guess at a suitable file
812
+ * @param {String} path File path to read, if omitted the contents of `options` are used to guess at a suitable file
709
813
  *
710
814
  * @param {Object} [options] Additional options to mutate behaviour
711
815
  * @param {String} [options.format='json'] Format for the file. ENUM: 'pojo' (return a parsed JS collection), 'blob' (raw JS Blob object), 'file' (named JS File object)
712
816
  * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
713
- * @param {Boolean} [options.multiple=false] Allow selection of multiple libraries
714
817
  * @param {Function} [options.filter] Optional async file filter, called each time as `(File:ProjectFile)`
715
818
  * @param {Function} [options.find] Optional async final stage file filter to reduce all candidates down to one subject file
716
- * @param {String|Array<String>} [options.hint] Hints to identify the library to select in array order of preference. Generally corresponds to the previous stage - e.g. 'deduped', 'review1', 'review2', 'dedisputed'
717
819
  *
718
- * @returns {Promise<Array<ProjectFile>>} Collection of references for the selected library matching the given hint + filter, this could be a zero length array
820
+ * @returns {Promise<Array<Ref>>|Promise<*>} A collection of references (default bevahiour) or a whatever format was requested
719
821
  */
720
- getProjectLibrary(path, options) {
822
+ parseProjectLibrary(path, options) {
721
823
  let settings = {
722
- path,
723
824
  format: 'pojo',
724
825
  autoRequire: true,
725
- multiple: false,
726
- hint: null,
727
826
  filter: file => true,
728
827
  find: files => files.at(0),
729
- ...(typeof path == 'string' ? {path, ...options} : path),
828
+ ...options,
730
829
  };
731
830
 
732
831
  return Promise.resolve()
733
- .then(()=> {
734
- if (settings.path) { // Already have a file name picked
735
- if (!settings.path.startsWith('/')) throw new Error('All file names must start with a forward slash');
736
- return settings.path;
737
- } else { // Try to guess the file from the options structure
738
- return this.getProjectFiles({
739
- autoRequire: settings.autoRequire,
740
- })
741
- .then(files => files.filter(file =>
742
- settings.filter(file)
743
- ))
744
- .then(files => settings.find(files))
745
- .then(file => file.path)
746
- }
747
- })
748
- .then(filePath => app.service('$supabase').fileGet(filePath, {
832
+ .then(()=> settings.autoRequire && this.requireProject())
833
+ .then(()=> app.service('$supabase').fileGet(path, {
749
834
  toast: false,
750
835
  }))
751
836
  .then(blob => {
752
837
  switch (settings.format) {
838
+ // NOTE: Any updates to the format list should also extend setProjectLibrary()
753
839
  case 'pojo':
754
840
  return Reflib.uploadFile({
755
841
  file: new File(
756
842
  [blob],
757
- app.service('$supabase')._parsePath(settings.path).basename,
843
+ app.service('$supabase')._parsePath(path).basename,
758
844
  ),
759
845
  });
760
846
  case 'blob':
@@ -762,7 +848,7 @@ export default class TeraFyServer {
762
848
  case 'file':
763
849
  return new File(
764
850
  [blob],
765
- app.service('$supabase')._parsePath(settings.path).basename,
851
+ app.service('$supabase')._parsePath(path).basename,
766
852
  );
767
853
  default:
768
854
  throw new Error(`Unsupported library format "${settings.format}"`);
@@ -772,26 +858,59 @@ export default class TeraFyServer {
772
858
 
773
859
 
774
860
  /**
775
- * Save back a projects citation library
861
+ * Save back a citation library from some input
776
862
  *
863
+ * @param {String} [path] File path to save back to
777
864
  * @param {Array<RefLibRef>} Collection of references for the selected library
778
865
  *
779
866
  * @param {Object} [options] Additional options to mutate behaviour
867
+ * @param {String} [options.format='json'] Input format used. ENUM: 'pojo' (return a parsed JS collection), 'blob' (raw JS Blob object), 'file' (named JS File object)
780
868
  * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
781
869
  * @param {String} [options.hint] Hint to store against the library. Generally corresponds to the current operation being performed - e.g. 'deduped'
870
+ * @param {Boolean} [options.overwrite=true] Allow existing file upsert
871
+ * @param {Object} [options.meta] Optional meta data to merge into the file data
782
872
  *
783
873
  * @returns {Promise} A promise which resolves when the save operation has completed
784
874
  */
785
- setProjectLibrary(refs, options) {
875
+ setProjectLibrary(path, refs, options) {
786
876
  let settings = {
787
877
  autoRequire: true,
788
878
  hint: null,
879
+ overwrite: true,
880
+ meta: null,
789
881
  ...options,
790
882
  };
791
883
 
792
884
  return Promise.resolve()
793
885
  .then(()=> settings.autoRequire && this.requireProject())
794
- // FIXME: Stub
886
+ .then(()=> {
887
+ switch (settings.format) {
888
+ // NOTE: Any updates to the format list should also extend parseProjectLibrary()
889
+ case 'pojo': // Use as is
890
+ if (!Array.isArray(refs)) throw new Error('setProjectLibrary() with format=pojo requires an array of references');
891
+ return refs;
892
+ case 'blob':
893
+ case 'file':
894
+ return refs; // Following functions should pass thru these formats anyway
895
+ default:
896
+ throw new Error(`Unsupported library format "${settings.format}"`);
897
+ }
898
+ })
899
+ .then(refs =>
900
+ refs instanceof Blob ? new File([refs], app.service('$supabase')._parsePath(path).basename)
901
+ : refs instanceof File ? refs
902
+ : Reflib.downloadFile(refs, { // Get Reflib to encode for us
903
+ filename: path,
904
+ promptDownload: false, // Just return the fileBlob we hand to Supabase
905
+ })
906
+ )
907
+ .then(fileBlob => this.$supabase.fileUpload(path, {
908
+ file: fileBlob,
909
+ mode: 'encoded',
910
+ overwrite: true,
911
+ meta: settings.meta,
912
+ transcoders: false, // We can skip transcoders as we are supplying the meta information above anyway
913
+ }))
795
914
  }
796
915
 
797
916
  // }}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iebh/tera-fy",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "TERA website worker",
5
5
  "scripts": {
6
6
  "dev": "esbuild --platform=browser --format=esm --bundle lib/terafy.client.js --outfile=dist/terafy.js --minify --sourcemap --serve --servedir=.",