Skip to content

Commit

Permalink
[18.0][IMP] sign_oca: Add guided arrow flow to sign_oca
Browse files Browse the repository at this point in the history
  • Loading branch information
kobros-tech committed Feb 3, 2025
1 parent 9074efa commit bd5a9ad
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 0 deletions.
6 changes: 6 additions & 0 deletions sign_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.xml",
"sign_oca/static/src/elements/elements.xml",
"sign_oca/static/src/scss/sign_oca.scss",
# kobros
"sign_oca/static/src/components/sign_oca_pdf_common/sign_oca_navigator.esm.js",
#
"sign_oca/static/src/components/sign_oca_pdf_common/sign_oca_pdf_common.esm.js",
"sign_oca/static/src/components/sign_oca_configure/sign_oca_configure_field_dialog.esm.js",
"sign_oca/static/src/components/sign_oca_configure/sign_oca_configure_field_dialog.xml",
Expand Down Expand Up @@ -79,6 +82,9 @@
"sign_oca/static/src/components/sign_oca_pdf_portal/sign_oca_pdf_portal.xml",
"sign_oca/static/src/elements/elements.xml",
"sign_oca/static/src/scss/sign_oca.scss",
# kobros
"sign_oca/static/src/components/sign_oca_pdf_common/sign_oca_navigator.esm.js",
#
"sign_oca/static/src/components/sign_oca_pdf_common/sign_oca_pdf_common.esm.js",
"sign_oca/static/src/elements/text.esm.js",
"sign_oca/static/src/elements/signature.esm.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/** @odoo-module QWeb **/
/* global document, window, console */
import {_t} from "@web/core/l10n/translation";

export function offset(el) {
const box = el.getBoundingClientRect();
const docElem = document.documentElement;
return {
top: box.top + window.scrollY - docElem.clientTop,
left: box.left + window.scrollY - docElem.clientLeft,
};
}

/**
* Starts the sign item navigator
* @param { SignablePDFIframe } parent
* @param { HTMLElement } target
* @param { Environment } env
*/
export function startSignItemNavigator(parent, target, env) {
const state = {
started: false,
isScrolling: false,
};
const checkSignItemsCompletion = parent.checkSignItemsCompletion();
let signItemsToComplete = checkSignItemsCompletion;

const navigator = document.createElement("div");
navigator.classList.add("o_sign_sign_item_navigator");
const navLine = document.createElement("div");
navLine.classList.add("o_sign_sign_item_navline");

function _scrollToSignItemPromise(item) {
return new Promise((resolve) => {
if (env.isSmall) {
state.isScrolling = true;
item.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center",
});
return resolve();
}

state.isScrolling = true;
const viewer = parent.iframe.el.contentDocument.getElementById("viewer");

// Recalculate container height each time
const containerHeight = target.offsetHeight;
const viewerHeight = viewer.offsetHeight;

let scrollOffset = containerHeight / 4;

// Get the latest scroll position
const updatedScrollTop =
offset(item).top - offset(viewer).top - scrollOffset;

// Adjust for overscroll cases
if (updatedScrollTop + containerHeight > viewerHeight) {
scrollOffset += updatedScrollTop + containerHeight - viewerHeight;
}
if (updatedScrollTop < 0) {
scrollOffset += updatedScrollTop;
}

// Ensure navigator updates properly
scrollOffset +=
offset(target).top -
navigator.offsetHeight / 2 +
item.getBoundingClientRect().height / 2;

// Dynamic animation duration
const duration = Math.max(
Math.min(
500,
5 *
(Math.abs(target.scrollTop - updatedScrollTop) +
Math.abs(navigator.getBoundingClientRect().top) -
scrollOffset)
),
100
);

// Perform scroll
target.scrollTo({top: updatedScrollTop, behavior: "smooth"});

// Update navigator animation
const an = navigator.animate(
{top: `${scrollOffset}px`},
{duration, fill: "forwards"}
);
const an2 = navLine.animate(
{top: `${scrollOffset}px`},
{duration, fill: "forwards"}
);

Promise.all([an.finished, an2.finished]).then(() => {
resolve();
});
});
}

function setTip(text) {
navigator.style.fontFamily = "Helvetica";
navigator.innerText = text;
}

/**
* Sets the entire radio set on focus.
* @param {Number} radio_set_id
*/
function highligtRadioSet(radio_set_id) {
parent
.checkSignItemsCompletion()
.filter((item) => item.data.radio_set_id === radio_set_id)
.forEach((item) => {
item.el.classList.add("ui-selected");
});
}

function scrollToSignItem({el: item, data}) {
_scrollToSignItemPromise(item).then(() => {
// Define input to deal with input fields if present
const input = item.querySelector("input");
if (input && input.type === "text") {
item.value = item.querySelector("input").value;
item.focus = () => item.querySelector("input").focus();
}
// Maybe store signature in data rather than in the dataset
if (item.value === "" && !item.dataset.signature) {
setTip(_t("next"));
}
if (data.type === "radio") {
// We need to highligt the entire radio set items
highligtRadioSet(data.radio_set_id);
} else {
item.focus();
item.classList.add("ui-selected");
}

if (input && ["signature", "initial"].includes(input.type)) {
if (item.dataset.hasFocus) {
const clickableElement = data.isSignItemEditable
? item.querySelector(".o_sign_item_display")
: item;
clickableElement.click();
} else {
item.dataset.hasFocus = true;
}
}
state.isScrolling = false;
});
}

