diff --git a/README.md b/README.md index 80d4d75..0cd55ff 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ cd timeline-lwc } ], "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "59.0" + "sourceApiVersion": "61.0" } ``` @@ -159,11 +159,12 @@ The component has the following properties that can be set at design time in App | `Parent Record` | Adjusts the timeline parent id | Dynamic Picklist (valid relationships) | | `Title` | Adjusts the label | String | | `Height` | Adjusts the vertical height | Picklist (1 - 5) | +| `Icon Style` | Adjusts the iconography style | Picklist (Square, Circular) | | `Historical Time Range (Years)` | Adjusts the start date | Picklist (0.25 - 10) | | `Future Time Range (Years)` | Adjusts the end date | Picklist (0.25 - 10) | | `Zoom Based On` | Adjusts the position of the zoom | Picklist (First Date, Current Date, Last Activity) | | `Zoom Range (Days)` | Adjusts the extent of the zoom | Integer min 15 max 365 | -| `Show Today` | Plots a line for the current date/time | Picklist (No/Various Colours) | +| `Show Today` | Plots a line for the current date/time | Picklist (No/Various Colours) | #### Custom Labels diff --git a/changelog.md b/changelog.md index 033aaa9..c6fd20a 100644 --- a/changelog.md +++ b/changelog.md @@ -3,10 +3,14 @@ ## XX Xxx 2024 v1.15.0 **WHAT'S NEW** -- Changed Salesforce API to v60.0 from v59.0 +- Changed Salesforce API to v61.0 from v59.0 +- Added EmailMessage drilldown support for ActivityHistory and OpenActivities +- Added UI support for SLDS v2 (Enhanced Lightning) +- Added support for circular iconography to align with SLDS v2 **BUG FIXES** -- Fixed memory leak issue +- Fixed issue plotting tasks for Person Accounts +- Fixed font and font size mismatches with SLDS v1 and v2 ## 30 Jan 2024 v1.14.0 diff --git a/force-app/main/default/classes/TimelineParentPicklist.cls-meta.xml b/force-app/main/default/classes/TimelineParentPicklist.cls-meta.xml index b1a915c..651b172 100644 --- a/force-app/main/default/classes/TimelineParentPicklist.cls-meta.xml +++ b/force-app/main/default/classes/TimelineParentPicklist.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 61.0 Active diff --git a/force-app/main/default/classes/TimelineParentPicklist_Test.cls-meta.xml b/force-app/main/default/classes/TimelineParentPicklist_Test.cls-meta.xml index b1a915c..651b172 100644 --- a/force-app/main/default/classes/TimelineParentPicklist_Test.cls-meta.xml +++ b/force-app/main/default/classes/TimelineParentPicklist_Test.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 61.0 Active diff --git a/force-app/main/default/classes/TimelineService.cls b/force-app/main/default/classes/TimelineService.cls index 35f3506..2eb715b 100644 --- a/force-app/main/default/classes/TimelineService.cls +++ b/force-app/main/default/classes/TimelineService.cls @@ -128,6 +128,7 @@ public with sharing class TimelineService { 'Parent_Object__c =:parentConfigType'; //NOPMD List listOfTimelineConfigurations = Database.query(queryTimelineConfiguration); //NOPMD + Map timelineConfigObjectName = new Map ([SELECT Id, Object_Name__c FROM Timeline_Configuration__mdt]); if (listofTimelineConfigurations.size() < 1) { String errorMsg = @@ -141,6 +142,9 @@ public with sharing class TimelineService { for (Timeline_Configuration__mdt timelineConfigurationRecord : listOfTimelineConfigurations) { TimelineRecord timelineRecord = new timelineRecord(); + Schema.SObjectType timelineRecordObjectType = Schema.getGlobalDescribe().get(timelineConfigurationRecord.Object_Name__c); + Schema.DescribeSObjectResult timelineDescribeResult = timelineRecordObjectType.getDescribe(); + timelineRecord.objectLabel = timelineDescribeResult.getLabel(); timelineRecord.relationshipName = timelineConfigurationRecord.Relationship_Name__c; timelineRecord.icon = timelineConfigurationRecord.Icon__c; timelineRecord.iconBackground = timelineConfigurationRecord.Icon_Background_Colour__c; @@ -187,6 +191,7 @@ public with sharing class TimelineService { objName == 'ActivityHistory' || objName == 'TaskRelation' || objName == 'ContentDocumentLink' || + objName == 'TaskWhoRelation' || objName == 'LookedUpFromActivity') ) { selectStatement = selectStatement + ', ' + tcr.type + ''; @@ -199,6 +204,13 @@ public with sharing class TimelineService { selectStatement = selectStatement + ', ActivityDate' + ''; } + if ( + (objName == 'OpenActivity' || objName == 'ActivityHistory') && + !selectStatement.contains('AlternateDetailId') + ) { + selectStatement = selectStatement + ', AlternateDetailId' + ''; + } + if (objName == 'ContentDocumentLink') { selectStatement = selectStatement + ', ' + 'ContentDocumentId' + ''; } @@ -330,6 +342,16 @@ public with sharing class TimelineService { Map typeValues = getFieldValues(tr.type, eachCh, tr.objectName); + String alternateDetailId = ''; + + if ( (tr.objectName == 'OpenActivity' || tr.objectName == 'ActivityHistory') && + (String.valueOf(eachCh.get('AlternateDetailId')) != '' && + String.valueOf(eachCh.get('AlternateDetailId')) != null) ) { + alternateDetailId = String.valueOf(eachCh.get('AlternateDetailId')); + } + + mapData.put('alternateDetailId', alternateDetailId); + if (tr.objectName == 'ContentDocumentLink') { //NOPMD myId = String.valueOf(eachCh.get('ContentDocumentId')); @@ -350,6 +372,7 @@ public with sharing class TimelineService { mapData.put('positionDateValue', positionValues.get('value')); mapData.put('positionDateType', positionValues.get('type')); mapData.put('objectName', tr.objectName); + mapData.put('objectLabel', tr.objectLabel); mapData.put('fallbackTooltipField', fallbackValues.get('label')); mapData.put('fallbackTooltipValue', fallbackValues.get('value')); mapData.put('drilldownId', drilldownIdValues.get('value')); @@ -640,6 +663,7 @@ public with sharing class TimelineService { private String positionDateValue; private String positionDateType; private String objectName; + private String objectLabel; private String type; private String tooltipIdField; private String tooltipObject; @@ -649,6 +673,7 @@ public with sharing class TimelineService { private String fallbackNameField; private String fallbackNameValue; private String inclusionField; + private String alternateDetailId; private Id recordId; } diff --git a/force-app/main/default/classes/TimelineService.cls-meta.xml b/force-app/main/default/classes/TimelineService.cls-meta.xml index b1a915c..651b172 100644 --- a/force-app/main/default/classes/TimelineService.cls-meta.xml +++ b/force-app/main/default/classes/TimelineService.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 61.0 Active diff --git a/force-app/main/default/classes/TimelineService_Test.cls-meta.xml b/force-app/main/default/classes/TimelineService_Test.cls-meta.xml index b1a915c..651b172 100755 --- a/force-app/main/default/classes/TimelineService_Test.cls-meta.xml +++ b/force-app/main/default/classes/TimelineService_Test.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 61.0 Active diff --git a/force-app/main/default/customMetadata/Timeline_Configuration.Person_EventRelations.md-meta.xml b/force-app/main/default/customMetadata/Timeline_Configuration.Person_EventRelations.md-meta.xml index 8d0e68d..0f69e88 100644 --- a/force-app/main/default/customMetadata/Timeline_Configuration.Person_EventRelations.md-meta.xml +++ b/force-app/main/default/customMetadata/Timeline_Configuration.Person_EventRelations.md-meta.xml @@ -28,7 +28,7 @@ Object_Name__c - EventRelation + EventWhoRelation Parent_Object__c @@ -40,7 +40,7 @@ Relationship_Name__c - EventRelations + PersonEventWhoRelations Sequence__c diff --git a/force-app/main/default/customMetadata/Timeline_Configuration.Person_TaskRelations.md-meta.xml b/force-app/main/default/customMetadata/Timeline_Configuration.Person_TaskRelations.md-meta.xml index b48b1ac..7ed1561 100644 --- a/force-app/main/default/customMetadata/Timeline_Configuration.Person_TaskRelations.md-meta.xml +++ b/force-app/main/default/customMetadata/Timeline_Configuration.Person_TaskRelations.md-meta.xml @@ -28,7 +28,7 @@ Object_Name__c - TaskRelation + TaskWhoRelation Parent_Object__c @@ -40,7 +40,7 @@ Relationship_Name__c - TaskRelations + PersonTaskWhoRelations Sequence__c diff --git a/force-app/main/default/labels/CustomLabels.labels-meta.xml b/force-app/main/default/labels/CustomLabels.labels-meta.xml index 72a897d..73ad7bf 100644 --- a/force-app/main/default/labels/CustomLabels.labels-meta.xml +++ b/force-app/main/default/labels/CustomLabels.labels-meta.xml @@ -104,6 +104,14 @@ Timeline_Label_Filter_Type_Legend Types to Show + + Timeline_Label_Filter_Date_Legend + Timeline + en_US + true + Timeline_Label_Filter_Date_Legend + Dates in View + Timeline_Label_Filters Timeline diff --git a/force-app/main/default/lwc/illustration/illustration.js-meta.xml b/force-app/main/default/lwc/illustration/illustration.js-meta.xml index 861701c..8691883 100755 --- a/force-app/main/default/lwc/illustration/illustration.js-meta.xml +++ b/force-app/main/default/lwc/illustration/illustration.js-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 61.0 false diff --git a/force-app/main/default/lwc/timeline/timeline.css b/force-app/main/default/lwc/timeline/timeline.css index 3250d6b..3718cd2 100644 --- a/force-app/main/default/lwc/timeline/timeline.css +++ b/force-app/main/default/lwc/timeline/timeline.css @@ -1,15 +1,14 @@ .stencil-label { - background-color: #b0adab; + background-color: #83807f; height: 0.4rem; border-radius: 2rem; width: 10rem; } .stencil-value { - background-color: #969492; + background-color: #5f5e5d; height: 0.4rem; border-radius: 2rem; - margin-top: 0.5rem; width: 10rem; } @@ -24,6 +23,10 @@ text-align: center; } +.slds-spinner_container, .slds-spinner--container { + border-radius: 20px !important; +} + .illustration { padding: 10px 5px 5px 5px; border-radius: 5px 5px 5px 5px; @@ -214,7 +217,7 @@ } .timeline-wrapper { - margin: 10px 10px 10px 10px; + --margin: 5px 5px 5px 5px; border: 1px solid #747474; border-radius: 3px; position: relative; @@ -281,10 +284,12 @@ } .timeline-summary-verbose { + padding-right: 10px; display: none; } .timeline-summary-short { + padding-right: 10px; display: inline-block !important; } } @@ -304,5 +309,26 @@ } .timeline-summary-short { + padding-right: 10px; display: none; } + +.sr-only { + font-size: 0; +} + +.sticky-button { + position: sticky; + bottom: 0; + background-color: white; + padding: 5px; +} + +.slds-panel__body { + padding-bottom: 50px; +} + +.timeline-summary-verbose { + padding-right: 10px; + +} \ No newline at end of file diff --git a/force-app/main/default/lwc/timeline/timeline.html b/force-app/main/default/lwc/timeline/timeline.html index e4d5c40..cd130c1 100755 --- a/force-app/main/default/lwc/timeline/timeline.html +++ b/force-app/main/default/lwc/timeline/timeline.html @@ -6,7 +6,7 @@
- +
- + \ No newline at end of file diff --git a/force-app/main/default/lwc/timeline/timeline.js b/force-app/main/default/lwc/timeline/timeline.js index d2c71e3..918b6d1 100755 --- a/force-app/main/default/lwc/timeline/timeline.js +++ b/force-app/main/default/lwc/timeline/timeline.js @@ -26,6 +26,7 @@ import SHOWING from '@salesforce/label/c.Timeline_Label_Showing'; import ITEMS from '@salesforce/label/c.Timeline_Label_Items'; import FILTERS from '@salesforce/label/c.Timeline_Label_Filters'; import TYPE_LEGEND from '@salesforce/label/c.Timeline_Label_Filter_Type_Legend'; +import DATE_LEGEND from '@salesforce/label/c.Timeline_Label_Filter_Date_Legend'; import DATE_RANGE_LEGEND from '@salesforce/label/c.Timeline_Label_Date_Range_Legend'; import FILE_TYPE from '@salesforce/label/c.Timeline_Label_Files'; import ALL_TYPES from '@salesforce/label/c.Timeline_Label_Filter_All_Types'; @@ -40,6 +41,7 @@ export default class timeline extends NavigationMixin(LightningElement) { @api timelineParent; //parent field for the lwc set as design attribute @api timelineTitle; //title for the lwc set as design attribute @api preferredHeight; //height of the timeline set as design attribute + @api iconStyle; //the style of icon plotted in the canvas @api earliestRange; //How far back in time to go @api latestRange; //How far into the future to go @api zoomTo; //Zoom to current dat or latest activity @@ -112,12 +114,20 @@ export default class timeline extends NavigationMixin(LightningElement) { No: "#107cad" }; + iconStyleMap = { + Square: 3, + Circular: 20 + }; + + iconRoundedValue = 3; + label = { DAYS, SHOWING, ITEMS, FILTERS, TYPE_LEGEND, + DATE_LEGEND, DATE_RANGE_LEGEND, FILE_TYPE, ALL_TYPES, @@ -205,7 +215,6 @@ export default class timeline extends NavigationMixin(LightningElement) { this.allFilterValues.push(key); } } - this.isFilterLoaded = true; } else if (result.error) { let errorType = 'Error'; let errorHeading, @@ -245,6 +254,7 @@ export default class timeline extends NavigationMixin(LightningElement) { if (!this._d3Rendered) { this.todaysColour = this.todayColourMap[this.showToday]; + this.iconRoundedValue = this.iconStyleMap[this.iconStyle]; //set the height of the component as the height is dynamic based on the attributes let timelineDIV = this.template.querySelector('div.timeline-canvas'); this.currentParentField = this.timelineParent; @@ -446,9 +456,9 @@ export default class timeline extends NavigationMixin(LightningElement) { recordCopy.recordId = record.objectId; recordCopy.id = index; - recordCopy.label = - record.detailField.length <= 30 ? record.detailField : record.detailField.slice(0, 30) + '...'; + recordCopy.label = record.detailField; recordCopy.objectName = record.objectName; + recordCopy.objectLabel = record.objectLabel; recordCopy.positionDateField = record.positionDateField; if (record.positionDateType === 'DATE') { @@ -492,6 +502,7 @@ export default class timeline extends NavigationMixin(LightningElement) { recordCopy.tooltipId = record.tooltipId; recordCopy.tooltipObject = record.tooltipObject; recordCopy.drilldownId = record.drilldownId; + recordCopy.alternateDetailId = record.alternateDetailId; recordCopy.type = record.type; recordCopy.icon = record.icon; @@ -625,6 +636,18 @@ export default class timeline extends NavigationMixin(LightningElement) { timelineCanvas.attr('height', svgHeight - 1); timelineCanvas.SVGHeight = svgHeight; + if(me.isLanguageRightToLeft){ + timelineCanvas.append('clipPath') + .attr('id', 'clipText') + .append('polygon') + .attr('points','-215,0 0,0 0,30 -215,30'); + } + else{ + timelineCanvas.append('clipPath') + .attr('id', 'clipText') + .append('polygon') + .attr('points','0,0 215,0 215,30 0,30'); + } timelineCanvas.data = timelineCanvas .selectAll('[class~=timeline-canvas-record]') @@ -642,7 +665,6 @@ export default class timeline extends NavigationMixin(LightningElement) { .attr('transform', function (d) { return 'translate(' + timelineCanvas.x(d.time) + ', ' + timelineCanvas.y(d.swimlane) + ')'; }); - if (timelineCanvas.records.size() > 0) { timelineCanvas.records .append('rect') @@ -672,8 +694,8 @@ export default class timeline extends NavigationMixin(LightningElement) { .attr('y', 0) .attr('width', 24) .attr('height', 24) - .attr('rx', 3) - .attr('ry', 3); + .attr('rx', me.iconRoundedValue) + .attr('ry', me.iconRoundedValue); timelineCanvas.records .append('image') @@ -681,6 +703,10 @@ export default class timeline extends NavigationMixin(LightningElement) { .attr('y', 1) .attr('height', 22) .attr('width', 22) + .attr('aria-label', function (d){ + const altText = d.objectLabel; + return altText; + }) .attr('xlink:href', function (d) { let iconImage = ''; @@ -704,9 +730,17 @@ export default class timeline extends NavigationMixin(LightningElement) { return iconImage; }); + timelineCanvas.records + .append('text') + .attr('class', 'sr-only') + .text(function (d) { + return d.positionDateValue; + }); + timelineCanvas.records .append('text') .attr('class', 'timeline-canvas-record-label') + .attr('clip-path','url(#clipText)') .attr('x', function () { let x = 30; switch (me.isLanguageRightToLeft) { @@ -721,12 +755,17 @@ export default class timeline extends NavigationMixin(LightningElement) { }) .attr('y', 16) .attr('font-size', 12) + .attr('tabindex', '0') .on('click', function (event, d) { let drilldownId = d.recordId; if (d.drilldownId !== '') { drilldownId = d.drilldownId; } + if (d.alternateDetailId !== '') { + drilldownId = d.alternateDetailId; + } + switch (d.objectName) { case 'ContentDocumentLink': { me[NavigationMixin.Navigate]({ @@ -761,6 +800,49 @@ export default class timeline extends NavigationMixin(LightningElement) { } } }) + .on('keydown', function (event, d) { + if(event.key === ' ' || event.key === 'Enter'){ + + let drilldownId = d.recordId; + if (d.drilldownId !== '') { + drilldownId = d.drilldownId; + } + + switch (d.objectName) { + case 'ContentDocumentLink': { + me[NavigationMixin.Navigate]({ + type: 'standard__namedPage', + attributes: { + pageName: 'filePreview' + }, + state: { + selectedRecordId: d.recordId + } + }); + break; + } + case 'CaseComment': { + const toastEvent = new ShowToastEvent({ + title: me.toast.NAVIGATION_HEADER, + message: me.toast.NAVIGATION_BODY, + messageData: [d.objectName] + }); + this.dispatchEvent(toastEvent); + break; + } + default: { + me[NavigationMixin.Navigate]({ + type: 'standard__recordPage', + attributes: { + recordId: drilldownId, + actionName: 'view' + } + }); + break; + } + } + } + }) .on('mouseover', function (event, d) { let tooltipId = d.recordId; let tooltipObject = d.objectName; @@ -789,21 +871,44 @@ export default class timeline extends NavigationMixin(LightningElement) { switch (me.isLanguageRightToLeft) { case true: - tipPosition = - this.getBoundingClientRect().top - - 30 + - 'px ;left:' + - (this.getBoundingClientRect().left - tooltipDIV.offsetWidth - 15) + - 'px ;visibility:visible'; - break; + if(this.getBoundingClientRect().width < 184){ + tipPosition = + this.getBoundingClientRect().top - + 30 + + 'px ;left:' + + (this.getBoundingClientRect().left - tooltipDIV.offsetWidth - 15) + + 'px ;visibility:visible'; + break; + } + else{ + tipPosition = + this.getBoundingClientRect().top - + 30 + + 'px ;left:' + + (this.getBoundingClientRect().right- tooltipDIV.offsetWidth - 215) + + 'px ;visibility:visible'; + break; + } + default: - tipPosition = - this.getBoundingClientRect().top - - 30 + - 'px ;left:' + - (this.getBoundingClientRect().right + 15) + - 'px ;visibility:visible'; - break; + if(this.getBoundingClientRect().width < 184){ + tipPosition = + this.getBoundingClientRect().top - + 30 + + 'px ;left:' + + (this.getBoundingClientRect().right + 15) + + 'px ;visibility:visible'; + break; + } + else{ + tipPosition = + this.getBoundingClientRect().top - + 30 + + 'px ;left:' + + (this.getBoundingClientRect().left + 205) + + 'px ;visibility:visible'; + break; + } } tooltipDIV.setAttribute('style', 'top:' + tipPosition); }) @@ -845,6 +950,8 @@ export default class timeline extends NavigationMixin(LightningElement) { const axis = targetSVG .insert('g', ':first-child') .attr('class', axisConfig.class + '-' + me.timelineWidth) + .attr('role', 'presentation') + .attr('aria-hidden', 'true') .call(x_axis); if (typeof axisConfig.translate === 'object') { @@ -1256,15 +1363,19 @@ export default class timeline extends NavigationMixin(LightningElement) { const filterPopover = this.template.querySelector('div.timeline-filter'); const filterClasses = String(filterPopover.classList); const refreshButton = this.template.querySelector('lightning-button-icon.timeline-refresh'); + const closeButton = this.template.querySelector('[data-id="closeDialogBtn"]'); + const filterButton = this.template.querySelector('[data-id="filterBtn"]'); if (filterClasses.includes('slds-is-open')) { refreshButton.disabled = false; filterPopover.classList.remove('slds-is-open'); this.isFilter = false; + filterButton.focus(); } else { refreshButton.disabled = true; filterPopover.classList.add('slds-is-open'); this.isFilter = true; + closeButton.focus(); } switch (this.isLanguageRightToLeft) { @@ -1299,6 +1410,16 @@ export default class timeline extends NavigationMixin(LightningElement) { } } + handleStartDateChange(e){ + this.zoomStartDateString = e.target.value; + this.isFilterUpdated = true; + } + + handleEndDateChange(e){ + this.zoomEndDateString = e.target.value; + this.isFilterUpdated = true; + } + handleAllTypesChange(e) { if (e.target.checked === true) { this.filterValues = this.allFilterValues; @@ -1334,6 +1455,20 @@ export default class timeline extends NavigationMixin(LightningElement) { } applyFilter() { + console.log('applyFilter start'); + console.log('this.filterValues: '+this.filterValues); + if (this.zoomStartDateString) { + this.zoomStartDate = new Date(this.zoomStartDateString); // Convert string to Date object + console.log('Start Date updated to:', this.zoomStartDate); + } else { + console.log('Please enter a valid startdate.'); + } + if (this.zoomEndDateString) { + this.zoomEndDate = new Date(this.zoomEndDateString); // Convert string to Date object + console.log('End Date updated to:', this.zoomEndDate); + } else { + console.log('Please enter a valid end date.'); + } this.refreshTimeline(); this.isFilterUpdated = false; this.startingFilterValues = this.filterValues; diff --git a/force-app/main/default/lwc/timeline/timeline.js-meta.xml b/force-app/main/default/lwc/timeline/timeline.js-meta.xml index c80b452..ed29e59 100755 --- a/force-app/main/default/lwc/timeline/timeline.js-meta.xml +++ b/force-app/main/default/lwc/timeline/timeline.js-meta.xml @@ -1,6 +1,6 @@ - 59.0 + 61.0 true Time Warp @@ -14,6 +14,7 @@ + @@ -30,6 +31,7 @@ + diff --git a/images/appBuilderDemo.gif b/images/appBuilderDemo.gif index 25566bc..7e22b37 100644 Binary files a/images/appBuilderDemo.gif and b/images/appBuilderDemo.gif differ diff --git a/images/heroDemo.gif b/images/heroDemo.gif index f436976..9930c70 100644 Binary files a/images/heroDemo.gif and b/images/heroDemo.gif differ