Skip to content

Commit

Permalink
Merge branch 'main' into fix/fe/FSADT1-1086
Browse files Browse the repository at this point in the history
  • Loading branch information
mamartinezmejia authored Dec 29, 2023
2 parents 1018ed1 + 2e265f4 commit 7ef2e50
Show file tree
Hide file tree
Showing 17 changed files with 377 additions and 61 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/merge-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ jobs:
sonar_token: ${{ secrets.SONAR_TOKEN_FRONTEND }}
triggers: ('frontend/')

- uses: actions/upload-artifact@v4
name: Upload Cypress Screenshots with error
if: failure()
with:
name: cypress-screenshots
path: frontend/cypress/screenshots
retention-days: 7

codeql:
name: Semantic Code Analysis
runs-on: ubuntu-22.04
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ jobs:
delete-old-comments: true
github-token: ${{ github.token }}
lcov-file: ./frontend/reports/e2e/lcov.info

- uses: actions/upload-artifact@v4
name: Upload Cypress Screenshots with error
if: failure()
with:
name: cypress-screenshots
path: frontend/cypress/screenshots
retention-days: 7

trivy:
name: Repository Report
Expand Down
6 changes: 6 additions & 0 deletions frontend/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export default defineConfig({
},
setupNodeEvents: (on, config) => {
require('@cypress/code-coverage/task')(on, config)
on('task', {
log: message => {
console.log(message);
return null;
},
});
return config
}
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/img/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<script src="/data/config.js"></script>
<title>Forest Client</title>
</head>
Expand Down
47 changes: 37 additions & 10 deletions frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
--cds-support-error: #e72000;

--light-theme-background-background-selected: #93939533;

//Layout
--header-height: 3.5rem;
}

*,
Expand Down Expand Up @@ -321,7 +324,7 @@ div#app {
align-items: center;
background: var(--light-theme-layer-layer-02, #fff);
flex-grow: 1;
margin-top: 3.5rem;
margin-top: var(--header-height);
}

