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 9, 2025
1 parent 9074efa commit 633eb06
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 0 deletions.
1 change: 1 addition & 0 deletions sign_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"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",
"sign_oca/static/src/components/sign_oca_pdf_portal/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
Expand Up @@ -122,6 +122,18 @@ 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;
}
}
SignOcaPdfCommon.template = "sign_oca.SignOcaPdfCommon";
SignOcaPdfCommon.props = [];
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/** @odoo-module QWeb **/
/* global document, window, Event, KeyboardEvent */
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;
}

function scrollToSignItem({el: item}) {
_scrollToSignItemPromise(item).then(() => {
// Define input to deal with input fields if present
const input = item.querySelector("input");
if (input) {
if (input.type === "text") {
input.focus();
} else if (input.type === "checkbox") {
input.focus();
// Additional auto check and launch its events for faster filling
input.checked = true;
input.dispatchEvent(new Event("change", {bubbles: true}));
input.dispatchEvent(
new KeyboardEvent("keydown", {
key: " ", // Space key to simulate manual checking
code: "Space",
keyCode: 32,
bubbles: true,
})
);
} else {
input.focus();
}
}
// Field can be signature div or anything else
else if (item.dataset.field) {
const clickableElement =
item.firstChild && item.querySelector("div")
? item.firstChild
: item;
clickableElement.click();
} else {
item.focus();
}
item.classList.add("ui-selected");
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) {
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 @@ -8,6 +8,7 @@ import SignOcaPdf from "../sign_oca_pdf/sign_oca_pdf.esm.js";
import {getTemplate} from "@web/core/templates";
import {MainComponentsContainer} from "@web/core/main_components_container";
import {rpc} from "@web/core/network/rpc";
import {startSignItemNavigator} from "./sign_oca_navigator.esm";

export class SignOcaPdfPortal extends SignOcaPdf {
setup() {
Expand Down Expand Up @@ -36,6 +37,8 @@ export class SignOcaPdfPortal extends SignOcaPdf {
postIframeFields() {
super.postIframeFields(...arguments);
this.checkFilledAll();
// Load navigator
this.navigate();
}
async _onClickSign(ev) {
ev.target.disabled = true;
Expand All @@ -55,6 +58,15 @@ export class SignOcaPdfPortal extends SignOcaPdf {
}
});
}
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"));
}
});
}
}
SignOcaPdfPortal.template = "sign_oca.SignOcaPdfPortal";
SignOcaPdfPortal.props = {
Expand Down
94 changes: 94 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,97 @@
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;
}
}

.ui-selected,
.ui-selecting {
/* jQuery UI */
box-shadow: 0px 0px 5px 1px orange;
}

0 comments on commit 633eb06

Please sign in to comment.