fix process list page and add process page

This commit is contained in:
AnisHADJARAB 2024-10-30 10:34:02 +00:00
parent 29f09e92b7
commit d7da806989
7 changed files with 324 additions and 115 deletions

View File

@ -715,3 +715,61 @@ select[data-multi-select-plugin] {
max-height: 130px; max-height: 130px;
overflow-y: auto; overflow-y: auto;
} }
/**************************************** Process page card ******************************************************/
.process-card {
min-width: 300px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-height: 40vh;
max-height: 60vh;
justify-content: space-between;
padding: 1rem;
overflow-y: auto;
}
.process-card-content {
text-align: left;
font-size: .8em;
position: relative;
left: 2vw;
width: 90%;
.process-title {
font-weight: bold;
padding: 1rem 0;
}
.process-element {
padding: .4rem 0;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
&.selected {
background-color: rgba(26, 28, 24, .08);
}
}
.selected-process-zone {
background-color: rgba(26, 28, 24, .08);
}
}
.process-card-description {
padding: 20px;
font-size: 1em;
color: #333;
width: 90%;
}
.process-card-action {
width: 100%;
}

View File

@ -0,0 +1,35 @@
<div class="nav-wrapper">
<div></div>
<div class="brand-logo">4NK</div>
<div class="nav-right-icons">
<div class="notification-container">
<div class="bell-icon">
<svg class="notification-bell" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416H424c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6C399.5 322.9 384 278.8 384 233.4V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3C98.1 328 112 281.3 112 233.4V208c0-61.9 50.1-112 112-112zm64 352H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7s18.7-28.3 18.7-45.3z"/></svg>
</div>
<div class="notification-badge">1</div>
</div>
<div class="burger-menu">
<svg class="burger-menu" onclick="toggleMenu()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/>
</svg>
<div class="menu-content" id="menu">
<a href="#">Revoke</a>
<a href="#">Export</a>
<a href="#">Import</a>
<a href="#">Disconnect</a>
</div>
</div>
</div>
</div>
<div class="title-container">
<h1>Process {{processTitle}}</h1>
</div>
<div class="process-container">
</div>

View File

@ -0,0 +1,55 @@
import Services from "../../services/service"
let currentPageStyle: HTMLStyleElement | null = null;
export async function initProcessElement(id: string, zone: string) {
console.log("🚀 ~ init ~ id:", id)
const processes = await getProcesses()
const currentProcess = processes.find(process => process[0] === id)[1]
console.log("🚀 ~ init ~ currentProcess:", currentProcess)
const test = await loadPage({processTitle: currentProcess.title, inputValue: 'Hello World !'})
console.log("🚀 ~ initProcessElement ~ test:", currentProcess.html)
const wrapper = document.querySelector('.process-container')
if(wrapper) {
wrapper.innerHTML = interpolate(currentProcess.html, {processTitle: currentProcess.title, inputValue: 'Hello World !'})
injectCss(currentProcess.css)
}
}
function interpolate(template: string, data: { [key: string]: string }) {
return template.replace(/{{(.*?)}}/g, (_, key) => data[key.trim()]);
}
async function loadPage(data?: any) {
const content = document.getElementById('containerId');
if (content && data) {
if (data) {
content.innerHTML = interpolate(content.innerHTML, data);
}
}
}
function injectCss(cssContent: string) {
removeCss(); // Ensure that the previous CSS is removed
currentPageStyle = document.createElement('style');
currentPageStyle.type = 'text/css';
currentPageStyle.appendChild(document.createTextNode(cssContent));
document.head.appendChild(currentPageStyle);
}
function removeCss() {
if (currentPageStyle) {
document.head.removeChild(currentPageStyle);
currentPageStyle = null;
}
}
async function getProcesses(): Promise<any[]> {
const service = await Services.getInstance();
const processes = await service.getProcesses()
console.log("🚀 ~ Services ~ getProcesses ~ processes:", processes)
return processes
}

View File