.full {
Expand All @@ -338,7 +341,7 @@ div#app {
align-items: stretch;
background: var(--light-theme-background-background, #fff);
flex-grow: 1;
margin-top: 3.5rem;
margin-top: var(--header-height);
}

.headers {
Expand All @@ -356,7 +359,7 @@ div#app {
}

.headers cds-header {
height: 3.5rem;
height: var(--header-height);
}

.headers a img {
Expand Down Expand Up @@ -532,7 +535,7 @@ cds-actionable-notification * {

.content {
flex-grow: 1;
align-self: flex-start;
align-self: stretch;
display: flex;
flex-direction: column;
gap: 2rem;
Expand Down Expand Up @@ -586,6 +589,7 @@ cds-actionable-notification * {
display: flex;
flex-direction: column;
gap: 2.5rem;
max-width: 50.4375rem;
}

.form-steps-01 {
Expand Down Expand Up @@ -629,6 +633,7 @@ cds-actionable-notification * {
align-items: flex-start;
align-self: stretch;
gap: 1.5rem;
max-width: 50.4375rem;
}

.form-steps-section-01 {
Expand All @@ -649,21 +654,23 @@ cds-actionable-notification * {
.form-footer {
align-self: stretch;
display: flex;
align-items: flex-start;
align-items: stretch;
flex-direction: column;
gap: 1rem;
}

.form-footer-group {
display: flex;
width: 34.75rem;
align-items: flex-start;
align-items: stretch;
flex-direction: column;
gap: 1rem;
}

.form-footer-group-next {
display: flex;
flex-direction: column;
align-items: flex-start;
align-items: stretch;
align-self: stretch;
gap: 1rem;
}

Expand Down Expand Up @@ -1270,6 +1277,21 @@ cds-header-panel[expanded] {
margin-bottom: 1rem;
}

/*
Useful for scrolling to the *start* of an HTML element without having it covered under the page's header.
*/
.header-offset {
position: relative;
top: calc(-1 * var(--header-height));
}

.hide-when-less-than-two-children {
display: none;
}
.hide-when-less-than-two-children:has(:nth-child(2)) {
display: unset;
}

/* Small (up to 671px) */
@media screen and (max-width: 671px) {
.wizard-head-toast {
Expand Down Expand Up @@ -1344,12 +1366,10 @@ cds-header-panel[expanded] {

.form-steps {
align-self: stretch;
width: 18rem;
}

.form-steps-01 {
align-self: stretch;
width: 18rem;
}

.form-steps-01-title {
Expand All @@ -1360,6 +1380,7 @@ cds-header-panel[expanded] {
.form-footer-group-buttons {
flex-direction: column-reverse;
justify-content: flex-end;
align-items: stretch;
}

.full-centered {
Expand Down Expand Up @@ -1451,6 +1472,12 @@ cds-header-panel[expanded] {
.card {
padding: 1.5rem;
}

cds-tooltip:has(cds-button) {
display: flex;
flex-direction: column;
align-self: stretch;
}
}

/* Medium (from 672px to 1055px) */
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/grouping/AddressGroupComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const emit = defineEmits<{
}>();
const generalErrorBus = useEventBus<string>("general-error-notification");
const { setFocusedComponent } = useFocus();
const { safeSetFocusedComponent } = useFocus();
const noValidation = (value: string) => "";
Expand Down Expand Up @@ -232,8 +232,8 @@ watch([detailsData], () => {
});
onMounted(() => {
if (props.id == 0) setFocusedComponent(`addr_${props.id}`, 800);
else setFocusedComponent(`name_${props.id}`, 200);
if (props.id == 0) safeSetFocusedComponent(`addr_${props.id}`, 800);
else safeSetFocusedComponent(`name_${props.id}`, 200);
});
</script>

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/grouping/ContactGroupComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const emit = defineEmits<{
(e: "remove", value: number): void;
}>();
const { setFocusedComponent } = useFocus();
const { safeSetFocusedComponent } = useFocus();
const noValidation = (value: string) => "";
//We set it as a separated ref due to props not being updatable
Expand Down Expand Up @@ -93,9 +93,9 @@ const nameTypesToCodeDescr = (
onMounted(() => {
if (props.id === 0) {
setFocusedComponent(`phoneNumber_${props.id}`, 800);
safeSetFocusedComponent(`phoneNumber_${props.id}`, 800);
} else {
setFocusedComponent(`firstName_${props.id}`, 800);
safeSetFocusedComponent(`firstName_${props.id}`, 800);
}
});
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/composables/useElementVisibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useIntersectionObserver } from "@vueuse/core";
import { ref } from "vue";
import type { Ref } from "vue";

const isClient = typeof window !== 'undefined' && typeof document !== 'undefined';
const defaultWindow = isClient ? window : void 0;

// Based on @vueuse/core
export default function useElementVisibility(element, options = {}) {
const { window = defaultWindow, scrollTarget, threshold } = options;
const elementIsVisible = ref<boolean>(undefined);

let done: () => void;
const elementIsVisibleRefPromise = new Promise<Ref<boolean>>((resolve) => {
done = () => {
resolve(elementIsVisible);
};
});
const { stop } = useIntersectionObserver(
element,
(intersectionObserverEntries) => {
let isIntersecting = elementIsVisible.value

// Get the latest value of isIntersecting based on the entry time
let latestTime = 0;
for (const entry of intersectionObserverEntries) {
if (entry.time >= latestTime) {
latestTime = entry.time;
isIntersecting = entry.isIntersecting;
}
}
elementIsVisible.value = isIntersecting;

// signalize the value is ready
done();
},
{
root: scrollTarget,
window,
threshold,
}
);
return { elementIsVisibleRefPromise, stop };
}
62 changes: 56 additions & 6 deletions frontend/src/composables/useFocus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ref } from 'vue'
import type { Ref } from 'vue'
import { isTouchScreen } from "./useScreenSize";
import useElementVisibility from "./useElementVisibility";

type OptionalElement = Element | HTMLElement | undefined

Expand All @@ -12,12 +14,16 @@ export const useFocus = (): {
componentName: string,
time?: number,
callback?: (refComponent: Ref<OptionalElement>) => void,
) => Ref<OptionalElement>
) => Ref<OptionalElement>;
safeSetFocusedComponent: (
componentName: string,
time?: number,
) => Ref<OptionalElement>;
setScrollPoint: (
componentName: string,
time?: number,
callback?: (refComponent: Ref<OptionalElement>) => void,
) => Ref<OptionalElement>
) => Ref<OptionalElement>;
} => {
// setActionOn is a function that execute the action on an a component
const setActionOn = (
Expand Down Expand Up @@ -46,16 +52,24 @@ export const useFocus = (): {
): Ref<OptionalElement> => {
const refComponent = ref<OptionalElement>(undefined)
setTimeout(() => {
if (!document) {
// Prevent error on CI
return;
}
refComponent.value = setActionOn(componentName, key, action)
if (callback) {
// We need to detect the end of the scrolling
// Use event 'scroll' if browser does not support 'scrollend'
const eventName = 'onscrollend' in window ? 'scrollend' : 'scroll'

const scrollEndListener = () => {
callback(refComponent)
document.removeEventListener(eventName, polyfilledListener)
}
callback(refComponent);
if (!document) {
// Prevent error on CI
return;
}
document.removeEventListener(eventName, polyfilledListener);
};

let scrollEndTimer: NodeJS.Timeout

Expand Down Expand Up @@ -86,6 +100,42 @@ export const useFocus = (): {
time,
callback,
)

/**
* Set the focus on a component with the data-focus attribute as long as:
* - the component is already visible (100%);
* - and the device is not a touch device (because we want to prevent the touch device's
* virtual keyboard from either covering the component or triggering automatic scroll).
*
* Otherwise, does nothing.
*/
const safeSetFocusedComponent = (
componentName: string,
time: number = 100,
): Ref<OptionalElement> => {
return execute(
componentName,
"data-focus",
async (element) => {
if (!(element instanceof HTMLElement) || isTouchScreen.value) {
return;
}

const { elementIsVisibleRefPromise, stop } = useElementVisibility(element, {
threshold: 1,
});

const elementIsVisibleRef = await elementIsVisibleRefPromise;
stop();
if (!elementIsVisibleRef.value) {
return;
}
element.focus();
},
time,
);
};

// Scroll into view a component with the data-scroll attribute
const setScrollPoint = (
componentName: string,
Expand All @@ -100,5 +150,5 @@ export const useFocus = (): {
callback,
)

return { setFocusedComponent, setScrollPoint }
return { safeSetFocusedComponent, setFocusedComponent, setScrollPoint };
}
7 changes: 4 additions & 3 deletions frontend/src/composables/useScreenSize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMediaQuery } from '@vueuse/core';
import { useMediaQuery } from "@vueuse/core";

export const isSmallScreen = useMediaQuery('(max-width: 671px)')
export const isMediumScreen = useMediaQuery('(min-width: 672px) and (max-width: 1055px)')
export const isSmallScreen = useMediaQuery("(max-width: 671px)");
export const isMediumScreen = useMediaQuery("(min-width: 672px) and (max-width: 1055px)");
export const isTouchScreen = useMediaQuery("(hover: none)");
Loading

0 comments on commit 7ef2e50

Please sign in to comment.