diff --git a/README.md b/README.md index 8968f1c3a3..5397b5837f 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ mvn -pl '!clamav-java' verify ### Running JavaScript tests ``` # JavaScript Tests -npm --prefix static/js/admin/vue-permissions-editor run test +npm --prefix static/js/admin/vue-cdr-admin run test npm --prefix static/js/vue-cdr-access run test ``` diff --git a/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/AccessPrincipalConstants.java b/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/AccessPrincipalConstants.java index 285d9714be..f73ccc9728 100644 --- a/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/AccessPrincipalConstants.java +++ b/auth-api/src/main/java/edu/unc/lib/boxc/auth/api/AccessPrincipalConstants.java @@ -23,6 +23,7 @@ public class AccessPrincipalConstants { public final static String PATRON_NAMESPACE = "unc:patron:"; public final static String IP_PRINC_NAMESPACE = PATRON_NAMESPACE + "ipp:"; public final static String ADMIN_ACCESS_PRINC = "admin_access"; + public final static String ON_CAMPUS_PRINC = "unc:patron:ipp:on_campus"; public final static Pattern PATRON_PRINC_PATTERN = Pattern.compile("(" + PUBLIC_PRINC diff --git a/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkRequest.java b/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkRequest.java index ef17fc35ac..2de499b396 100644 --- a/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkRequest.java +++ b/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkRequest.java @@ -1,17 +1,17 @@ package edu.unc.lib.boxc.operations.impl.download; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; import edu.unc.lib.boxc.auth.api.models.AgentPrincipals; import edu.unc.lib.boxc.auth.fcrepo.models.AgentPrincipalsImpl; public class DownloadBulkRequest { private String workPidString; - @JsonDeserialize(as = AgentPrincipalsImpl.class) - private AgentPrincipals agent; + private AccessGroupSet principals; - public DownloadBulkRequest(String workPidString, AgentPrincipals agent) { + public DownloadBulkRequest(String workPidString, AccessGroupSet principals) { this.workPidString = workPidString; - this.agent = agent; + this.principals = principals; } public String getWorkPidString() { @@ -22,11 +22,11 @@ public void setWorkPidString(String workPidString) { this.workPidString = workPidString; } - public AgentPrincipals getAgent() { - return agent; + public AccessGroupSet getPrincipals() { + return principals; } - public void setAgent(AgentPrincipals agent) { - this.agent = agent; + public void setPrincipals(AccessGroupSet principals) { + this.principals = principals; } } diff --git a/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkService.java b/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkService.java index df47e33b32..6ca2491eb1 100644 --- a/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkService.java +++ b/operations/src/main/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkService.java @@ -4,7 +4,6 @@ import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; import edu.unc.lib.boxc.auth.api.services.AccessControlService; import edu.unc.lib.boxc.fcrepo.exceptions.ServiceException; -import edu.unc.lib.boxc.model.api.exceptions.NotFoundException; import edu.unc.lib.boxc.model.api.objects.ContentObject; import edu.unc.lib.boxc.model.api.objects.FileObject; import edu.unc.lib.boxc.model.api.objects.RepositoryObjectLoader; @@ -29,11 +28,12 @@ public class DownloadBulkService { private AccessControlService aclService; private RepositoryObjectLoader repoObjLoader; private Path basePath; + private int fileLimit; public Path downloadBulk(DownloadBulkRequest request) { var pidString = request.getWorkPidString(); var workPid = PIDs.get(pidString); - var agentPrincipals = request.getAgent().getPrincipals(); + var agentPrincipals = request.getPrincipals(); aclService.assertHasAccess( "User does not have permissions to view the Work for download", workPid, agentPrincipals, Permission.viewOriginal); @@ -61,7 +61,14 @@ private void zipFiles(WorkObject workObject, AccessGroupSet agentPrincipals, Pat } Map duplicates = new HashMap<>(); + int count = 0; for (ContentObject memberObject : memberObjects ) { + if (count == fileLimit) { + break; + } + if (!(memberObject instanceof FileObject)) { + continue; + } var fileObject = (FileObject) memberObject; if (aclService.hasAccess(memberObject.getPid(), agentPrincipals, Permission.viewOriginal)) { var binObj = fileObject.getOriginalFile(); @@ -82,6 +89,7 @@ private void zipFiles(WorkObject workObject, AccessGroupSet agentPrincipals, Pat IOUtils.copy(binaryStream, zipOut); } + count++; } } } @@ -111,4 +119,8 @@ public void setRepoObjLoader(RepositoryObjectLoader repoObjLoader) { public void setBasePath(Path basePath) { this.basePath = basePath; } + + public void setFileLimit(int fileLimit) { + this.fileLimit = fileLimit; + } } diff --git a/operations/src/test/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkServiceTest.java b/operations/src/test/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkServiceTest.java index 23b6a024a0..30a98cf5b4 100644 --- a/operations/src/test/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkServiceTest.java +++ b/operations/src/test/java/edu/unc/lib/boxc/operations/impl/download/DownloadBulkServiceTest.java @@ -5,12 +5,12 @@ import edu.unc.lib.boxc.auth.api.models.AccessGroupSet; import edu.unc.lib.boxc.auth.api.models.AgentPrincipals; import edu.unc.lib.boxc.auth.api.services.AccessControlService; -import edu.unc.lib.boxc.model.api.exceptions.NotFoundException; import edu.unc.lib.boxc.model.api.exceptions.ObjectTypeMismatchException; import edu.unc.lib.boxc.model.api.ids.PID; import edu.unc.lib.boxc.model.api.objects.BinaryObject; import edu.unc.lib.boxc.model.api.objects.FileObject; import edu.unc.lib.boxc.model.api.objects.RepositoryObjectLoader; +import edu.unc.lib.boxc.model.api.objects.Tombstone; import edu.unc.lib.boxc.model.api.objects.WorkObject; import edu.unc.lib.boxc.model.fcrepo.ids.PIDs; import org.apache.commons.io.IOUtils; @@ -64,6 +64,8 @@ public class DownloadBulkServiceTest { private WorkObject parentWork; @Mock private FileObject fileObject1, fileObject2; + @Mock + private Tombstone tombstone; @TempDir public Path zipStorageBasePath; @@ -75,10 +77,11 @@ public void init() throws Exception { service.setAclService(aclService); service.setRepoObjLoader(repoObjLoader); service.setBasePath(zipStorageBasePath); + service.setFileLimit(5); parentPid = PIDs.get(PARENT_UUID); fileObject1Pid = PIDs.get(CHILD1_UUID); fileObject2Pid = PIDs.get(CHILD2_UUID); - request = new DownloadBulkRequest(PARENT_UUID, mockAgent); + request = new DownloadBulkRequest(PARENT_UUID, mockAccessSet); when(mockAgent.getUsername()).thenReturn("user"); when(mockAgent.getPrincipals()).thenReturn(mockAccessSet); @@ -154,6 +157,31 @@ public void noOriginalFilesTest() throws IOException { assertZipFiles(List.of(), List.of()); } + @Test + public void tombstoneTest() throws IOException { + when(repoObjLoader.getWorkObject(eq(parentPid))).thenReturn(parentWork); + when(parentWork.getMembers()).thenReturn(List.of(tombstone)); + service.downloadBulk(request); + // the zip file should be empty + assertZipFiles(List.of(), List.of()); + } + + @Test + public void fileLimitTest() throws IOException { + when(repoObjLoader.getWorkObject(any(PID.class))).thenReturn(parentWork); + when(parentWork.getMembers()).thenReturn(List.of(fileObject1, fileObject2)); + makeBinaryObject(fileObject1, FILENAME1); + makeBinaryObject(fileObject2, FILENAME2); + when(aclService.hasAccess(eq(fileObject1Pid), any(), + eq(Permission.viewOriginal))).thenReturn(true); + when(aclService.hasAccess(eq(fileObject2Pid), any(), + eq(Permission.viewOriginal))).thenReturn(true); + service.setFileLimit(1); + service.downloadBulk(request); + // the zip file should have one entry + assertZipFiles(List.of(FILENAME1), List.of("flower")); + } + @Test public void successTest() throws IOException { when(repoObjLoader.getWorkObject(eq(parentPid))).thenReturn(parentWork); diff --git a/static/css/cdrui_styles.css b/static/css/cdrui_styles.css index 45ea8cfe65..8498759fdc 100644 --- a/static/css/cdrui_styles.css +++ b/static/css/cdrui_styles.css @@ -52,7 +52,6 @@ .thumbnail { display: block; position:relative; - float: left; margin: 0 0 20px 0; text-align: center; object-fit: contain; diff --git a/static/css/sass/cdr_ui_styles.scss b/static/css/sass/cdr_ui_styles.scss index f506e79851..61039ad690 100644 --- a/static/css/sass/cdr_ui_styles.scss +++ b/static/css/sass/cdr_ui_styles.scss @@ -125,7 +125,7 @@ $box-shadow: inset 3px 3px 10px -1px $box-shadow-color, 0px 16px 22px -15px $box * **/ .search-query-text { - margin: 40px 20px 40px 25px; + margin: 40px 20px 20px 25px; font-size: 24px; h2 { @@ -147,13 +147,6 @@ form.search { } } -.browse-search { - .button.is-focused, - .button:focus { - color: #363636; - } -} - a.search-result-num { background-color: $container-blue; color: white !important; @@ -420,11 +413,7 @@ p.no-search-results { #child-files_filter { margin-bottom: 25px; - - label { - float: right; - width: 250px; - } + margin-top: -50px; } .input::placeholder, .input { @@ -439,14 +428,19 @@ img.data-thumb { max-width: 60px; } -.child-records { - font-size: 16px; - margin: 30px 50px 25px 50px; +.file-list-header { + margin: 30px 0 5px; h3 { font-size: 16px; - margin-bottom: 30px; + margin: 0; + padding: 0; } +} + +.child-records { + font-size: 16px; + overflow: visible; .fa.default-img-icon { font-size: 32px; @@ -459,14 +453,6 @@ img.data-thumb { } } - .is-icon { - background-color: $container-blue; - border: 1px solid $container-blue; - border-radius: 5px; - color: white; - padding: 7px; - } - .no-download { background-color: #B8B8B8; border-color: #B8B8B8; @@ -614,7 +600,6 @@ img.data-thumb { .full_record_top { border-bottom: 1px solid $light-gray; - padding-bottom: 25px; .collinfo { width: 100%; @@ -719,7 +704,7 @@ table.dataTable { .actionlink { a.action { font-size: 16px; - padding: 15px 15px 30px 15px; + padding: 15px; } } } @@ -729,7 +714,6 @@ table.dataTable { .restricted-access { border: 1px solid; border-radius: 5px; - padding: 15px; .button { text-align: left; @@ -827,11 +811,6 @@ table.dataTable { margin-bottom: 10px; } -.button.is-focused, -.button:focus { - color: white; -} - /* MODs display for work and file pages, plus modalMetadata.vue component */ #mods_data_display { margin: auto; @@ -905,6 +884,21 @@ table.dataTable { display: inline-flex; } +.button.is-link.is-hovered, +.button.is-link:hover { + background-color: #276cda; + border-color: transparent; + color: #fff; +} + +.navbar-link.is-active, +.navbar-link:hover, +a.navbar-item.is-active, +a.navbar-item:hover { + background-color: #fafafa; + color: #3273dc; +} + /*** End of Bulma overrides ***/ /* Following Bulma breakpoints */ @@ -1042,6 +1036,10 @@ table.dataTable { } } + #child-files_filter { + margin-top: -25px; + } + .dataTables_wrapper { .dataTables_filter { float: left !important; @@ -1143,7 +1141,7 @@ iframe { .clover-viewer { margin: auto; - width: 90%; + width: 95%; } #information-toggle { diff --git a/static/js/vue-cdr-access/index.html b/static/js/vue-cdr-access/index.html index 6cdd5eeac7..85d9821af3 100644 --- a/static/js/vue-cdr-access/index.html +++ b/static/js/vue-cdr-access/index.html @@ -2,7 +2,7 @@ - + diff --git a/static/js/vue-cdr-access/package.json b/static/js/vue-cdr-access/package.json index 086b4e0304..9de101aadf 100644 --- a/static/js/vue-cdr-access/package.json +++ b/static/js/vue-cdr-access/package.json @@ -1,56 +1,59 @@ { - "name": "vue-cdr-access", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "vite", - "serve": "vite preview", - "build": "vite build", - "test": "jest" - }, - "dependencies": { - "@samvera/clover-iiif": "^2.10.2", - "@vueuse/head": "^2.0.0", - "axios": "^1.7.7", - "canvas": "^2.11.2", - "datatables.net": "^2.1.8", - "datatables.net-bm": "^2.1.8", - "datatables.net-buttons-bm": "^3.1.2", - "datatables.net-vue3": "^3.0.2", - "date-fns": "^2.30.0", - "date-fns-tz": "^2.0.0", - "lodash.chunk": "^4.2.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.isempty": "^4.4.0", - "lodash.isequal": "^4.5.0", - "lodash.range": "^3.2.0", - "lodash.sortby": "^4.7.0", - "nouislider": "^15.8.1", - "pinia": "^2.2.4", - "veaury": "^2.5.1", - "vue": "^3.5.12", - "vue-i18n": "^10.0.4", - "vue-router": "^4.4.5" - }, - "devDependencies": { - "@babel/plugin-transform-runtime": "^7.25.7", - "@babel/preset-env": "^7.25.8", - "@intlify/vue-i18n-loader": "^5.0.1", - "@pinia/testing": "0.1.6", - "@testing-library/jest-dom": "^6.5.0", - "@unhead/addons": "^1.8.9", - "@vitejs/plugin-vue": "^4.6.2", - "@vue/compiler-sfc": "^3.5.12", - "@vue/test-utils": "2.4.6", - "@vue/vue3-jest": "^29.2.6", - "babel-jest": "^29.7.0", - "caniuse-lite": "^1.0.30001668", - "jest-environment-jsdom": "^29.7.0", - "jest-localstorage-mock": "^2.4.26", - "moxios": "^0.4.0", - "pretty": "^2.0.0", - "sass": "^1.69.7", - "vite": "4.5.5" - } -} + "name": "vue-cdr-access", + "version": "0.1.0", + "private": true, + "scripts": + { + "dev": "vite", + "serve": "vite preview", + "build": "vite build", + "test": "jest" + }, + "dependencies": + { + "@samvera/clover-iiif": "^2.10.2", + "@vueuse/head": "^2.0.0", + "axios": "^1.7.7", + "canvas": "^2.11.2", + "datatables.net": "^2.1.8", + "datatables.net-bm": "^2.1.8", + "datatables.net-buttons-bm": "^3.1.2", + "datatables.net-vue3": "^3.0.2", + "date-fns": "^2.30.0", + "date-fns-tz": "^2.0.0", + "lodash.chunk": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.isempty": "^4.4.0", + "lodash.isequal": "^4.5.0", + "lodash.range": "^3.2.0", + "lodash.sortby": "^4.7.0", + "nouislider": "^15.8.1", + "pinia": "^2.2.4", + "veaury": "^2.5.1", + "vue": "^3.5.12", + "vue-i18n": "^10.0.4", + "vue-router": "^4.4.5" + }, + "devDependencies": + { + "@babel/plugin-transform-runtime": "^7.25.7", + "@babel/preset-env": "^7.25.8", + "@intlify/vue-i18n-loader": "^5.0.1", + "@pinia/testing": "0.1.6", + "@testing-library/jest-dom": "^6.5.0", + "@unhead/addons": "^1.8.9", + "@vitejs/plugin-vue": "^4.6.2", + "@vue/compiler-sfc": "^3.5.12", + "@vue/test-utils": "2.4.6", + "@vue/vue3-jest": "^29.2.6", + "babel-jest": "^29.7.0", + "caniuse-lite": "^1.0.30001668", + "jest-environment-jsdom": "^29.7.0", + "jest-localstorage-mock": "^2.4.26", + "moxios": "^0.4.0", + "pretty": "^2.0.0", + "sass": "^1.69.7", + "vite": "4.5.5" + } +} \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/assets/common-styles.css b/static/js/vue-cdr-access/src/assets/common-styles.css index ddc2c1bf99..5906cf1c01 100644 --- a/static/js/vue-cdr-access/src/assets/common-styles.css +++ b/static/js/vue-cdr-access/src/assets/common-styles.css @@ -35,17 +35,13 @@ a.button.is-link span { background-color: white; } -.image-download-options button { +.button.is-primary { background-color: #1A698C; color: white; - font-family: 'Open Sans', sans-serif; - height: 48px; } -.image-download-options button:hover, -.image-download-options button:focus { +.button.is-primary:hover { background-color: #084b6b; - color: white; } .image-download-options a { diff --git a/static/js/vue-cdr-access/src/components/clearFacetsButton.vue b/static/js/vue-cdr-access/src/components/clearFacetsButton.vue index da3a775cfb..7f527f1d77 100644 --- a/static/js/vue-cdr-access/src/components/clearFacetsButton.vue +++ b/static/js/vue-cdr-access/src/components/clearFacetsButton.vue @@ -2,9 +2,9 @@ Button for clearing all currently active facets limiting the search. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/components/clearFilters.vue b/static/js/vue-cdr-access/src/components/clearFilters.vue index b999d161f3..d9cd1f1535 100644 --- a/static/js/vue-cdr-access/src/components/clearFilters.vue +++ b/static/js/vue-cdr-access/src/components/clearFilters.vue @@ -1,8 +1,10 @@