@ -31,19 +31,19 @@
</div> </div>
<div class="process-container"> <div class="process-container">
<div class="card"> <div class="process-card">
<div class="card-description"> <div class="process-card-description">
<div class="input-container"> <div class="input-container">
<select multiple data-multi-select-plugin id="autocomplete" placeholder="Filter processes..." class="select-field"> <select multiple data-multi-select-plugin id="autocomplete" placeholder="Filter processes..." class="select-field">
</select> </select>
<label for="autocomplete" class="input-label">Filter processes :</label> <label for="autocomplete" class="input-label">Filter processes :</label>
<div class="selected-processes"></div> <div class="selected-processes"></div>
</div> </div>
<div class="card-content"> <div class="process-card-content">
</div> </div>
</div> </div>
<div class="card-action"> <div class="process-card-action">
<a class="btn">OK</a> <a class="btn" onclick="goToProcessPage()">OK</a>
</div> </div>
</div> </div>

View File

@ -1,3 +1,4 @@
import { navigate } from '../../router';
import Services from '../../services/service'; import Services from '../../services/service';
import { IProcess } from '~/models/process.model'; import { IProcess } from '~/models/process.model';
function toggleMenu() { function toggleMenu() {
@ -10,7 +11,8 @@ function toggleMenu() {
} }
// Initialize function, create initial tokens with itens that are already selected by the user // Initialize function, create initial tokens with itens that are already selected by the user
function init(element: HTMLElement) { export async function init() {
const element = document.querySelector('select') as HTMLSelectElement;
// Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside // Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.addEventListener('click', clickOnWrapper); wrapper.addEventListener('click', clickOnWrapper);
@ -38,6 +40,14 @@ function init(element: HTMLElement) {
search_div.appendChild(autocomplete_list); search_div.appendChild(autocomplete_list);
search_div.appendChild(dropdown_icon); search_div.appendChild(dropdown_icon);
const processes = await getProcesses();
for(let process of processes) {
const processName = process[1].title;
const opt = new Option(processName)
opt.value = processName
element.add(opt)
}
// set the wrapper as child (instead of the element) // set the wrapper as child (instead of the element)
element.parentNode?.replaceChild(wrapper, element); element.parentNode?.replaceChild(wrapper, element);
// set element as child of wrapper // set element as child of wrapper
@ -127,7 +137,6 @@ function createToken(wrapper: HTMLElement, value: any) {
close.setAttribute('tabindex', '-1'); close.setAttribute('tabindex', '-1');
close.setAttribute('data-option', value); close.setAttribute('data-option', value);
close.setAttribute('data-hits', '0'); close.setAttribute('data-hits', '0');
close.setAttribute('href', '#');
close.innerText = 'x'; close.innerText = 'x';
close.addEventListener('click', removeToken); close.addEventListener('click', removeToken);
token.appendChild(token_span); token.appendChild(token_span);
@ -144,6 +153,7 @@ function clickDropdown(e: Event) {
dropdown.classList.toggle('active'); dropdown.classList.toggle('active');
if (dropdown.classList.contains('active')) { if (dropdown.classList.contains('active')) {
removePlaceholder(wrapper as HTMLElement); removePlaceholder(wrapper as HTMLElement);
input_search?.focus(); input_search?.focus();
@ -169,6 +179,7 @@ function clearAutocompleteList(select: HTMLSelectElement) {
// Populate the autocomplete list following a given query from the user // Populate the autocomplete list following a given query from the user
function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) {
const { autocomplete_options } = getOptions(select); const { autocomplete_options } = getOptions(select);
console.log("🚀 ~ populateAutocompleteList ~ autocomplete_options:", autocomplete_options)
let options_to_show; let options_to_show;
@ -209,17 +220,19 @@ function populateAutocompleteList(select: HTMLSelectElement, query: string, drop
// Listener to autocomplete results when clicked set the selected property in the select option // Listener to autocomplete results when clicked set the selected property in the select option
function selectOption(e: any) { function selectOption(e: any) {
console.log("🚀 ~ selectOption ~ e:", e)
const wrapper = e.target.parentNode.parentNode.parentNode; const wrapper = e.target.parentNode.parentNode.parentNode;
const input_search = wrapper.querySelector('.selected-input'); const input_search = wrapper.querySelector('.selected-input');
const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`); const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`);
console.log("🚀 ~ selectOption ~ option:", option)
option.setAttribute('selected', ''); option.setAttribute('selected', '');
createToken(wrapper, e.target.dataset.value); createToken(wrapper, e.target.dataset.value);
if (input_search.value) { if (input_search.value) {
input_search.value = ''; input_search.value = '';
} }
// showSelectedProcess(e.target.dataset.value); showSelectedProcess(e.target.dataset.value);
input_search.focus(); input_search.focus();
@ -331,14 +344,6 @@ function deletePressed(e: KeyboardEvent) {
// opt.innerHTML = text; // opt.innerHTML = text;
// select.appendChild(opt); // select.appendChild(opt);
// } // }
// get select that has the options available
const select = document.querySelectorAll('[data-multi-select-plugin]') as NodeListOf<HTMLElement>;
select.forEach((select) => {
console.log(select);
init(select);
});
// Dismiss on outside click // Dismiss on outside click
document.addEventListener('click', () => { document.addEventListener('click', () => {
// get select that has the options available // get select that has the options available
@ -367,29 +372,31 @@ export async function unpair() {
(window as any).unpair = unpair; (window as any).unpair = unpair;
async function showSelectedProcess(event: MouseEvent) { async function showSelectedProcess(elem: MouseEvent) {
const elem = event.target;
if (elem) { if (elem) {
const cardContent = document.querySelector('.card-content'); const cardContent = document.querySelector('.process-card-content');
const processes = await getProcesses(); const processes = await getProcesses();
console.log('🚀 ~ Services ~ showSelectedProcess ~ processes:', processes); const process = processes.find((process: any) => process[1].title === elem);
const process = processes.find((process: any) => process.name === (elem as any).dataset.value);
if (process) { if (process) {
const processDiv = document.createElement('div'); const processDiv = document.createElement('div');
processDiv.className = 'process'; processDiv.className = 'process';
processDiv.id = process.name; processDiv.id = process[0];
const titleDiv = document.createElement('div'); const titleDiv = document.createElement('div');
titleDiv.className = 'process-title'; titleDiv.className = 'process-title';
titleDiv.innerHTML = `${process.name} : ${process.description}`; titleDiv.innerHTML = `${process[1].title} : ${process[1].description}`;
processDiv.appendChild(titleDiv); processDiv.appendChild(titleDiv);
for (const zone of process.zoneList) { for (const zone of process[1].zones) {
const zoneElement = document.createElement('div'); const zoneElement = document.createElement('div');
zoneElement.className = 'process-element'; zoneElement.className = 'process-element';
zoneElement.setAttribute('zone-id', zone.id.toString()); const zoneId = process[1].title + '-' + zone.id;
zoneElement.innerHTML = `Zone ${zone.id} : ${zone.name}`; zoneElement.setAttribute('zone-id', zoneId);
zoneElement.setAttribute('process-title', process[1].title);
zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`);
zoneElement.innerHTML = `${zone.title}: ${zone.description}`;
const service = await Services.getInstance(); const service = await Services.getInstance();
service.addSubscription(zoneElement, 'click', 'goToProcessPage'); // service.addSubscription(zoneElement, 'click', 'goToProcessPage');
zoneElement.addEventListener('click', select)
processDiv.appendChild(zoneElement); processDiv.appendChild(zoneElement);
} }
if (cardContent) cardContent.appendChild(processDiv); if (cardContent) cardContent.appendChild(processDiv);
@ -397,65 +404,34 @@ async function showSelectedProcess(event: MouseEvent) {
} }
} }
function goToProcessPage(event: MouseEvent) { function select(event: MouseEvent) {
const target = event.target as HTMLDivElement; const target = event.target as HTMLElement;
const zoneId = target?.getAttribute('zone-id'); const oldSelectedProcess = document.querySelector('.selected-process-zone')
const processList = document.querySelectorAll('.process-element'); oldSelectedProcess?.classList.remove('selected-process-zone')
if (processList) { if(target) {
for (const process of processList) { target.classList.add('selected-process-zone')
process.classList.remove('selected');
} }
} const name = target.getAttribute('zone-id')
target.classList.add('selected'); console.log("🚀 ~ select ~ name:", name)
console.log('=======================> going to process page', zoneId);
} }
function goToProcessPage() {
const target = document.querySelector('.selected-process-zone');
console.log("🚀 ~ goToProcessPage ~ event:", target)
if(target) {
const process = target?.getAttribute('process-id');
console.log('=======================> going to process page', process);
navigate('process-element/' + process)
}
}
(window as any).goToProcessPage = goToProcessPage
async function getProcesses(): Promise<any[]> { async function getProcesses(): Promise<any[]> {
const service = await Services.getInstance(); const service = await Services.getInstance();
// const processes = service.getProcesses() const processes = await service.getProcesses()
// console.log("🚀 ~ Services ~ getProcesses ~ processes:", processes) console.log("🚀 ~ Services ~ getProcesses ~ processes:", processes)
const process = [
{ return processes
title: 'Messaging',
description: 'Messagerie chiffrée',
html: '<div><input /></div>',
css: '',
script: '',
zones: ['zone 1', 'zone 2'],
roles: {
owner: {
members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'],
min_sig_member: 1.0,
},
],
},
},
},
{
title: 'Database',
description: 'Database chiffrée',
html: '<div><input /></div>',
css: '',
script: '',
zones: ['zone 1', 'zone 2'],
roles: {
owner: {
members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'],
min_sig_member: 1.0,
},
],
},
},
},
];
return process
} }

View File

@ -1,22 +1,32 @@
import '../public/style/4nk.css'; import '../public/style/4nk.css';
import { initHomePage } from './pages/home/home';
import Services from './services/service'; import Services from './services/service';
const routes: { [key: string]: string } = { const routes: { [key: string]: string } = {
home: '/src/pages/home/home.html', home: '/src/pages/home/home.html',
process: '/src/pages/process/process.html', process: '/src/pages/process/process.html',
'process-element': '/src/pages/process-element/process-element.html',
}; };
export let currentRoute = ''
export async function navigate(path: string) { export async function navigate(path: string) {
path = path.replace(/^\//, ''); path = path.replace(/^\//, '');
if (!routes[path]) { if(path.includes('/')) {
const parsedPath = path.split('/')[0]
if (!routes[parsedPath]) {
path = 'home'; path = 'home';
} }
}
await handleLocation(path); await handleLocation(path);
} }
async function handleLocation(path: string) { async function handleLocation(path: string) {
console.log("🚀 ~ handleLocation ~ path:", path)
const parsedPath = path.split('/')
if(path.includes('/')) {
path = parsedPath[0]
}
const routeHtml = routes[path] || routes['home']; const routeHtml = routes[path] || routes['home'];
const content = document.getElementById('containerId'); const content = document.getElementById('containerId');
@ -30,9 +40,17 @@ async function handleLocation(path: string) {
const { initHomePage } = await import('./pages/home/home'); const { initHomePage } = await import('./pages/home/home');
initHomePage(); initHomePage();
} else if (path === 'process') { } else if (path === 'process') {
await import('./pages/process/process'); const { init } = await import('./pages/process/process');
init()
} else if(path.includes('process-element')) {
const { initProcessElement } = await import('./pages/process-element/process-element');
if(parsedPath && parsedPath.length) {
const parseProcess = parsedPath[1].split('_')
initProcessElement(parseProcess[0], parseProcess[1])
} }
} }
currentRoute = path
}
} }
window.onpopstate = () => handleLocation('home'); window.onpopstate = () => handleLocation('home');
@ -53,20 +71,20 @@ async function init(): Promise<boolean> {
await services.restoreProcesses(); await services.restoreProcesses();
await services.restoreMessages(); await services.restoreMessages();
if (services.isPaired()) { // if (services.isPaired()) {
isPaired = true; // isPaired = true;
console.log('🚀 ~ setTimeout ~ isPaired:', isPaired); // console.log('🚀 ~ setTimeout ~ isPaired:', isPaired);
await navigate('process'); // await navigate('process');
return isPaired; // return isPaired;
} else { // } else {
const queryString = window.location.search; // const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString); // const urlParams = new URLSearchParams(queryString);
const pairingAddress = urlParams.get('sp_address'); // const pairingAddress = urlParams.get('sp_address');
if (pairingAddress) { // if (pairingAddress) {
setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000); // setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000);
} // }
return isPaired; // return isPaired;
} // }
}, 200); }, 200);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -78,5 +96,5 @@ async function init(): Promise<boolean> {
(async () => { (async () => {
const isPaired = await init(); const isPaired = await init();
console.log('🚀 ~ handleLocation ~ isPaired:', isPaired); console.log('🚀 ~ handleLocation ~ isPaired:', isPaired);
await navigate('home'); await navigate('process');
})(); })();

View File

@ -1,15 +1,12 @@
// import { WebSocketClient } from '../websockets'; // import { WebSocketClient } from '../websockets';
import { INotification } from '~/models/notification.model'; import { INotification } from '~/models/notification.model';
import homePage from '../pages/home/home.html?raw';
import homeScript from '../pages/home/home.ts?raw';
import processPage from '../pages/process/process.html?raw';
import processScript from '../pages/process/process.js?raw';
import { IProcess } from '~/models/process.model'; import { IProcess } from '~/models/process.model';
// import Database from './database'; // import Database from './database';
import { WebSocketClient } from '../websockets'; import { WebSocketClient } from '../websockets';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import { ApiReturn } from '../../dist/pkg/sdk_client'; import { ApiReturn } from '../../dist/pkg/sdk_client';
import Routing from './routing.service'; import Routing from './routing.service';
import { currentRoute } from '../router';
type ProcessesCache = { type ProcessesCache = {
[key: string]: any; [key: string]: any;
@ -511,7 +508,7 @@ export default class Services {
addSubscription(element: Element, event: string, eventHandler: string): void { addSubscription(element: Element, event: string, eventHandler: string): void {
this.subscriptions.push({ element, event, eventHandler }); this.subscriptions.push({ element, event, eventHandler });
element.addEventListener(event, (this as any)[eventHandler].bind(this)); element.addEventListener(event, (this as any)[eventHandler]);
} }
async createNewDevice() { async createNewDevice() {
@ -525,10 +522,12 @@ export default class Services {
console.error('Services ~ Error:', e); console.error('Services ~ Error:', e);
} }
if(currentRoute === 'home') {
this.generateQRCode(spAddress || ''); this.generateQRCode(spAddress || '');
//Adress to Emoji integration //Adress to Emoji integration
this.displayEmojis(spAddress); this.displayEmojis(spAddress);
//Adress to Emoji integration //Adress to Emoji integration
}
return spAddress; return spAddress;
} }
@ -536,15 +535,82 @@ export default class Services {
try { try {
await this.sdkClient.restore_device(device); await this.sdkClient.restore_device(device);
const spAddress = this.sdkClient.get_address(); const spAddress = this.sdkClient.get_address();
if(currentRoute === 'home') {
this.generateQRCode(spAddress || ''); this.generateQRCode(spAddress || '');
//Adress to Emoji integration //Adress to Emoji integration
this.displayEmojis(spAddress); this.displayEmojis(spAddress);
//Adress to Emoji integration //Adress to Emoji integration
}
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
async getProcesses(): Promise<any[]> {
const process = [
['1',{
title: 'Messaging',
description: 'Messagerie chiffrée',
html: '<div><input /></div>',
css: '',
script: '',
zones: [{
id: '1',
title: 'zone 1',
description: 'zone 1'
},
{
id: '2',
title: 'zone 2',
description: 'zone 2'
}],
roles: {
owner: {
members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'],
min_sig_member: 1.0,
},
],
},
},
}],
['2',{
title: 'Database',
description: 'Database chiffrée',
html: '<div><input value="{{inputValue}}" /></div>',
css: '',
script: '',
zones: [{
id: '1',
title: 'zone 1',
description: 'zone 1'
},
{
id: '2',
title: 'zone 2',
description: 'zone 2'
}],
roles: {
owner: {
members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'],
min_sig_member: 1.0,
},
],
},
},
}],
];
return process
}
private getProcessesCache(): ProcessesCache { private getProcessesCache(): ProcessesCache {
// Regular expression to match 64-character hexadecimal strings // Regular expression to match 64-character hexadecimal strings
const hexU32KeyRegex: RegExp = /^[0-9a-fA-F]{64}:\d+$/; const hexU32KeyRegex: RegExp = /^[0-9a-fA-F]{64}:\d+$/;
@ -643,6 +709,7 @@ export default class Services {
} }
public async setProcessesInSelectElement(processList: any[]) { public async setProcessesInSelectElement(processList: any[]) {
console.log("🚀 ~ Services ~ setProcessesInSelectElement ~ processList:", processList)
const select = document.querySelector('.select-field'); const select = document.querySelector('.select-field');
if (select) { if (select) {
for (const process of processList) { for (const process of processList) {