function goToNextSignItem() {
if (!state.started) {
state.started = true;
goToNextSignItem();
return false;
}
const selectedElements = target.querySelectorAll(".ui-selected");
selectedElements.forEach((selectedElement) => {
selectedElement.classList.remove("ui-selected");
});
if (signItemsToComplete.length > 0) {
console.log(
"nbPages",
parent.iframe.el.contentDocument.getElementsByClassName("page").length
);
scrollToSignItem(signItemsToComplete[0]);
}
}

target.append(navigator);
navigator.before(navLine);
navigator.addEventListener("click", () => {
if (checkSignItemsCompletion.length > 0) {
goToNextSignItem();
signItemsToComplete = signItemsToComplete.slice(1);
}
});

setTip(_t("Click to start"));
navigator.focus();

function toggle(force) {
navigator.style.display = force ? "" : "none";
navLine.style.display = force ? "" : "none";
}

return {
setTip,
goToNextSignItem,
toggle,
state,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Component, onMounted, onWillStart, onWillUnmount, useRef} from "@odoo/ow
import {AlertDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
import {renderToString} from "@web/core/utils/render";
import {useService} from "@web/core/utils/hooks";
import {startSignItemNavigator} from "./sign_oca_navigator.esm";

export default class SignOcaPdfCommon extends Component {
setup() {
Expand Down Expand Up @@ -104,6 +105,9 @@ export default class SignOcaPdfCommon extends Component {
"sign_oca_ready"
);
this.iframeLoaded.resolve();
// Kobros
this.navigate();
//
}
postIframeField(item) {
if (this.items[item.id]) {
Expand All @@ -122,6 +126,27 @@ export default class SignOcaPdfCommon extends Component {
this.items[item.id] = signatureItem[0];
return signatureItem;
}
// CheckSignItemsCompletion and navigate functions for handling navigation
checkSignItemsCompletion() {
const signItemsToComplete = [];
$.each(this.info.items, (key, value) => {
const $element = $(value);
const signItemToComplete = {};
signItemToComplete.data = $element[0];
signItemToComplete.el = this.postIframeField(value)[0];
signItemsToComplete.push(signItemToComplete);
});
return signItemsToComplete;
}
navigate() {
const target = this.iframe.el.contentDocument.getElementById("viewerContainer");
this.navigator = startSignItemNavigator(this, target, this.env);
target.addEventListener("scroll", () => {
if (!this.navigator.state.isScrolling && this.navigator.state.started) {
this.navigator.setTip(_t("next"));
}
});
}
}
SignOcaPdfCommon.template = "sign_oca.SignOcaPdfCommon";
SignOcaPdfCommon.props = [];
88 changes: 88 additions & 0 deletions sign_oca/static/src/scss/sign.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,91 @@
background-color: rgba(0, 128, 128, 0.4);
}
}

// kobros

:root {
--gray-200-rgb: 231, 233, 237;
--gray-300-rgb: 216, 218, 221;
// EE
--bs-primary-rgb: 113, 75, 103;
// CE
--bs-primary-rgb: 113, 99, 158;
--bs-secondary-rgb: var(--gray-200-rgb);
--bs-danger-rgb: 212, 76, 89;
--bs-dark-rgb: 17, 24, 39;
--bs-white-rgb: 255, 255, 255;
--bs-body-color-rgb: 55, 65, 81;
--bs-body-bg-rgb: 249, 250, 251;
--btn-font-weight: 500;
--btn-font-size: 0.875rem;
--btn-line-height: 1.5;
--border-radius: 0.25rem;
}

.o_sign_sign_item_navigator {
position: fixed;
top: 15%;
left: 0;
line-height: 50px;
height: 50px;
font-size: 1.4em;
text-transform: uppercase;
z-index: 100;
padding: 0 10px 0 5px;
color: white;
cursor: pointer;
background-color: rgba(var(--bs-primary-rgb), 1);
}

.o_sign_sign_item_navigator:after {
content: "";
position: absolute;
margin-left: 10px;
width: 0px;
height: 1px;
border-top: 24px solid transparent;
border-bottom: 25px solid transparent;
border-left: 25px solid rgba(var(--bs-primary-rgb), 1);
}

@media (max-width: 767px) {
/* @screen-xs-max */
.o_sign_sign_item_navigator {
width: 100%;
top: initial !important;
bottom: 0;
z-index: 9999;
line-height: 25px;
height: 35px;
padding: 5px 0 0;
font-size: var(--btn-font-size);
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
text-align: center;
}
.o_sign_sign_item_navline {
display: none !important;
}
}

.o_sign_sign_item_navline {
position: fixed;
top: 15%;
left: 1%;

pointer-events: none;
z-index: 80;

width: 99%;
height: 25px;
border-bottom: 1px dashed silver;
opacity: 0.5;
}

@media (max-width: 767px) {
/* @screen-xs-max */
.o_sign_sign_item_navline {
line-height: 12.5px;
height: 12.5px;
}
}

0 comments on commit bd5a9ad

Please sign in to comment.