diff --git a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js index d6de1e6f34..19f9deb588 100644 --- a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js +++ b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js @@ -361,4 +361,8 @@ function createBlackThinOutline($element) { function createBlackMediumOutline($element) { $element.addClass("black-medium-outline"); +} + +function highlightCasePreviewDocument(){ + createRedMediumOutline($("a[id$=':preview-file']")); } \ No newline at end of file diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/common/Variable.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/common/Variable.java index 81ffd6fc1a..0326674235 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/common/Variable.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/common/Variable.java @@ -35,7 +35,10 @@ public enum Variable { GLOBAL_FOOTER_INFO("Portal.GlobalFooterInfo"), USER_MENU("Portal.UserMenu"), DEFAULT_THEME_MODE("Portal.Theme.Mode"), GLOBAL_SEARCH_SCOPE_BY_CATEGORIES("Portal.GlobalSearchScopeCategories"), SHOW_QUICK_GLOBAL_SEARCH("Portal.ShowQuickGlobalSearch"), - ENABLE_SWITCH_THEME_BUTTON("Portal.Theme.EnableSwitchThemeModeButton"), DASHBOARD_MAIN_MENU_ENTRY("Portal.Dashboard.MainMenuEntry"), APPLICATION_NAME("Portal.ApplicationName"); + ENABLE_SWITCH_THEME_BUTTON("Portal.Theme.EnableSwitchThemeModeButton"), + DASHBOARD_MAIN_MENU_ENTRY("Portal.Dashboard.MainMenuEntry"), + APPLICATION_NAME("Portal.ApplicationName"), + ENABLE_DOCUMENT_PREVIEW("Portal.Document.EnablePreview"); private String key; diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalCasesScreenshotTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalCasesScreenshotTest.java index 8a6f125fe7..b59fed7b6e 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalCasesScreenshotTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalCasesScreenshotTest.java @@ -226,4 +226,19 @@ public void screenshotProcessOverviewLink() throws IOException { ScreenshotUtils .captureHalfLeftPageScreenShot(ScreenshotUtils.PROCESSES_INFORMATION_WIDGET_FOLDER + "process-overview-link"); } + + @Test + public void screenshotPreviewDocument() throws IOException{ + updateGlobalVariable(Variable.ENABLE_DOCUMENT_PREVIEW.getKey(), "true"); + CaseWidgetNewDashBoardPage caseWidget = mainMenuPage.openCaseList(); + ScreenshotUtils.resizeBrowser(new Dimension(1600, SCREENSHOT_WIDTH)); + WaitHelper.waitForNavigation(() -> caseWidget.openDetailsCase("Order Pizza")); + CaseDetailsPage detailsPage = new CaseDetailsPage(); + detailsPage.uploadDocumentWithoutError(FileHelper.getAbsolutePathToTestFile("test-no-files-no-js.pdf")); + refreshPage(); + detailsPage.waitForCaseDetailsDisplay(); + ScreenshotUtils.executeDecorateJs("highlightCasePreviewDocument()"); + ScreenshotUtils.captureElementWithMarginOptionScreenshot(detailsPage.getDocumentBox(), + ScreenshotUtils.CASE_DETAIL_FOLDER + "how-to-preview-document", new ScreenshotMargin(10)); + } } diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseDetailsPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseDetailsPage.java index e069a25ee1..d60ddeccfd 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseDetailsPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseDetailsPage.java @@ -1034,5 +1034,9 @@ public void clickShowCaseOwners() { public int countCaseOwners() { return $$("div[id$=':security-member-container']").size(); } + + public boolean getFirstItemPreviewDocumentVisible() { + return $("a[id$=':0:preview-file']").exists(); + } } diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/UploadDocumentTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/UploadDocumentTest.java index 536641b154..b4c5c1fbef 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/UploadDocumentTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/UploadDocumentTest.java @@ -36,12 +36,14 @@ public void setup() { @Test public void uploadNormalDocument() { + updateGlobalVariable(Variable.ENABLE_DOCUMENT_PREVIEW.getKey(), "true"); initNewDashboardPage(TestAccount.ADMIN_USER); casePage = menuPage.openCaseList(); caseDetailsPage = casePage.openDetailsCase("Leave Request"); int numberOfDocument = caseDetailsPage.countNumberOfDocument(); caseDetailsPage.uploadDocumentWithoutError(FileHelper.getAbsolutePathToTestFile("test-no-files-no-js.pdf")); caseDetailsPage.checkNumberOfDocument(numberOfDocument + 1); + assertTrue(caseDetailsPage.getFirstItemPreviewDocumentVisible()); } @Test @@ -84,6 +86,7 @@ public void uploadUnsupportedFileType() { @Test public void uploadDocumentAndCheckDocumentName() { + updateGlobalVariable(Variable.ENABLE_DOCUMENT_PREVIEW.getKey(), "true"); final String pdfFile = "test-no-files-no-js.pdf"; final String wordFile = "test-ms-word-extension.doc"; final String unsupportFile = "unsupportedExtension.abc"; @@ -94,16 +97,22 @@ public void uploadDocumentAndCheckDocumentName() { caseDetailsPage = casePage.openDetailsCase("Leave Request"); caseDetailsPage.uploadDocumentWithoutError(FileHelper.getAbsolutePathToTestFile(pdfFile)); isCorrectIconExtension(pdfFile, "si si-office-file-pdf-1"); + // can preview this document + assertTrue(caseDetailsPage.getFirstItemPreviewDocumentVisible()); casePage = menuPage.openCaseList(); caseDetailsPage = casePage.openDetailsCase("Leave Request"); caseDetailsPage.uploadDocumentWithoutError(FileHelper.getAbsolutePathToTestFile(wordFile)); isCorrectIconExtension(wordFile, "si si-office-file-doc-1"); + // can not preview + assertFalse(caseDetailsPage.getFirstItemPreviewDocumentVisible()); casePage = menuPage.openCaseList(); caseDetailsPage = casePage.openDetailsCase("Leave Request"); caseDetailsPage.uploadDocumentWithoutError(FileHelper.getAbsolutePathToTestFile(unsupportFile)); isCorrectIconExtension(unsupportFile, "si si-common-file-empty"); + // can not preview + assertFalse(caseDetailsPage.getFirstItemPreviewDocumentVisible()); } private boolean isCorrectIconExtension(String fileName, String iconClass) { diff --git a/AxonIvyPortal/portal/cms/cms_de.yaml b/AxonIvyPortal/portal/cms/cms_de.yaml index f7223acbf5..8dd85de889 100644 --- a/AxonIvyPortal/portal/cms/cms_de.yaml +++ b/AxonIvyPortal/portal/cms/cms_de.yaml @@ -202,6 +202,7 @@ ch.ivy.addon.portalkit.ui.jsf: ApplicationName: Der Standard-Anwendungsname, der im Seitentitel angezeigt wird. BaseQRCodeUrl: Legen Sie die Basis-URL für den anzuzeigenden QR-Code fest. DefaultThemeMode: Der Standardthemenmodus der Anwendung + EnableDocumentPreview: 'Auf "true" setzen, um die Vorschau von Dokumenten für bestimmte Dateitypen zu aktivieren. Unterstützte Dateitypen sind: pdf, png, jpeg, jpg, txt, log.' EnableSwitchThemeModeButton: Setzen Sie diesen Wert auf true, um die Schaltfläche "Thema wechseln" zu aktivieren. GlobalSearchScopeCategoriesNote: Festlegung der Typen, nach denen bei der globalen Suche gesucht werden soll GooglePlayURL: Legen Sie die URL fest, um die Axon Ivy-Mobilanwendung auf Google Play herunterzuladen. @@ -878,6 +879,7 @@ ch.ivy.addon.portalkit.ui.jsf: yourProcesses: Ihre Prozesse yourTasks: Ihre Aufgaben documentFiles: + PreviewDocument: Vorschau deleteDocumentNote: '{0} hat folgendes gelöscht: {1}' deleteSuceed: Datei wurde erfolgreich gelöscht download: Herunterladen diff --git a/AxonIvyPortal/portal/cms/cms_en.yaml b/AxonIvyPortal/portal/cms/cms_en.yaml index 73c02a2e09..f1b3b2f579 100644 --- a/AxonIvyPortal/portal/cms/cms_en.yaml +++ b/AxonIvyPortal/portal/cms/cms_en.yaml @@ -202,6 +202,7 @@ ch.ivy.addon.portalkit.ui.jsf: ApplicationName: The default application name that will be displayed on the page title. BaseQRCodeUrl: Set Base URL for QR code to display. DefaultThemeMode: The default theme mode of the application + EnableDocumentPreview: 'Set to true to enable document preview for some certain file types. Supported file types are: pdf, png, txt, log.' EnableSwitchThemeModeButton: Set to true to enable the switch theme button. GlobalSearchScopeCategoriesNote: Defining the types that the global search will search for GooglePlayURL: Set URL to download Axon Ivy mobile app on Google Play. @@ -880,6 +881,7 @@ ch.ivy.addon.portalkit.ui.jsf: yourProcesses: Your Processes yourTasks: Your Tasks documentFiles: + PreviewDocument: Preview deleteDocumentNote: '{0} has deleted {1}' deleteSuceed: File deleted successfully download: Download diff --git a/AxonIvyPortal/portal/cms/cms_es.yaml b/AxonIvyPortal/portal/cms/cms_es.yaml index cc0eeedb7d..f21872cbde 100644 --- a/AxonIvyPortal/portal/cms/cms_es.yaml +++ b/AxonIvyPortal/portal/cms/cms_es.yaml @@ -202,6 +202,7 @@ ch.ivy.addon.portalkit.ui.jsf: ApplicationName: El nombre por defecto de la aplicación que se mostrará en el título de la página. BaseQRCodeUrl: Establezca la URL base para mostrar el código QR. DefaultThemeMode: El modo de tema por defecto de la aplicación + EnableDocumentPreview: 'Establecer en "true" para habilitar la vista previa de documentos para ciertos tipos de archivos. Los tipos de archivos compatibles son: pdf, png, jpeg, jpg, txt, log.' EnableSwitchThemeModeButton: Establecer en true para habilitar el botón de cambio de tema. GlobalSearchScopeCategoriesNote: Definición de los tipos que buscará la búsqueda global GooglePlayURL: Establece la URL para descargar la aplicación móvil Axon Ivy en Google Play. @@ -877,6 +878,7 @@ ch.ivy.addon.portalkit.ui.jsf: yourProcesses: Sus procesos yourTasks: Sus tareas documentFiles: + PreviewDocument: Vista previa deleteDocumentNote: '{0} ha eliminado {1}' deleteSuceed: Archivo eliminado con éxito download: Descargar diff --git a/AxonIvyPortal/portal/cms/cms_fr.yaml b/AxonIvyPortal/portal/cms/cms_fr.yaml index c343a2a9a7..b0170a7b82 100644 --- a/AxonIvyPortal/portal/cms/cms_fr.yaml +++ b/AxonIvyPortal/portal/cms/cms_fr.yaml @@ -202,6 +202,7 @@ ch.ivy.addon.portalkit.ui.jsf: ApplicationName: Le nom de l'application par défaut qui sera affiché dans le titre de la page. BaseQRCodeUrl: Définir l'URL de base du code QR à afficher. DefaultThemeMode: Le mode de thème par défaut de l'application + EnableDocumentPreview: 'Définir sur "true" pour activer l''aperçu des documents pour certains types de fichiers. Les types de fichiers pris en charge sont : pdf, png, jpeg, jpg, txt, log.' EnableSwitchThemeModeButton: Défini à true pour activer le bouton de changement de thème. GlobalSearchScopeCategoriesNote: Définition des types recherchés par la recherche globale GooglePlayURL: Définir l'URL pour télécharger l'application mobile Axon Ivy sur Google Play. @@ -875,6 +876,7 @@ ch.ivy.addon.portalkit.ui.jsf: yourProcesses: Vos processus yourTasks: Vos tâches documentFiles: + PreviewDocument: Aperçu deleteDocumentNote: '{0} a supprimé(e) {1}' deleteSuceed: Le fichier a été supprimé avec succès download: Télécharger diff --git a/AxonIvyPortal/portal/config/variables.yaml b/AxonIvyPortal/portal/config/variables.yaml index c7242e6ff9..a039b3d3ae 100644 --- a/AxonIvyPortal/portal/config/variables.yaml +++ b/AxonIvyPortal/portal/config/variables.yaml @@ -179,6 +179,9 @@ Variables: # If just allow some extensions, list out extensions here, separated by comma. Example: pdf, txt, doc, docx. WhitelistExtension: doc, docx, xls, xlsx, xlsm, csv, pdf, ppt, pptx, txt, zip, jpg, jpeg, bmp, png + # Set to true to enable document preview for some certain file types. Supported file types are: pdf, png, txt, log. + EnablePreview: false + DateTimeFormat: # Set to true to hide hours and minutes next to date in the datetime format. # This setting affects in Tasks, Cases and Statistic results. diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/CaseTaskDocumentBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/CaseTaskDocumentBean.java index 4d83493001..7b826ae252 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/CaseTaskDocumentBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/CaseTaskDocumentBean.java @@ -6,8 +6,11 @@ import javax.faces.bean.ManagedBean; +import org.apache.commons.lang3.StringUtils; import org.primefaces.model.SortMeta; +import com.axonivy.portal.components.ivydata.bo.IvyDocument; + import ch.ivy.addon.portal.generic.navigation.PortalNavigator; import ch.ivy.addon.portalkit.enums.GlobalVariable; import ch.ivy.addon.portalkit.service.GlobalSettingService; @@ -80,4 +83,13 @@ public String getCaseDocumentsLink(ICase iCase) { public SortMeta getDocumentSortByCreationTimestamp() { return SortFieldUtil.buildSortMeta("creation.timestamp", true); } + + public boolean canPreviewDocument(IvyDocument document) { + boolean enablePreviewSetting = GlobalSettingService.getInstance().findBooleanGlobalSettingValue(GlobalVariable.ENABLE_DOCUMENT_PREVIEW); + if (document != null && StringUtils.startsWithIgnoreCase(document.getContentType(), "image/")) { + return enablePreviewSetting; + } + boolean isSupportedPreviewType = document != null && StringUtils.endsWithAny(document.getPath().toLowerCase(), ".pdf", ".txt", ".log"); + return enablePreviewSetting && isSupportedPreviewType; + } } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/GlobalVariable.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/GlobalVariable.java index 421a6216db..0c528648f2 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/GlobalVariable.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/GlobalVariable.java @@ -81,7 +81,8 @@ public enum GlobalVariable { "appleStoreURL"), GOOGLE_PLAY_URL("Portal.UserMenu.GooglePlayURL", GlobalVariableType.TEXT, "googlePlayURL"), APPLICATION_NAME("Portal.ApplicationName", GlobalVariableType.TEXT, "Axon Ivy", "ApplicationName"), HIDE_CASE_CREATOR( - "Portal.Cases.HideCaseCreator", GlobalVariableType.SELECTION, Option.FALSE.toString(), "hideCaseCreator"); + "Portal.Cases.HideCaseCreator", GlobalVariableType.SELECTION, Option.FALSE.toString(), "hideCaseCreator"), + ENABLE_DOCUMENT_PREVIEW("Portal.Document.EnablePreview", GlobalVariableType.SELECTION, Option.TRUE.toString(), "enableDocumentPreview"); diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/TaskDocuments/TaskDocuments.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/TaskDocuments/TaskDocuments.xhtml index b4c4e1da39..f3d47ad96e 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/TaskDocuments/TaskDocuments.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/TaskDocuments/TaskDocuments.xhtml @@ -45,7 +45,7 @@ styleClass="u-truncate-text ui-sm-12" width="65%" filterBy="#{document.name}" filterMatchMode="contains"> - + + styleClass="case-document-name-text u-truncate-text"> + + + + + +
+ + +
+
diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentData.d.json b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentData.d.json index 8acef9945b..c675038803 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentData.d.json +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentData.d.json @@ -37,5 +37,9 @@ "name" : "deleteDocumentMessage", "type" : "String", "modifiers" : [ "PERSISTENT" ] + }, { + "name" : "isImage", + "type" : "Boolean", + "modifiers" : [ "PERSISTENT" ] } ] } \ No newline at end of file diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentProcess.p.json b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentProcess.p.json index 90dc2f4606..264a1e72e9 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentProcess.p.json +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/CaseItemDocument/CaseItemDocumentProcess.p.json @@ -116,7 +116,11 @@ ], "map" : { "out.document" : "param.document" - } + }, + "code" : [ + "import org.apache.commons.lang3.StringUtils;", + "out.isImage = param.document!= null && StringUtils.startsWithIgnoreCase(param.document.getContentType(), \"image/\");" + ] }, "result" : { "params" : [ diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocuments.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocuments.xhtml index 724ab83c41..cc8172d6f7 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocuments.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocuments.xhtml @@ -55,13 +55,22 @@ sortBy="#{document.name}" styleClass=""> - + + + + @@ -155,7 +164,16 @@ update="#{p:resolveFirstComponentWithId('upload-messages', view).clientId} @this" /> - + + +
+ + +
+
diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocumentsData.d.json b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocumentsData.d.json index e5f0cc7786..630c2a7e6d 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocumentsData.d.json +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDocuments/TaskItemDocumentsData.d.json @@ -47,5 +47,9 @@ "name" : "deleteDocumentMessage", "type" : "String", "modifiers" : [ "PERSISTENT" ] + }, { + "name" : "isImage", + "type" : "Boolean", + "modifiers" : [ "PERSISTENT" ] } ] } \ No newline at end of file diff --git a/AxonIvyPortal/portal/webContent/resources/css/module.css b/AxonIvyPortal/portal/webContent/resources/css/module.css index 62b286ae00..dccf8910a3 100644 --- a/AxonIvyPortal/portal/webContent/resources/css/module.css +++ b/AxonIvyPortal/portal/webContent/resources/css/module.css @@ -424,6 +424,14 @@ span.case-details-document-add-link, span.task-details-document-add-link { top: 2px; right: 4px; } +.document-preview{ + position:absolute; + top:2px; + right:30px; +} +.document-preview-image{ + object-fit:contain; +} /********************************** CASE ITEM SIMILAR TO TASK ITEM CLASSES **********************************/ .case-widget-container .case-widget-top-header { position: relative;