From cc6f856785b7d7e3def77bb251386433d9b97deb Mon Sep 17 00:00:00 2001 From: sharonl <6546457+sharonluong@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:23:06 -0500 Subject: [PATCH 01/11] Bxc 4734 thumbnail placeholder (#1830) * BXC-4734 update for thumbnail placeholder usage * BXC-4734 remove placeholder test from download image IT * BXC-4734 change null to empty array * BXC-4734 clean up code * BXC-4734 update file extension --- .../boxc/indexing/solr/test/TestCorpus.java | 10 ++- .../api/images/ImageServerUtil.java | 2 +- .../search/solr/models/DatastreamImpl.java | 2 +- .../processing/DownloadImageService.java | 76 +++++++++++++++--- .../rest/DatastreamRestControllerIT.java | 37 +++++++-- .../test/resources/__files/placeholder.png | Bin 0 -> 11857 bytes 6 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 web-services-app/src/test/resources/__files/placeholder.png diff --git a/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/test/TestCorpus.java b/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/test/TestCorpus.java index a82ec3e103..7ddb903aad 100644 --- a/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/test/TestCorpus.java +++ b/indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/test/TestCorpus.java @@ -135,9 +135,10 @@ public List populate() { newDoc.addField("ancestorIds", makeAncestorIds(pid1, pid2)); newDoc.addField("ancestorPath", makeAncestorPath(pid1, pid2)); newDoc.addField("resourceType", ResourceType.Work.name()); - imgDatastreams.set(0, imgDatastreams.get(0) + pid6File.getId()); - imgDatastreams.set(1, imgDatastreams.get(1) + pid6File.getId()); - newDoc.addField(SearchFieldKey.DATASTREAM.getSolrField(), imgDatastreams); + List workDatastreams = Arrays.asList( + ORIGINAL_FILE.getId() + "|image/png|file.png|png|766|urn:sha1:checksum|" + pid6File.getId() + "|1200x1200", + DatastreamType.JP2_ACCESS_COPY.getId() + "|image/jp2|bunny.jp2|jp2|||" + pid6File.getId() + "|1200x1200"); + newDoc.addField(SearchFieldKey.DATASTREAM.getSolrField(), workDatastreams); newDoc.addField(SearchFieldKey.FILE_FORMAT_CATEGORY.getSolrField(), ContentCategory.image.getDisplayName()); newDoc.addField(SearchFieldKey.FILE_FORMAT_TYPE.getSolrField(), "png"); docs.add(newDoc); @@ -152,6 +153,9 @@ public List populate() { newDoc.addField("ancestorIds", makeAncestorIds(pid1, pid3)); newDoc.addField("ancestorPath", makeAncestorPath(pid1)); newDoc.addField("resourceType", "Collection"); + List collection2Datastream = List.of( + DatastreamType.JP2_ACCESS_COPY.getId() + "|image/jp2|bunny.jp2|jp2|||" + pid2.getId() + "|120x120"); + newDoc.addField(SearchFieldKey.DATASTREAM.getSolrField(), collection2Datastream); docs.add(newDoc); return docs; diff --git a/operations/src/main/java/edu/unc/lib/boxc/operations/api/images/ImageServerUtil.java b/operations/src/main/java/edu/unc/lib/boxc/operations/api/images/ImageServerUtil.java index 6fd7e4dc2f..dedab54a3a 100644 --- a/operations/src/main/java/edu/unc/lib/boxc/operations/api/images/ImageServerUtil.java +++ b/operations/src/main/java/edu/unc/lib/boxc/operations/api/images/ImageServerUtil.java @@ -30,7 +30,7 @@ public static String getImageServiceId(String id) { * @return */ public static String getImageServerEncodedId(String id) { - return URLEncoder.encode(getImageServiceId(id)); + return URLEncoder.encode(getImageServiceId(id), StandardCharsets.UTF_8); } /** diff --git a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/models/DatastreamImpl.java b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/models/DatastreamImpl.java index c4344e0f4b..16c91f03ec 100644 --- a/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/models/DatastreamImpl.java +++ b/search-solr/src/main/java/edu/unc/lib/boxc/search/solr/models/DatastreamImpl.java @@ -62,7 +62,7 @@ public DatastreamImpl(String datastream) { @Override public String toString() { - //DS name|mimetype|extension|filesize|checksum|owner|extent + //DS name|mimetype|filename|extension|filesize|checksum|owner|extent StringBuilder sb = new StringBuilder(); if (name != null) { sb.append(name); diff --git a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java index 88dda0dacf..93aabf8be8 100644 --- a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java +++ b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java @@ -7,6 +7,7 @@ import edu.unc.lib.boxc.search.api.models.Datastream; import edu.unc.lib.boxc.operations.api.images.ImageServerUtil; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; @@ -16,6 +17,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Objects; import static edu.unc.lib.boxc.operations.api.images.ImageServerUtil.FULL_SIZE; @@ -25,6 +29,7 @@ * @author snluong */ public class DownloadImageService { + private String PLACEHOLDER_ID = URLEncoder.encode("/default_images/placeholder.png", StandardCharsets.UTF_8); private String iiifBasePath; public static final String INVALID_SIZE_MESSAGE = "Unable to determine size for access copy download"; @@ -42,21 +47,31 @@ public ResponseEntity streamImage(ContentObjectRecord conte return ResponseEntity.notFound().build(); } - String pidString = contentObjectRecord.getPid().getId(); - String url = ImageServerUtil.buildURL(iiifBasePath, pidString, size); + var contentDispositionHeader = "inline;"; + if (attachment) { + String filename = getDownloadFilename(contentObjectRecord, size); + contentDispositionHeader = "attachment; filename=" + filename; + } + + var url = ""; + var contentType = MediaType.IMAGE_JPEG; + if (needsPlaceholder(contentObjectRecord, size)) { + url = getPlaceholderUrl(size); + contentType = MediaType.IMAGE_PNG; + } else { + String pidString = contentObjectRecord.getPid().getId(); + url = ImageServerUtil.buildURL(iiifBasePath, pidString, size); + } + InputStream input = new URL(url).openStream(); InputStreamResource resource = new InputStreamResource(input); - String filename = getDownloadFilename(contentObjectRecord, size); - var type = attachment ? "attachment" : "inline"; return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, type + "; filename=" + filename) - .contentType(MediaType.IMAGE_JPEG) + .header(HttpHeaders.CONTENT_DISPOSITION, contentDispositionHeader) + .contentType(contentType) .body(resource); } - - /** * Determines size based on original dimensions of requested file, unless requested size is full size. * @param contentObjectRecord solr record of the file @@ -128,14 +143,51 @@ private String getExtent(ContentObjectRecord contentObjectRecord) { return ds.getExtent(); } - private int getLongestSide(ContentObjectRecord contentObjectRecord) { + /** + * If the shortest side of the content object is smaller than the size requested, return the placeholder + * @param contentObjectRecord + * @param size + * @return true if service needs to return a placeholder image + */ + private boolean needsPlaceholder(ContentObjectRecord contentObjectRecord, String size) { + if (Objects.equals(FULL_SIZE, size)) { + return false; + } + var shortestSide = getShortestSide(contentObjectRecord); + return shortestSide < Integer.parseInt(size); + } + + private int[] getImageDimensions(ContentObjectRecord contentObjectRecord) { var extent = getExtent(contentObjectRecord); if (extent == null || extent.isEmpty()) { - return 0; + return ArrayUtils.EMPTY_INT_ARRAY; } // format of dimensions is like 800x1200, heightxwidth - String[] dimensionParts = extent.split("x"); - return Math.max(Integer.parseInt(dimensionParts[0]), Integer.parseInt(dimensionParts[1])); + // split by x and convert to integers + return Arrays.stream(extent.split("x")).mapToInt(Integer::parseInt).toArray(); + } + + private int getLongestSide(ContentObjectRecord contentObjectRecord) { + var extent = getImageDimensions(contentObjectRecord); + if (extent.length == 0) { + return 0; + } + + return Math.max(extent[0], extent[1]); + } + + private int getShortestSide(ContentObjectRecord contentObjectRecord) { + var extent = getImageDimensions(contentObjectRecord); + if (extent.length == 0) { + return 0; + } + return Math.min(extent[0], extent[1]); + } + + private String getPlaceholderUrl(String size) { + // pixel length should be in !123,123 format + var formattedSize = "!" + size + "," + size; + return iiifBasePath + PLACEHOLDER_ID + "/full/" + formattedSize + "/0/default.png"; } public void setIiifBasePath(String iiifBasePath) { diff --git a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java index a9612bbea8..b3bacbe8db 100644 --- a/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java +++ b/web-services-app/src/test/java/edu/unc/lib/boxc/web/services/rest/DatastreamRestControllerIT.java @@ -43,6 +43,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.io.File; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; @@ -222,7 +224,7 @@ public void testGetThumbnailWithFileObject() throws Exception { MockHttpServletResponse response = result.getResponse(); // TO DO assert correct image returned assertEquals("image/jpeg", response.getContentType()); - assertEquals("inline; filename=file_64px.jpg", response.getHeader(CONTENT_DISPOSITION)); + assertEquals("inline;", response.getHeader(CONTENT_DISPOSITION)); } @Test @@ -237,7 +239,7 @@ public void testGetThumbnailForWork() throws Exception { .willReturn(aResponse() .withStatus(HttpStatus.OK.value()) .withBody(filename) - .withHeader("Content-Type", "image/jpeg"))); + .withHeader("Content-Type", MediaType.IMAGE_JPEG_VALUE))); MvcResult result = mvc.perform(get("/thumb/" + workPid.getId() + "/large")) .andExpect(status().is2xxSuccessful()) @@ -246,8 +248,8 @@ public void testGetThumbnailForWork() throws Exception { // Verify content was retrieved MockHttpServletResponse response = result.getResponse(); // TO DO assert correct image returned - assertEquals("image/jpeg", response.getContentType()); - assertEquals("inline; filename=file_128px.jpg", response.getHeader(CONTENT_DISPOSITION)); + assertEquals(MediaType.IMAGE_JPEG_VALUE, response.getContentType()); + assertEquals("inline;", response.getHeader(CONTENT_DISPOSITION)); } @Test @@ -273,7 +275,7 @@ public void testGetThumbnailForCollection() throws Exception { MockHttpServletResponse response = result.getResponse(); assertEquals(filename, response.getContentAsString()); assertEquals(MediaType.IMAGE_JPEG_VALUE, response.getContentType()); - assertEquals("inline; filename=bunny_128px.jpg", response.getHeader(CONTENT_DISPOSITION)); + assertEquals("inline;", response.getHeader(CONTENT_DISPOSITION)); } @Test @@ -302,6 +304,31 @@ public void testGetThumbnailNoPermission() throws Exception { assertEquals("", response.getContentAsString(), "Content must not be returned"); } + @Test + public void testGetThumbnailReturnPlaceholder() throws Exception { + var corpus = populateCorpus(); + var collectionPid = corpus.pid3; + var placeholder = "placeholder.png"; + var id = URLEncoder.encode("/default_images/placeholder.png", UTF_8); + + stubFor(WireMock.get(urlMatching("/iiif/v3/" + id + "/full/!128,128/0/default.png")) + .willReturn(aResponse() + .withStatus(HttpStatus.OK.value()) + .withBodyFile(placeholder) + .withHeader("Content-Type", "image/png"))); + + MvcResult result = mvc.perform(get("/thumb/" + collectionPid.getId() + "/large")) + .andExpect(status().is2xxSuccessful()) + .andReturn(); + + // Verify content was retrieved + MockHttpServletResponse response = result.getResponse(); + // TO DO assert correct image returned + assertEquals(MediaType.IMAGE_PNG_VALUE, response.getContentType()); + assertEquals("inline;", response.getHeader(CONTENT_DISPOSITION)); + + } + private TestCorpus populateCorpus() throws Exception { var corpus = new TestCorpus(); embeddedSolrServer.add(corpus.populate()); diff --git a/web-services-app/src/test/resources/__files/placeholder.png b/web-services-app/src/test/resources/__files/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..cde924956d812df1473f66e534ebeeb085318030 GIT binary patch literal 11857 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 literal 0 HcmV?d00001 From fcc6ef5e005e909019522d2dc393d52b1545ed37 Mon Sep 17 00:00:00 2001 From: Ben Pennell Date: Thu, 7 Nov 2024 12:46:55 -0500 Subject: [PATCH 02/11] Block jp2 processing for x-raw-panasonic. Add unit test (#1829) --- .../images/ImageDerivativeProcessor.java | 2 +- .../images/ImageDerivativeProcessorTest.java | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessorTest.java diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessor.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessor.java index 2b5d916b04..418f91a248 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessor.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessor.java @@ -19,7 +19,7 @@ public class ImageDerivativeProcessor implements Processor { private static final Pattern MIMETYPE_PATTERN = Pattern.compile("^(image.*$|application.*?(photoshop|psd)$)"); - private static final Pattern DISALLOWED_PATTERN = Pattern.compile(".*(vnd\\.fpx|x-icon).*"); + private static final Pattern DISALLOWED_PATTERN = Pattern.compile(".*(vnd\\.fpx|x-icon|x-raw-panasonic).*"); /** * Returns true if the subject of the exchange is a binary which diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessorTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessorTest.java new file mode 100644 index 0000000000..5da7eba5dd --- /dev/null +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/images/ImageDerivativeProcessorTest.java @@ -0,0 +1,76 @@ +package edu.unc.lib.boxc.services.camel.images; + +import edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +/** + * @author bbpennel + */ +public class ImageDerivativeProcessorTest { + @Mock + private Exchange exchange; + @Mock + private Message messageIn; + private AutoCloseable closeable; + private ImageDerivativeProcessor processor; + + @BeforeEach + public void init() { + closeable = openMocks(this); + + when(exchange.getIn()).thenReturn(messageIn); + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryPath)).thenReturn("path/to/binary"); + + processor = new ImageDerivativeProcessor(); + } + + @AfterEach + public void close() throws Exception { + closeable.close(); + } + + @Test + public void testAllowedImageTypePanasonicRaw() { + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType)).thenReturn("image/x-raw-panasonic"); + + assertFalse(ImageDerivativeProcessor.allowedImageType(exchange)); + } + + @Test + public void testAllowedImageTypePanasonicRw2() { + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType)).thenReturn("image/x-panasonic-rw2"); + + assertTrue(ImageDerivativeProcessor.allowedImageType(exchange)); + } + + @Test + public void testAllowedImageTypeIcon() { + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType)).thenReturn("image/x-icon"); + + assertFalse(ImageDerivativeProcessor.allowedImageType(exchange)); + } + + @Test + public void testAllowedImageTypeJpeg() { + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType)).thenReturn("image/jpeg"); + + assertTrue(ImageDerivativeProcessor.allowedImageType(exchange)); + } + + @Test + public void testAllowedImageTypePhotoshop() { + when(messageIn.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType)).thenReturn("application/photoshop"); + + assertTrue(ImageDerivativeProcessor.allowedImageType(exchange)); + } +} From dd09609a60f81f4bce4a134ba4cd138fe80adb6a Mon Sep 17 00:00:00 2001 From: sharonl <6546457+sharonluong@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:48:41 -0500 Subject: [PATCH 03/11] Bxc 4761 replace existing bug (#1832) * BXC-4761 set force to true to force access copy regeneration * BXC-4761 set to string true --- .../camel/thumbnails/ImportThumbnailRequestProcessor.java | 2 ++ .../services/camel/thumbnails/ImportThumbnailProcessorTest.java | 1 + 2 files changed, 3 insertions(+) diff --git a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java index 40e0b6b9ed..a1dd0fc957 100644 --- a/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java +++ b/services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailRequestProcessor.java @@ -36,6 +36,8 @@ public void process(Exchange exchange) throws IOException { in.setHeader(CdrBinaryPath, storagePath.toString()); in.setHeader(CdrBinaryMimeType, mimetype); in.setHeader(FCREPO_URI, repoPath); + // force access copy regeneration when importing a thumbnail + in.setHeader("force", "true"); } /** diff --git a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java index 4ccb3e3c30..7839a2973b 100644 --- a/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java +++ b/services-camel-app/src/test/java/edu/unc/lib/boxc/services/camel/thumbnails/ImportThumbnailProcessorTest.java @@ -71,6 +71,7 @@ public void testImportThumbnail() throws IOException { verify(message).setHeader(FCREPO_URI, pid.getRepositoryPath()); verify(message).setHeader(CdrBinaryMimeType, mimetype); verify(message).setHeader(CdrBinaryPath, path.toString()); + verify(message).setHeader("force", "true"); } @Test From 4ec5bf38e9b89745a9435f949693dd3037c36901 Mon Sep 17 00:00:00 2001 From: sharonl <6546457+sharonluong@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:25:54 -0500 Subject: [PATCH 04/11] Bxc 4760 admin unit thumb (#1833) * BXC-4760 excuse admin unit from permissions check * BXC-4760 move method --- .../vue-cdr-access/src/components/full_record/thumbnail.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/js/vue-cdr-access/src/components/full_record/thumbnail.vue b/static/js/vue-cdr-access/src/components/full_record/thumbnail.vue index 5bf3ef0bf5..e3b6fa74b8 100644 --- a/static/js/vue-cdr-access/src/components/full_record/thumbnail.vue +++ b/static/js/vue-cdr-access/src/components/full_record/thumbnail.vue @@ -44,6 +44,10 @@ export default { methods: { altText(title) { return `Thumbnail for ${title}`; + }, + + canView() { + return (this.objectData.type === 'AdminUnit' || this.hasPermission(this.objectData, 'viewAccessCopies')); } }, @@ -101,7 +105,7 @@ export default { }, src() { - if (this.objectData.thumbnail_url !== undefined && this.hasPermission(this.objectData, 'viewAccessCopies')) { + if (this.objectData.thumbnail_url !== undefined && this.canView()) { return this.objectData.thumbnail_url; } From 87899ca8bcafb9e26c8e8d4fed0cdd53fcaeaa95 Mon Sep 17 00:00:00 2001 From: lfarrell Date: Wed, 13 Nov 2024 09:27:36 -0500 Subject: [PATCH 05/11] Display filesize on file objects --- .../vue-cdr-access/src/components/full_record/fileRecord.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/js/vue-cdr-access/src/components/full_record/fileRecord.vue b/static/js/vue-cdr-access/src/components/full_record/fileRecord.vue index e579805647..d402449430 100644 --- a/static/js/vue-cdr-access/src/components/full_record/fileRecord.vue +++ b/static/js/vue-cdr-access/src/components/full_record/fileRecord.vue @@ -35,6 +35,10 @@ {{ $t('full_record.creator') }}: {{ recordData.briefObject.creator.join('; ') }} +
  • + {{ $t('full_record.filesize') }}: + {{ formatFilesize(recordData.briefObject.filesizeTotal) }} +
  • {{ $t('full_record.date_created') }}: {{ formatDate(recordData.briefObject.created) }} From 83928464ec5a83302a7a7e99ca5bf339449fd8e4 Mon Sep 17 00:00:00 2001 From: Dean Farrell Date: Thu, 14 Nov 2024 12:35:27 -0500 Subject: [PATCH 06/11] Move file downloads into the files table (#1831) * Move image download access into the files table * Update tests and fix issues found by tests * Fix issue with empty download column showing up, if there is no downloadable content * Add translations back in and check dropdown urls, instead of text, as translations aren't in scope in the tests. * Don't show modal if user is logged in * Check for restricted content, not downloadable images, and pass the file's info into the restriction check instead of using the work's info. * Fix display issue and modal tests * Use login modal on file pages * Don't show modal login if logging in doesn't grant further access. * Fix layout of buttons on small screens and add "view" item back in for file records * Display login button for folders and collections * Fix button layout * Fix button layout in the files table * Fix issue with no valid download sizes, but user canViewOriginal * Set default as logged in * Check if JP2 exists and rename restrictedContent component --- static/css/sass/cdr_ui_styles.scss | 1 + static/js/vue-cdr-access/package-lock.json | 201 ++++----- static/js/vue-cdr-access/package.json | 12 +- .../full_record/aggregateRecord.vue | 4 +- .../full_record/collectionRecord.vue | 6 +- .../full_record/downloadOptions.vue | 292 ++++++++++++++ .../src/components/full_record/fileList.vue | 56 ++- .../src/components/full_record/fileRecord.vue | 6 +- .../components/full_record/folderRecord.vue | 12 +- ...estrictedContent.vue => objectActions.vue} | 51 +-- .../src/mixins/fileDownloadUtils.js | 140 ------- .../src/mixins/fullRecordUtils.js | 2 +- static/js/vue-cdr-access/src/translations.js | 1 + .../tests/unit/downloadOptions.spec.js | 380 ++++++++++++++++++ .../tests/unit/fileList.spec.js | 93 ----- ...dContent.spec.js => objectActions.spec.js} | 166 +------- 16 files changed, 868 insertions(+), 555 deletions(-) create mode 100644 static/js/vue-cdr-access/src/components/full_record/downloadOptions.vue rename static/js/vue-cdr-access/src/components/full_record/{restrictedContent.vue => objectActions.vue} (50%) delete mode 100644 static/js/vue-cdr-access/src/mixins/fileDownloadUtils.js create mode 100644 static/js/vue-cdr-access/tests/unit/downloadOptions.spec.js rename static/js/vue-cdr-access/tests/unit/{restrictedContent.spec.js => objectActions.spec.js} (53%) diff --git a/static/css/sass/cdr_ui_styles.scss b/static/css/sass/cdr_ui_styles.scss index 6dc0e5c752..f506e79851 100644 --- a/static/css/sass/cdr_ui_styles.scss +++ b/static/css/sass/cdr_ui_styles.scss @@ -1094,6 +1094,7 @@ table.dataTable { } } + .relateditem { margin-left: 5px; margin-right: 5px; diff --git a/static/js/vue-cdr-access/package-lock.json b/static/js/vue-cdr-access/package-lock.json index c6abaec346..19ad89e6d3 100644 --- a/static/js/vue-cdr-access/package-lock.json +++ b/static/js/vue-cdr-access/package-lock.json @@ -12,10 +12,10 @@ "@vueuse/head": "^2.0.0", "axios": "^1.7.7", "canvas": "^2.11.2", - "datatables.net": "^1.13.11", - "datatables.net-bm": "^1.13.11", - "datatables.net-buttons-bm": "^2.4.3", - "datatables.net-vue3": "^2.1.3", + "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", @@ -29,13 +29,13 @@ "pinia": "^2.2.4", "veaury": "^2.5.1", "vue": "^3.5.12", - "vue-i18n": "^9.14.1", + "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": "^4.2.0", + "@intlify/vue-i18n-loader": "^5.0.1", "@pinia/testing": "0.1.6", "@testing-library/jest-dom": "^6.5.0", "@unhead/addons": "^1.8.9", @@ -2228,15 +2228,15 @@ } }, "node_modules/@intlify/bundle-utils": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-2.2.2.tgz", - "integrity": "sha512-vngkvlIVV8ZJoyC5VqMvqJd2nvsx+qMN7pQjPiPjOrVndeiR7Dlue0k86Q8FsFUzyksW3HJZZi833ldxwbFzTA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.4.0.tgz", + "integrity": "sha512-2UQkqiSAOSPEHMGWlybqWm4G2K0X+FyYho5AwXz6QklSX1EY5EDmOSxZmwscn2qmKBnp6OYsme5kUrnN9xrWzQ==", "dev": true, "dependencies": { - "@intlify/message-compiler": "^9.1.0", - "@intlify/shared": "^9.1.0", + "@intlify/message-compiler": "next", + "@intlify/shared": "next", "jsonc-eslint-parser": "^1.0.1", - "source-map": "^0.6.1", + "source-map": "0.6.1", "yaml-eslint-parser": "^0.3.2" }, "engines": { @@ -2261,12 +2261,12 @@ } }, "node_modules/@intlify/core-base": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.1.tgz", - "integrity": "sha512-rG5/hlNW6Qfve41go37szEf0mVLcfhYuOu83JcY0jZKasnwsrcZYYWDzebCcuO5I/6Sy1JFWo9p+nvkQS1Dy+w==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.4.tgz", + "integrity": "sha512-GG428DkrrWCMhxRMRQZjuS7zmSUzarYcaHJqG9VB8dXAxw4iQDoKVQ7ChJRB6ZtsCsX3Jse1PEUlHrJiyQrOTg==", "dependencies": { - "@intlify/message-compiler": "9.14.1", - "@intlify/shared": "9.14.1" + "@intlify/message-compiler": "10.0.4", + "@intlify/shared": "10.0.4" }, "engines": { "node": ">= 16" @@ -2275,12 +2275,39 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@intlify/core-base/node_modules/@intlify/message-compiler": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.4.tgz", + "integrity": "sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==", + "dependencies": { + "@intlify/shared": "10.0.4", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/core-base/node_modules/@intlify/shared": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.4.tgz", + "integrity": "sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@intlify/message-compiler": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.1.tgz", - "integrity": "sha512-MY8hwukJBnXvGAncVKlHsqKDQ5ZcQx4peqEmI8wBUTXn4pezrtTGYXNoz81cLyEEHB+L/zlKWVBSh5TiX4gYoQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.0.tgz", + "integrity": "sha512-OcaWc63NC/9p1cMdgoNKBj4d61BH8sUW1Hfs6YijTd9656ZR4rNqXAlRnBrfS5ABq0vjQjpa8VnyvH9hK49yBw==", + "dev": true, "dependencies": { - "@intlify/shared": "9.14.1", + "@intlify/shared": "10.0.0", "source-map-js": "^1.0.2" }, "engines": { @@ -2290,10 +2317,23 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@intlify/message-compiler/node_modules/@intlify/shared": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.0.tgz", + "integrity": "sha512-6ngLfI7DOTew2dcF9WMJx+NnMWghMBhIiHbGg+wRvngpzD5KZJZiJVuzMsUQE1a5YebEmtpTEfUrDp/NqVGdiw==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@intlify/shared": { "version": "9.14.1", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.1.tgz", "integrity": "sha512-XjHu6PEQup9MnP1x0W9y0nXXfq9jFftAYSfV11hryjtH4XqXP8HrzMvXI+ZVifF+jZLszaTzIhvukllplxTQTg==", + "dev": true, "engines": { "node": ">= 16" }, @@ -2302,13 +2342,13 @@ } }, "node_modules/@intlify/vue-i18n-loader": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@intlify/vue-i18n-loader/-/vue-i18n-loader-4.2.0.tgz", - "integrity": "sha512-d7aBmMNWJskcZPT5rJH4h2XHe/PwNoJUaY0PGla9g+NSD4B0UR8LBKrp126nlaUfA74Xt0FEGvzCfG9KdC9KoA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@intlify/vue-i18n-loader/-/vue-i18n-loader-5.0.1.tgz", + "integrity": "sha512-z1dFLsR5YsEbA7+zqd8C3lvBOr6DWMMyUdX3Y42e+6Y5cL8uE55uQfdjUDbhQe7R6YlZT7ZDaIXoIGyoFaJDNg==", "dev": true, "dependencies": { - "@intlify/bundle-utils": "^2.2.2", - "@intlify/shared": "^9.1.0", + "@intlify/bundle-utils": "3.4.0", + "@intlify/shared": "^9.2.2", "js-yaml": "^4.1.0", "json5": "^2.2.0", "loader-utils": "^2.0.0" @@ -2317,9 +2357,9 @@ "node": ">= 12" }, "peerDependencies": { - "petite-vue-i18n": "^9.1.0", + "petite-vue-i18n": "*", "vue": "^3.0.0", - "vue-i18n": "^9.1.0" + "vue-i18n": "*" }, "peerDependenciesMeta": { "petite-vue-i18n": { @@ -5737,90 +5777,50 @@ } }, "node_modules/datatables.net": { - "version": "1.13.11", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.11.tgz", - "integrity": "sha512-AE6RkMXziRaqzPcu/pl3SJXeRa6fmXQG/fVjuRESujvkzqDCYEeKTTpPMuVJSGYJpPi32WGSphVNNY1G4nSN/g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.1.8.tgz", + "integrity": "sha512-47ULt+U4bcjbuGTpTlT6SnCuSFVRBxxdWa6X3NfvTObBJ2BZU0o+JUIl05wQ6cABNIavjbAV51gpgvFsMHL9zA==", "dependencies": { - "jquery": "1.8 - 4" + "jquery": ">=1.7" } }, "node_modules/datatables.net-bm": { - "version": "1.13.11", - "resolved": "https://registry.npmjs.org/datatables.net-bm/-/datatables.net-bm-1.13.11.tgz", - "integrity": "sha512-yaNyHgxDuyUqXN08tdLSYWQrb5vp+7H+Nh1ZVbtnWeDxmgSOQvCS08NW5YOb8m8yzyrC7B8vZ/NR26/q+yRX6A==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/datatables.net-bm/-/datatables.net-bm-2.1.8.tgz", + "integrity": "sha512-xTMFJItMEHIdu+PR0N6pIiPifNF4/kSa0j4D4CmfOTsQtbxtKnK+PRlaOdRK+UUw6S2W5+8qJy73dfikex6FUg==", "dependencies": { - "datatables.net": "1.13.11", - "jquery": "1.8 - 4" + "datatables.net": "2.1.8", + "jquery": ">=1.7" } }, "node_modules/datatables.net-buttons": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-2.4.3.tgz", - "integrity": "sha512-xoHD6I2kxnU/CEp97Ar0lSnAL1siucQ/5Q/otGWWfWE2VN0o4n5C2h2Ot/ZCS8kxbEHBGd873Bc2xPdJH87yOw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-3.1.2.tgz", + "integrity": "sha512-D7xjmXR4P056JAD6skjKYcSvQxRslt69n5dgw8KX/6nmkSJirt3iFlhKo69GzHTFTOBN49WyPvStxpbUFgzc2A==", "dependencies": { - "datatables.net": "^1.13.0", + "datatables.net": "^2", "jquery": ">=1.7" } }, "node_modules/datatables.net-buttons-bm": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/datatables.net-buttons-bm/-/datatables.net-buttons-bm-2.4.3.tgz", - "integrity": "sha512-Dxsk+FRWp30blUQgMPYC5PKdZzGH5Vk3WB+/YJufIVwNUcTc7VS0f08MO7NLk4mSrIX34iTmdKpj0fOClstENw==", - "dependencies": { - "datatables.net-bm": "^1.13.0", - "datatables.net-buttons": "2.4.3", - "jquery": ">=1.7" - } - }, - "node_modules/datatables.net-dt": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/datatables.net-dt/-/datatables.net-dt-1.13.8.tgz", - "integrity": "sha512-/ZPzr1hQ+domerlg/MbcQHqeeqxK9fsZmpRs1YeKxsdfr+UyHQTUiiOO7RqekppSLc7MPqxGnzKkCX9vAgqm0w==", - "dependencies": { - "datatables.net": "1.13.8", - "jquery": ">=1.7" - } - }, - "node_modules/datatables.net-dt/node_modules/datatables.net": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.8.tgz", - "integrity": "sha512-2pDamr+GUwPTby2OgriVB9dR9ftFKD2AQyiuCXzZIiG4d9KkKFQ7gqPfNmG7uj9Tc5kDf+rGj86do4LAb/V71g==", - "dependencies": { - "jquery": ">=1.7" - } - }, - "node_modules/datatables.net-responsive": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-2.5.0.tgz", - "integrity": "sha512-GL7DFiRl5qqrp5ql54Psz92xTGPR0rMcrO3hzNxMfvcfpRGL5zFNTvMpTUh59Erm6u1+KoX+j+Ig1ZD3r0iFsA==", - "dependencies": { - "datatables.net": ">=1.13.4", - "jquery": ">=1.7" - } - }, - "node_modules/datatables.net-responsive-dt": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/datatables.net-responsive-dt/-/datatables.net-responsive-dt-2.5.0.tgz", - "integrity": "sha512-u0oHqG1LLrFj+cBVYpyOO03nECUJJim4EovKhdQtB2g8X0fbH7jWvLAOdDFvyIzktCYWScObqR8uUEtm6J9O6Q==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/datatables.net-buttons-bm/-/datatables.net-buttons-bm-3.1.2.tgz", + "integrity": "sha512-Sm34LxvIH/2xXj/cUfNCSf2q2WW5W5X2jGrJqK7vBZIM6IhsY5O/LGQfw19s5AOmZL6Oj0ma14+l7kkfxAW1fQ==", "dependencies": { - "datatables.net-dt": ">=1.13.4", - "datatables.net-responsive": ">=2.4.1", + "datatables.net-bm": "^2", + "datatables.net-buttons": "3.1.2", "jquery": ">=1.7" } }, "node_modules/datatables.net-vue3": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/datatables.net-vue3/-/datatables.net-vue3-2.1.3.tgz", - "integrity": "sha512-9A81RiszsaH5sia01+E8tusagXbzeYVMSD0I+tzqx7V2iqVVZJgVVZM+4hwGa5skSJiHOUCRl3kp2TLF9uLf9A==", - "dependencies": { - "datatables.net-responsive-dt": "^2.4.0" - }, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/datatables.net-vue3/-/datatables.net-vue3-3.0.2.tgz", + "integrity": "sha512-UJs3IrKS+gsaPSy3CeL5ny/hGWm8xs77DZLYSW8QXPpDhMS7ZZfC4AGKNvXa2C9J1OllWhiR20dScpi0lKdCvQ==", "engines": { "node": ">=12" }, "peerDependencies": { - "datatables.net": "^1.13.1", - "jquery": "^3.6.0", + "datatables.net": "^2", "vue": "^3.0.5" } }, @@ -12522,12 +12522,12 @@ "dev": true }, "node_modules/vue-i18n": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.1.tgz", - "integrity": "sha512-xjxV0LYc1xQ8TbAVfIyZiOSS8qoU1R0YwV7V5I8I6Fd64+zvsTsdPgtylPsie3Vdt9wekeYhr+smKDeaK6RBuA==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.4.tgz", + "integrity": "sha512-1xkzVxqBLk2ZFOmeI+B5r1J7aD/WtNJ4j9k2mcFcQo5BnOmHBmD7z4/oZohh96AAaRZ4Q7mNQvxc9h+aT+Md3w==", "dependencies": { - "@intlify/core-base": "9.14.1", - "@intlify/shared": "9.14.1", + "@intlify/core-base": "10.0.4", + "@intlify/shared": "10.0.4", "@vue/devtools-api": "^6.5.0" }, "engines": { @@ -12540,6 +12540,17 @@ "vue": "^3.0.0" } }, + "node_modules/vue-i18n/node_modules/@intlify/shared": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.4.tgz", + "integrity": "sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/vue-router": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", diff --git a/static/js/vue-cdr-access/package.json b/static/js/vue-cdr-access/package.json index f10abb5687..086b4e0304 100644 --- a/static/js/vue-cdr-access/package.json +++ b/static/js/vue-cdr-access/package.json @@ -13,10 +13,10 @@ "@vueuse/head": "^2.0.0", "axios": "^1.7.7", "canvas": "^2.11.2", - "datatables.net": "^1.13.11", - "datatables.net-bm": "^1.13.11", - "datatables.net-buttons-bm": "^2.4.3", - "datatables.net-vue3": "^2.1.3", + "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", @@ -30,13 +30,13 @@ "pinia": "^2.2.4", "veaury": "^2.5.1", "vue": "^3.5.12", - "vue-i18n": "^9.14.1", + "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": "^4.2.0", + "@intlify/vue-i18n-loader": "^5.0.1", "@pinia/testing": "0.1.6", "@testing-library/jest-dom": "^6.5.0", "@unhead/addons": "^1.8.9", diff --git a/static/js/vue-cdr-access/src/components/full_record/aggregateRecord.vue b/static/js/vue-cdr-access/src/components/full_record/aggregateRecord.vue index 66eb6e14bd..a4f0856119 100644 --- a/static/js/vue-cdr-access/src/components/full_record/aggregateRecord.vue +++ b/static/js/vue-cdr-access/src/components/full_record/aggregateRecord.vue @@ -82,13 +82,13 @@ import abstract from '@/components/full_record/abstract.vue'; import fileList from '@/components/full_record/fileList.vue'; import metadataDisplay from '@/components/full_record/metadataDisplay.vue'; import neighborList from '@/components/full_record/neighborList.vue'; +import objectActions from '@/components/full_record/objectActions.vue'; import player from '@/components/full_record/player.vue'; -import restrictedContent from '@/components/full_record/restrictedContent.vue'; export default { name: 'aggregateRecord', - components: {abstract, fileList, neighborList, metadataDisplay, player, restrictedContent}, + components: {abstract, fileList, neighborList, metadataDisplay, objectActions, player}, mixins: [fileUtils, fullRecordUtils], } diff --git a/static/js/vue-cdr-access/src/components/full_record/collectionRecord.vue b/static/js/vue-cdr-access/src/components/full_record/collectionRecord.vue index 716212bce6..c78b0a2e13 100644 --- a/static/js/vue-cdr-access/src/components/full_record/collectionRecord.vue +++ b/static/js/vue-cdr-access/src/components/full_record/collectionRecord.vue @@ -25,7 +25,7 @@

    {{ $t('full_record.additional_metadata') }}

    - + import fullRecordUtils from '../../mixins/fullRecordUtils'; import abstract from '@/components/full_record/abstract.vue'; -import restrictedContent from '@/components/full_record/restrictedContent.vue'; +import objectActions from '@/components/full_record/objectActions.vue'; export default { name: 'collectionRecord', mixins: [fullRecordUtils], - components: { abstract, restrictedContent } + components: { abstract, objectActions } } diff --git a/static/js/vue-cdr-access/src/components/full_record/downloadOptions.vue b/static/js/vue-cdr-access/src/components/full_record/downloadOptions.vue new file mode 100644 index 0000000000..3a839f837b --- /dev/null +++ b/static/js/vue-cdr-access/src/components/full_record/downloadOptions.vue @@ -0,0 +1,292 @@ + + + + \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/components/full_record/fileList.vue b/static/js/vue-cdr-access/src/components/full_record/fileList.vue index 983a2f3020..7f1ce646e1 100644 --- a/static/js/vue-cdr-access/src/components/full_record/fileList.vue +++ b/static/js/vue-cdr-access/src/components/full_record/fileList.vue @@ -19,17 +19,23 @@ force it to reload {{ $t('full_record.file_type') }} {{ $t('full_record.filesize') }} {{ $t('full_record.view_file') }} - {{ $t('full_record.download_file') }} + {{ $t('full_record.download_file') }} {{ $t('full_record.mods') }} + @@ -62,10 +62,4 @@ export default { font-size: 1.5rem; } } - -.restricted-access { - h2 { - text-align: center; - } -} \ No newline at end of file diff --git a/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue b/static/js/vue-cdr-access/src/components/full_record/objectActions.vue similarity index 50% rename from static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue rename to static/js/vue-cdr-access/src/components/full_record/objectActions.vue index c0f2b5f018..7fba7d6667 100644 --- a/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue +++ b/static/js/vue-cdr-access/src/components/full_record/objectActions.vue @@ -1,20 +1,17 @@