1422 lines
62 KiB
HTML
1422 lines
62 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>FLO LogSheet</title>
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&family=Roboto:wght@400;500;700&display=swap"
|
|
rel="stylesheet">
|
|
<link rel="stylesheet" href="css/main.css">
|
|
<script id="floGlobals">
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
|
const floGlobals = {
|
|
|
|
//Required for all
|
|
blockchain: "FLO",
|
|
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf", //"FRaBr5F665RVkQ1A1EYrMfbX2UF52vWjKr",
|
|
application: "TEST_MODE", // "LogSheet",
|
|
// for storing signle log sheet data
|
|
currentSheet: {}
|
|
}
|
|
</script>
|
|
<script src="scripts/lib.js"></script>
|
|
<script src="scripts/floCrypto.js"></script>
|
|
<script src="scripts/floBlockchainAPI.js"></script>
|
|
<script src="scripts/compactIDB.js"></script>
|
|
<script src="scripts/floCloudAPI.js"></script>
|
|
<script src="scripts/floDapps.js"></script>
|
|
<script src="scripts/logsheet.js"></script>
|
|
<script id="onLoadStartUp">
|
|
function onLoadStartUp() {
|
|
//display loading screen
|
|
showLoader()
|
|
//document.getElementById("loading-screen").classList.remove("hide")
|
|
//clear Rendered Elements
|
|
/*clearElement(document.getElementById('person-list'))*/
|
|
clearElement(sheetsContainer)
|
|
//set the custom Privkey input
|
|
floDapps.setCustomPrivKeyInput(() => {
|
|
return new Promise((resolve, reject) => {
|
|
hideLoader()
|
|
showPage('sign_in_page')
|
|
let signInBtn = document.getElementById('sign_in_btn'),
|
|
//guestButton = document.getElementById('guest_btn'),
|
|
privateKeyInput = document.getElementById('get_priv_key_field')
|
|
signInBtn.onclick = () => {
|
|
hidePopup()
|
|
showLoader()
|
|
resolve(privateKeyInput.value)
|
|
privateKeyInput.value = ''
|
|
}
|
|
/*guestButton.onclick = () => {
|
|
hidePopup()
|
|
showLoader()
|
|
reject(null)
|
|
}*/
|
|
})
|
|
})
|
|
//invoke the startup functions
|
|
floDapps.launchStartUp().then(result => {
|
|
console.log(result)
|
|
document.getElementById("user_flo_id").textContent = myFloID
|
|
reactor.dispatchEvent("startUpSuccessLog", `Downloading objectData! Please Wait...`)
|
|
//request object data from Supernode
|
|
logSheet.init().then(result => {
|
|
console.log(result)
|
|
//Render the personList and sheetList
|
|
renderPersonList(logSheet.listPersons())
|
|
renderSheetList(logSheet.listSheets())
|
|
//hide loading screen
|
|
hideLoader()
|
|
showPage('home_page')
|
|
//display add buttons if subAdmin, else hide
|
|
if (floGlobals.subAdmins.includes(myFloID)) {
|
|
document.querySelectorAll('.sub-admin-option').forEach(option => option.classList.remove('hide-completely'))
|
|
} else {
|
|
document.querySelectorAll('.sub-admin-option').forEach(option => option.classList.add('hide-completely'))
|
|
}
|
|
}).catch(error => {
|
|
reactor.dispatchEvent("startUpErrorLog", `Failed to download objectData`)
|
|
notify(error, "error")
|
|
})
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body onload="onLoadStartUp()" class="hide-completely">
|
|
<audio id="notification_sound">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.mp3" type="audio/mpeg">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.ogg" type="audio/ogg">
|
|
</audio>
|
|
<sm-popup id="confirmation">
|
|
<p id="confirm_message"></p>
|
|
<div class="flex align-center">
|
|
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
|
|
<sm-button variant="no-outline" class="submit-btn">OK</button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="prompt">
|
|
<h4 id="prompt_message">Some input required</h4>
|
|
<sm-input id="prompt_field"></sm-input>
|
|
<div class="flex align-center">
|
|
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
|
|
<sm-button variant="no-outline" class="submit-btn">OK</button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-notifications id="show_message"></sm-notifications>
|
|
<!-- Loading screen-->
|
|
<section id="main_loader" class="grid">
|
|
<svg id="loader" viewBox="0 0 73 73">
|
|
<title>Loader</title>
|
|
<path d="M72.5,36.5c0,19.88-16.12,36-36,36s-36-16.12-36-36s16.12-36,36-36S72.5,16.62,72.5,36.5" />
|
|
</svg>
|
|
<h4 id="tip_container">Loading RanchiMall FLO LogSheet</h4>
|
|
<sm-button onclick="signOut()">Sign Out</sm-button>
|
|
</section>
|
|
|
|
<!-- User settings popup -->
|
|
<sm-popup id="user_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>
|
|
Settings
|
|
</h4>
|
|
</header>
|
|
<section>
|
|
<h4>My FLO address</h4>
|
|
<div class="copy-row">
|
|
<h4 id="user_flo_id" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this.parentNode)" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
<sm-button id="sign_out_btn" onclick="signOut()">Sign out</sm-button>
|
|
</section>
|
|
<section>
|
|
<h4>Theme</h4>
|
|
<div class="flex align-center space-between">
|
|
<p>Turn dark theme on/off.</p>
|
|
<sm-switch id="theme_switcher"></sm-switch>
|
|
</div>
|
|
</section>
|
|
</sm-popup>
|
|
|
|
<!-- Add a person popup -->
|
|
<sm-popup id="add_person_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close popup</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>
|
|
Add new person
|
|
</h4>
|
|
<button onclick="addPerson()" class="primary-btn expand" type="submit" disabled>
|
|
Add
|
|
</button>
|
|
</header>
|
|
<sm-input id="person_flo_id_input" floId placeholder="FLO ID*" animate required></sm-input>
|
|
<sm-input id="person_name_input" placeholder="Name*" animate required></sm-input>
|
|
<div id="specify_details" class="grid">
|
|
<div class="flex direction-column">
|
|
<h4>Additional details</h4>
|
|
<p>Add more details about a person. Specify type of information and actual detail.</p>
|
|
</div>
|
|
<div id="additional_fields" class="grid"></div>
|
|
<div id="add_detail" class="grid align-center">
|
|
<sm-input id="add_detail_type_input" exclude placeholder="Type"></sm-input>
|
|
<sm-input id="add_detail_value_input" exclude placeholder="Value"></sm-input>
|
|
<svg class="icon" onclick="addDetails()" viewBox="0 0 64 64">
|
|
<title>Add detail</title>
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</sm-popup>
|
|
<!-- Create new sheet popup -->
|
|
<sm-popup id="new_sheet_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close popup</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>
|
|
Create new sheet
|
|
</h4>
|
|
<button onclick="createSheet()" class="primary-btn expand" type="submit" disabled>
|
|
Create
|
|
</button>
|
|
</header>
|
|
<sm-input id="sheet_title_input" placeholder="Name*" animate required></sm-input>
|
|
<sm-textarea id="sheet_description_input" placeholder="Description" animate required></sm-textarea>
|
|
<div id="specify_columns" class="grid">
|
|
<div class="flex direction-column">
|
|
<h4>Add Columns</h4>
|
|
<p>Columns will be added as the order you added them.</p>
|
|
</div>
|
|
<div id="columns_container" class="flex"></div>
|
|
<div id="add_column" class="flex align-center space-between">
|
|
<sm-input id="add_column_input" exclude placeholder="Column title"></sm-input>
|
|
<svg class="icon" onclick="addColumn()" viewBox="0 0 64 64">
|
|
<title>Add this column</title>
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="flex align-center space-between top-margin">
|
|
Make sheet private
|
|
<sm-switch id="sheet_type_switch" onchange="toggleEditors()"></sm-switch>
|
|
</div>
|
|
<div id="specify_editors" class="grid hide-completely">
|
|
<div class="flex direction-column">
|
|
<h4>Add editors</h4>
|
|
<p>Only specified editors will be able to update this sheet.</p>
|
|
</div>
|
|
<div id="editors_container" class="grid"></div>
|
|
<div id="add_editor" class="flex align-center space-between">
|
|
<sm-input id="add_editor_input" exclude placeholder="Editor's FLO Address"></sm-input>
|
|
<svg class="icon" onclick="addEditor()" viewBox="0 0 64 64">
|
|
<title>Add this editor</title>
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</sm-popup>
|
|
|
|
<!-- Group by popup -->
|
|
<sm-popup id="group_by">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close popup</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4 id="group_by_title">
|
|
</h4>
|
|
</header>
|
|
<div class="flex align-center" id="group_by_menu">
|
|
<sm-select id="first_select" class="justify-right">
|
|
<sm-option value="count">Count</sm-option>
|
|
<sm-option value="total" selected>Total</sm-option>
|
|
<sm-option value="avg">Avg</sm-option>
|
|
<sm-option value="max">Max</sm-option>
|
|
<sm-option value="min">Min</sm-option>
|
|
</sm-select>
|
|
<sm-select id="second_select"></sm-select>
|
|
</div>
|
|
<div class="table-container" id="group_by_view"></div>
|
|
</sm-popup>
|
|
|
|
<div id="save_button" class="hide flex align-center space-between" data-unsaved="0">
|
|
<span id="changes_indicator"></span>
|
|
<span>Changes needs to be saved!</span>
|
|
<sm-button>Save</sm-button>
|
|
</div>
|
|
|
|
<main>
|
|
<div id="sign_in_page" class="page grid space-between">
|
|
<div class="info">
|
|
<h4>RanchiMall</h4>
|
|
<h1>LogSheet</h1>
|
|
<p>Open • Distributed • Reliable</p>
|
|
</div>
|
|
<div class="sign-in-box flex direction-column">
|
|
<sm-tab-header target="user_entry">
|
|
<sm-tab>Sign In</sm-tab>
|
|
<sm-tab>Sign Up</sm-tab>
|
|
</sm-tab-header>
|
|
<sm-tab-panels id="user_entry">
|
|
<sm-panel>
|
|
<h3>Welcome back</h3>
|
|
<p>Just enter your FLO private key to continue.</p>
|
|
<form action="" onsubmit="return false">
|
|
<sm-input id="get_priv_key_field" privateKey placeholder="FLO Private Key" type="password"
|
|
required animate>
|
|
</sm-input>
|
|
<button id="sign_in_btn" class="primary-btn expand" type="submit" disabled>
|
|
Sign In
|
|
</button>
|
|
</form>
|
|
</sm-panel>
|
|
<sm-panel>
|
|
<h3>Get started</h3>
|
|
<p>Create your FLO public and private key pair. <Strong>Don't forget to store them
|
|
securely!</Strong></p>
|
|
<sm-button id="generate_flo_id" onclick="generateId()" variant="primary">Get FLO credentials
|
|
</sm-button>
|
|
<section id="credentials_section" class="hide-completely">
|
|
<h5>FLO ID</h5>
|
|
<div class="copy-row">
|
|
<h4 id="generated_id" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this.parentNode)" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
<h5>Private key</h5>
|
|
<div class="copy-row">
|
|
<h4 id="generated_key" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this.parentNode)" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
</sm-panel>
|
|
</sm-tab-panels>
|
|
</div>
|
|
</div>
|
|
<header id="main_header" class="grid">
|
|
<div class="flex align-center space-between">
|
|
<div class="flex direction-column">
|
|
<h5>RanchiMall</h5>
|
|
<h4>FLO LogSheet</h4>
|
|
</div>
|
|
<svg id="user_icon" onclick="showPopup('user_popup')" class="icon" viewBox="0 0 64 64">
|
|
<title>user</title>
|
|
<path d="M31.6,0.5c8.3,0,14.2,5.6,14.2,15S41.1,32.9,32,32.9s-13.8-8-13.8-17.3s5.9-15,14.2-15" />
|
|
<path d="M25.4,33.4c-2.5,4.7-10.5,7.1-16.4,9.7c-2.4,1-4.4,14-1.6,14c7.7,4.4,16.4,6.6,25.2,6.4
|
|
c8.5,0.3,16.9-2,24.2-6.4c2.8,0,0.8-12.9-1.6-14c-5.9-2.6-13.9-5-16.4-9.7" />
|
|
</svg>
|
|
</div>
|
|
</header>
|
|
<section id="home_page" class="page grid hide-completely">
|
|
<section id="main_section">
|
|
<header class="flex align-center space-between section-header">
|
|
<h4>Sheets</h4>
|
|
<sm-input id="search_sheets" placeholder="Search sheets" type="search"></sm-input>
|
|
</header>
|
|
<section id="sheets_container" class="grid"></section>
|
|
</section>
|
|
</section>
|
|
<section id="sheet_page" class="page toggle-side-bar grid hide-completely">
|
|
<nav id="side_bar">
|
|
<div class="flex align-center">
|
|
<svg class="icon hide-on-desktop" onclick="toggleSideBar()" viewBox="0 0 64 64">
|
|
<title>Go back to sheet</title>
|
|
<polyline points="48.01 0.35 16.35 32 48.01 63.65" />
|
|
</svg>
|
|
<h4 class="section-header">People</h4>
|
|
<sm-button class="small sub-admin-option justify-right" onclick="showPopup('add_person_popup')">Add
|
|
person</sm-button>
|
|
</div>
|
|
<div id="people_container" class="grid"></div>
|
|
</nav>
|
|
<section id="right">
|
|
<section id="sheet_details" class="collapse">
|
|
<div class="flex align-center">
|
|
<svg class="icon" onclick="showPage('home_page')" id="go_to_home" viewBox="0 0 64 64">
|
|
<title>Go back to homepage</title>
|
|
<polyline points="48.01 0.35 16.35 32 48.01 63.65" />
|
|
</svg>
|
|
<h5>Sheets</h5>
|
|
<sm-button class="small justify-right" onclick="showPopup('group_by')">Group by</sm-button>
|
|
</div>
|
|
<div class="flex align-center">
|
|
<svg class="icon" onclick="toggleSideBar()" viewBox="0 0 64 64">
|
|
<title>Reveal/hide side bar</title>
|
|
<line y1="18.5" x2="64" y2="18.5" />
|
|
<line y1="45.5" x2="42" y2="45.5" />
|
|
</svg>
|
|
<h3 id="sheet_heading"></h3>
|
|
<h5 id="sheet_type"></h5>
|
|
<svg id="toggle_details" class="icon justify-right" onclick="toggleDetails()"
|
|
viewBox="0 0 64 64">
|
|
<title>expand/collapse</title>
|
|
<polyline points="63.65 15.99 32 47.66 0.35 15.99" />
|
|
</svg>
|
|
</div>
|
|
<div id="sheet_editors" class="flex align-center"></div>
|
|
<p id="sheet_description"></p>
|
|
</section>
|
|
<div id="sheet_container">
|
|
<table>
|
|
<thead>
|
|
<tr></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
<form id="new-log" style="display: none;" onsubmit="enterLog(event); return false;"></form>
|
|
<button type="submit" form="new-log" style="display: none;"></button>
|
|
</section>
|
|
</section>
|
|
</main>
|
|
<script src="components.js"></script>
|
|
<script>
|
|
let frag = document.createDocumentFragment(),
|
|
notificationSound = document.getElementById('notification_sound');
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
notify('There seems to be a problem connecting to the internet.', 'error', 'success', true)
|
|
window.addEventListener('offline', () => {
|
|
notify('There seems to be a problem connecting to the internet.', 'error', true, true)
|
|
})
|
|
window.addEventListener('online', () => {
|
|
notifications.clearAll()
|
|
notify('We are back online.', '', '', true)
|
|
})
|
|
|
|
let themeSwitcher = document.getElementById('theme_switcher')
|
|
|
|
if (localStorage.theme === "dark") {
|
|
nightlight()
|
|
themeSwitcher.checked = true;
|
|
} else {
|
|
daylight()
|
|
themeSwitcher.checked = false;
|
|
}
|
|
|
|
function daylight() {
|
|
document.body.setAttribute("data-theme", "light");
|
|
}
|
|
|
|
function nightlight() {
|
|
document.body.setAttribute("data-theme", "dark");
|
|
}
|
|
themeSwitcher.addEventListener('change', function (e) {
|
|
if (this.checked) {
|
|
nightlight();
|
|
localStorage.setItem("theme", "dark");
|
|
}
|
|
else {
|
|
daylight();
|
|
localStorage.setItem("theme", "light");
|
|
}
|
|
})
|
|
|
|
// function required for popups or modals to appear
|
|
class Stack {
|
|
constructor() {
|
|
this.items = [];
|
|
}
|
|
push(element) {
|
|
this.items.push(element);
|
|
}
|
|
pop() {
|
|
if (this.items.length == 0)
|
|
return "Underflow";
|
|
return this.items.pop();
|
|
}
|
|
peek() {
|
|
return this.items[this.items.length - 1];
|
|
}
|
|
}
|
|
let popupStack = new Stack(),
|
|
zIndex = 10;
|
|
|
|
function showPopup(popup, pinned) {
|
|
let thisPopup = document.getElementById(popup);
|
|
zIndex++
|
|
thisPopup.setAttribute('style', `z-index: ${zIndex}`)
|
|
popupStack = thisPopup.show(pinned, popupStack)
|
|
return thisPopup;
|
|
}
|
|
|
|
// hides the popup or modal
|
|
function hidePopup() {
|
|
if (popupStack.peek() === undefined)
|
|
return;
|
|
popupStack.peek().popup.hide()
|
|
}
|
|
|
|
document.addEventListener('popupclosed', e => {
|
|
let popup = e.detail.popup
|
|
switch (popup.id) {
|
|
case 'new_sheet_popup':
|
|
columnsContainer.innerHTML = ''
|
|
editorsContainer.innerHTML = ''
|
|
break;
|
|
}
|
|
})
|
|
|
|
const loaderPage = document.getElementById('main_loader'),
|
|
mainHeader = document.getElementById('main_header')
|
|
function showLoader() {
|
|
document.body.classList.remove('hide-completely')
|
|
loaderPage.classList.remove('hide-completely')
|
|
loader.classList.add('animate-loader')
|
|
document.querySelector('main').classList.add('hide-completely')
|
|
mainHeader.classList.add('hide-completely')
|
|
}
|
|
function hideLoader() {
|
|
loaderPage.classList.add('hide-completely')
|
|
loader.classList.remove('animate-loader')
|
|
document.querySelector('main').classList.remove('hide-completely')
|
|
mainHeader.classList.remove('hide-completely')
|
|
}
|
|
|
|
|
|
function setAttributes(el, attrs) {
|
|
for (var key in attrs) {
|
|
el.setAttribute(key, attrs[key]);
|
|
}
|
|
}
|
|
|
|
function randomHsl(saturation = 80, lightness = 80) {
|
|
let hue = Math.random() * 360
|
|
let color = {
|
|
primary: `hsla( ${hue}, ${saturation}%, ${lightness}%, 1)`,
|
|
light: `hsla( ${hue}, ${saturation}%, 90%, 0.6)`
|
|
}
|
|
return color;
|
|
}
|
|
|
|
let notifications = document.getElementById('show_message');
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, pinned, sound) {
|
|
if (mode === 'error')
|
|
console.error(message)
|
|
else if (mode === 'warn')
|
|
console.warn(message)
|
|
else
|
|
console.log(message)
|
|
notifications.push(message, mode, pinned)
|
|
if (navigator.onLine && sound) {
|
|
notificationSound.currentTime = 0;
|
|
notificationSound.play();
|
|
}
|
|
}
|
|
|
|
// displays a popup for asking permission. Use this instead of JS confirm
|
|
let getConfirmation = (message, cancelText = 'Cancel', confirmText = 'OK') => {
|
|
return new Promise(resolve => {
|
|
let popup = document.getElementById('confirmation');
|
|
showPopup('confirmation')
|
|
popup.querySelector('#confirm_message').textContent = message;
|
|
let submitButton = popup.querySelector('.submit-btn')
|
|
submitButton.textContent = confirmText
|
|
submitButton.onclick = () => {
|
|
popup.hide()
|
|
resolve(true);
|
|
}
|
|
let cancelButton = popup.querySelector('.cancel-btn')
|
|
cancelButton.textContent = cancelText
|
|
cancelButton.onclick = () => {
|
|
popup.hide()
|
|
reject(false);
|
|
}
|
|
})
|
|
}
|
|
|
|
// displays a popup for asking user input. Use this instead of JS prompt
|
|
let askPrompt = function (message, defaultVal) {
|
|
return new Promise(resolve => {
|
|
let popup = document.getElementById('prompt'),
|
|
input = popup.querySelector('sm-input');
|
|
if (defaultVal)
|
|
input.value = defaultVal;
|
|
showPopup('prompt')
|
|
input.focus()
|
|
input.addEventListener('keyup', e => {
|
|
if (e.key === 'Enter') {
|
|
resolve(input.value);
|
|
popup.hide()
|
|
}
|
|
})
|
|
popup.querySelector('#prompt_message').textContent = message;
|
|
popup.querySelector('.submit-btn').onclick = () => {
|
|
popup.hide()
|
|
resolve(input.value);
|
|
}
|
|
popup.querySelector('.cancel-btn').onclick = () => {
|
|
popup.hide()
|
|
resolve(null);
|
|
}
|
|
})
|
|
}
|
|
|
|
const currentYear = new Date().getFullYear()
|
|
function formatedTime(time, relative) {
|
|
try {
|
|
if (String(time).indexOf('_'))
|
|
time = String(time).split('_')[0]
|
|
let timeFrag = new Date(parseInt(time)).toString().split(' '),
|
|
day = timeFrag[0],
|
|
month = timeFrag[1],
|
|
date = timeFrag[2],
|
|
year = timeFrag[3],
|
|
minutes = new Date(parseInt(time)).getMinutes(),
|
|
hours = new Date(parseInt(time)).getHours(),
|
|
currentTime = new Date().toString().split(' ')
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
|
let finalHours = ``;
|
|
if (hours > 12)
|
|
finalHours = `${hours - 12}:${minutes}`
|
|
else if (hours === 0)
|
|
finalHours = `12:${minutes}`
|
|
else
|
|
finalHours = `${hours}:${minutes}`
|
|
|
|
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
|
if (relative) {
|
|
if (year == currentYear) {
|
|
if (currentTime[1] === month && date === currentTime[2])
|
|
return `Today at ${finalHours}`;
|
|
else
|
|
return ` ${date} ${month}`;
|
|
|
|
}
|
|
}
|
|
else
|
|
return `${finalHours} ${month} ${date} ${year}`;
|
|
} catch (e) {
|
|
console.error(e);
|
|
return time;
|
|
}
|
|
}
|
|
|
|
function copyToClipboard(parent) {
|
|
let toast = document.getElementById('textCopied'),
|
|
textToCopy = parent.querySelector('.copy').textContent;
|
|
navigator.clipboard.writeText(textToCopy)
|
|
notify('Copied', 'success')
|
|
}
|
|
function areInputsValid(parent) {
|
|
let allInputs = parent.querySelectorAll("sm-input:not([disabled])"),
|
|
inputsFilled = [...allInputs].every(input => {
|
|
if (input.hasAttribute('floId')) {
|
|
if (floCrypto.validateAddr(input.value.trim())) return true
|
|
}
|
|
else if (input.hasAttribute('privateKey')) {
|
|
if (floCrypto.getPubKeyHex(input.value.trim())) return true
|
|
}
|
|
else
|
|
return input.isValid
|
|
})
|
|
let allRadios = parent.querySelectorAll("input[type='radio']")
|
|
if (allRadios.length)
|
|
return inputsFilled && parent.querySelector('[checked]')
|
|
else
|
|
return inputsFilled
|
|
}
|
|
|
|
function formValidation(formElement) {
|
|
let parent = formElement.closest('sm-popup') || formElement.closest('form'),
|
|
submitBtn = parent.querySelector("[type='submit']");
|
|
if (!submitBtn) return;
|
|
if (areInputsValid(parent))
|
|
submitBtn.disabled = false;
|
|
else
|
|
submitBtn.disabled = true;
|
|
}
|
|
|
|
window.addEventListener('load', () => {
|
|
document.addEventListener('input', e => {
|
|
if (e.target.closest('sm-input')) {
|
|
let input = e.target.closest('sm-input')
|
|
if (!input.closest('sm-popup') && !input.closest('form')) return
|
|
formValidation(input)
|
|
if (input.value === '')
|
|
input.setValidity('')
|
|
let validityState = input.validity
|
|
if (input.hasAttribute('type') && input.getAttribute('type') === 'number') {
|
|
if (validityState.rangeUnderflow) {
|
|
input.setValidity('Minium ₹1 should be entered.')
|
|
}
|
|
else if (validityState.typeMismatch) {
|
|
input.setValidity('Only digits are allowed.')
|
|
}
|
|
else {
|
|
input.setValidity('')
|
|
}
|
|
}
|
|
else if (input.hasAttribute('floId')) {
|
|
if (floCrypto.validateAddr(input.value.trim()) || input.value.trim() === '')
|
|
input.setValidity('')
|
|
else
|
|
input.setValidity('Invalid FLO address.')
|
|
}
|
|
else if (input.hasAttribute('privateKey')) {
|
|
if (floCrypto.getPubKeyHex(input.value.trim()) || input.value.trim() === '')
|
|
input.setValidity('')
|
|
else
|
|
input.setValidity('Invalid FLO private key!')
|
|
}
|
|
else {
|
|
if (validityState.patternMismatch)
|
|
input.setValidity('Invalid UPI address.')
|
|
if (validityState.tooShort || validityState.tooLong)
|
|
input.setValidity('UPI transaction Id should be 12 digits long.')
|
|
else
|
|
input.setValidity('')
|
|
}
|
|
}
|
|
})
|
|
document.addEventListener('keyup', (e) => {
|
|
if (e.target.closest('sm-input')) {
|
|
if (e.key === 'Enter') {
|
|
let parent = e.target.closest('sm-popup') || e.target.closest('form')
|
|
parent.querySelector("button[type='submit']")
|
|
.click();
|
|
}
|
|
}
|
|
})
|
|
let currentTableHeader
|
|
document.addEventListener('click', e => {
|
|
try {
|
|
if (e.target.closest('#add_new_sheet'))
|
|
showPopup('new_sheet_popup')
|
|
else if (e.target.closest('.sheet-card'))
|
|
viewSheet(e.target.closest('.sheet-card').dataset.name)
|
|
else if (e.target.closest(".person-card"))
|
|
copyToLogInput(e.target.closest(".person-card").dataset.floId)
|
|
else if (e.target.closest("th")) {
|
|
let trgt = e.target.closest('th')
|
|
if (currentTableHeader && currentTableHeader !== trgt)
|
|
currentTableHeader.classList.remove('ascending', 'descending')
|
|
sortTable(trgt.cellIndex, trgt.classList.contains('descending'))
|
|
if (trgt.classList.contains('descending'))
|
|
trgt.classList.add('ascending')
|
|
else
|
|
trgt.classList.remove('ascending')
|
|
trgt.classList.toggle('descending')
|
|
currentTableHeader = trgt
|
|
}
|
|
else if (e.target.closest('h4')) {
|
|
createRipple(e)
|
|
}
|
|
} catch (error) {
|
|
//do nothing
|
|
}
|
|
})
|
|
function createRipple(event) {
|
|
const target = event.target.closest('h4')
|
|
const ripple = target.querySelector('.ripple');
|
|
if (ripple) {
|
|
ripple.remove();
|
|
}
|
|
const ogOverflow = target.style.overflow
|
|
target.style.overflow = 'hidden'
|
|
console.log(target.getBoundingClientRect(), event)
|
|
const circle = document.createElement("span");
|
|
const diameter = Math.max(target.clientWidth, target.clientHeight);
|
|
const radius = diameter / 2;
|
|
circle.style.width = circle.style.height = `${diameter}px`;
|
|
circle.style.left = `${event.clientX - radius}px`;
|
|
circle.style.top = `${event.clientY - radius}px`;
|
|
circle.classList.add("ripple");
|
|
target.append(circle);
|
|
circle.onanimationend = () => {
|
|
target.style.overflow = ogOverflow
|
|
}
|
|
}
|
|
const observer = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
let { title, description, editors, attributes, sheet, isWriteable, isSubAdmin } = floGlobals.currentSheet
|
|
renderSheetView(title, description, editors, attributes, sheet, isWriteable, isSubAdmin, true, true)
|
|
observer.disconnect()
|
|
}
|
|
})
|
|
})
|
|
const watchLastElement = new MutationObserver(mutations => {
|
|
mutations.forEach(mutation => {
|
|
if (mutation.addedNodes.length) {
|
|
observer.observe(mutation.addedNodes[mutation.addedNodes.length - 1])
|
|
}
|
|
})
|
|
})
|
|
watchLastElement.observe(tableBody, { childList: true, subtree: true })
|
|
|
|
document.getElementById('search_sheets').addEventListener('input', function () {
|
|
if (this.value.trim !== '') {
|
|
for (child of sheetsContainer.children) {
|
|
if (child.id === 'add_new_sheet') continue
|
|
if (child.dataset.name.toUpperCase().indexOf(this.value.toUpperCase()) > -1) {
|
|
child.classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
child.classList.add('hide-completely')
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
function removeUnderscore(word) {
|
|
return word.replace(/_/g, ' ')
|
|
}
|
|
|
|
const render = {
|
|
sheetCard(sheetName, editors) {
|
|
let card = document.createElement('div'),
|
|
type = 'Public'
|
|
card.classList.add('sheet-card')
|
|
card.setAttribute('data-name', sheetName)
|
|
if (editors)
|
|
type = 'Private'
|
|
card.innerHTML = `
|
|
<div class="card">
|
|
<h5>${type}</h5>
|
|
</div>
|
|
<h4>${removeUnderscore(sheetName)}</h4>
|
|
`
|
|
return card
|
|
},
|
|
personCard(floId, details) {
|
|
let card = document.createElement('div'),
|
|
{ name } = details
|
|
card.classList.add('person-card')
|
|
setAttributes(card, {
|
|
'data-flo-id': floId,
|
|
'data-name': name
|
|
})
|
|
card.innerHTML = `
|
|
<h3 class="person-initials">${name.charAt(0)}</h3>
|
|
<h4 class="person-name">${name}</h4>
|
|
<h5 class="person-flo-id overflow-ellipsis">${floId}</h5>
|
|
`
|
|
return card
|
|
},
|
|
editorCard(floId) {
|
|
let card = document.createElement('div')
|
|
card.classList.add('editor-card', 'flex', 'space-between', 'align-center')
|
|
setAttributes(card, {
|
|
'data-flo-id': floId
|
|
})
|
|
card.innerHTML = `
|
|
<h4 class="editor-address">${floId}</h4>
|
|
<svg class="icon" onclick="this.parentNode.remove()" viewBox="0 0 64 64">
|
|
<title>Remove editor</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
`
|
|
return card
|
|
},
|
|
columnCard(title) {
|
|
let card = document.createElement('div')
|
|
card.classList.add('column-card', 'flex', 'space-between', 'align-center')
|
|
setAttributes(card, {
|
|
'data-column-title': title
|
|
})
|
|
card.innerHTML = `
|
|
<h5 class="column-title">${title}</h5>
|
|
<svg class="icon" onclick="this.parentNode.remove()" viewBox="0 0 64 64">
|
|
<title>Remove column</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
`
|
|
return card
|
|
},
|
|
detailsCard(type, value) {
|
|
let card = document.createElement('div')
|
|
card.classList.add('details-card', 'grid', 'align-center')
|
|
setAttributes(card, {
|
|
'data-type': type,
|
|
'data-value': value
|
|
})
|
|
card.innerHTML = `
|
|
<h5 class="type">${type}</h5>
|
|
<h4 class="value">${value}</h4>
|
|
<svg class="icon" onclick="this.parentNode.remove()" viewBox="0 0 64 64">
|
|
<title>Remove this field</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
`
|
|
return card
|
|
}
|
|
}
|
|
|
|
const sheetDetails = document.getElementById('sheet_details')
|
|
const sheetPage = document.getElementById('sheet_page')
|
|
|
|
const addEditorInput = document.getElementById('add_editor_input')
|
|
const specifyEditors = document.getElementById('specify_editors')
|
|
const editorsContainer = document.getElementById('editors_container')
|
|
|
|
const addColumnInput = document.getElementById('add_column_input')
|
|
const columnsContainer = document.getElementById('columns_container')
|
|
|
|
const additionalFields = document.getElementById('additional_fields')
|
|
const personFloIdInput = document.getElementById('person_flo_id_input')
|
|
const personNameInput = document.getElementById('person_name_input')
|
|
const addDetailTypeInput = document.getElementById('add_detail_type_input')
|
|
const addDetailValueInput = document.getElementById('add_detail_value_input')
|
|
|
|
|
|
|
|
function addEditor() {
|
|
let address = addEditorInput.value.trim()
|
|
if (address === '') return;
|
|
if (floCrypto.validateAddr(address)) {
|
|
editorsContainer.append(render.editorCard(address))
|
|
addEditorInput.value = ''
|
|
}
|
|
else {
|
|
notify('Invalid editor FLO address', 'error')
|
|
}
|
|
}
|
|
function addColumn() {
|
|
let columnTitle = addColumnInput.value.trim()
|
|
if (columnTitle !== '') {
|
|
columnsContainer.append(render.columnCard(columnTitle))
|
|
addColumnInput.value = ''
|
|
|
|
}
|
|
else
|
|
notify("Column name should be specified.", 'error')
|
|
}
|
|
|
|
function addDetails() {
|
|
let type = addDetailTypeInput.value.trim()
|
|
let value = addDetailValueInput.value.trim()
|
|
if (type !== '' && value !== '') {
|
|
additionalFields.append(render.detailsCard(type, value))
|
|
addDetailTypeInput.value = ''
|
|
addDetailValueInput.value = ''
|
|
}
|
|
else
|
|
notify('Please enter both type and value', 'error')
|
|
}
|
|
|
|
function toggleDetails() {
|
|
sheetDetails.classList.toggle('collapse')
|
|
}
|
|
function toggleSideBar() {
|
|
sheetPage.classList.toggle('toggle-side-bar')
|
|
}
|
|
function toggleEditors() {
|
|
specifyEditors.classList.toggle('hide-completely')
|
|
}
|
|
|
|
const allPages = document.querySelectorAll('.page')
|
|
function showPage(page) {
|
|
allPages.forEach(page => page.classList.add('hide-completely'))
|
|
document.getElementById(page).classList.remove('hide-completely')
|
|
if (page === 'home_page') {
|
|
tableBody.innerHTML = ''
|
|
sheetHeading.textContent = ''
|
|
sheetType.textContent = ''
|
|
sheetDescription.textContent = ''
|
|
mainHeader.classList.remove('hide-completely')
|
|
}
|
|
else
|
|
mainHeader.classList.add('hide-completely')
|
|
}
|
|
|
|
const credentialsSection = document.getElementById('credentials_section'),
|
|
generateFloId = document.getElementById('generate_flo_id'),
|
|
generatedId = document.getElementById('generated_id'),
|
|
generatedKey = document.getElementById('generated_key')
|
|
|
|
function generateId() {
|
|
generateFloId.classList.add('hide-completely')
|
|
credentialsSection.classList.remove('hide-completely')
|
|
let { floID, privKey } = floCrypto.generateNewID()
|
|
generatedId.textContent = floID
|
|
generatedKey.textContent = privKey
|
|
}
|
|
|
|
function signOut() {
|
|
getConfirmation('Are you sure you want to Sign out?').then(result => {
|
|
try {
|
|
hidePopup()
|
|
generateFloId.classList.remove('hide-completely')
|
|
credentialsSection.classList.add('hide-completely')
|
|
generatedId.textContent = ''
|
|
generatedKey.textContent = ''
|
|
floDapps.clearCredentials();
|
|
setTimeout(onLoadStartUp, 1000)
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
})
|
|
}
|
|
|
|
document.getElementById('save_button').children[2].addEventListener("click", function (e) {
|
|
logSheet.commitUpdates().then(result => {
|
|
this.parentNode.classList.add('hide')
|
|
this.parentNode.classList.remove('no-transformations')
|
|
this.parentNode.dataset["unsaved"] = '0'
|
|
notify("Save Successful")
|
|
}).catch(error => notify(`Save Unsuccessful [${error}]`, error))
|
|
})
|
|
|
|
const firstSelect = document.getElementById('first_select')
|
|
const secondSelect = document.getElementById('second_select')
|
|
|
|
document.getElementById('first_select').addEventListener("change", function (e) {
|
|
try {
|
|
let title = floGlobals.currentSheet.title;
|
|
let sheet = floGlobals.currentSheet.sheet;
|
|
let type = this.value;
|
|
console.log(this)
|
|
if (type === 'count') {
|
|
secondSelect.classList.add("hide-completely")
|
|
let data = logSheet.groupLogsBy.count(title, sheet)
|
|
renderGroupByView("count", data)
|
|
} else {
|
|
secondSelect.classList.remove("hide-completely")
|
|
let column = secondSelect.value;
|
|
let data = logSheet.groupLogsBy[type](title, sheet, column);
|
|
renderGroupByView(`${type}(${column})`, data)
|
|
}
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
})
|
|
|
|
document.getElementById('second_select').addEventListener("change", function (e) {
|
|
try {
|
|
console.log(this)
|
|
let title = floGlobals.currentSheet.title;
|
|
let sheet = floGlobals.currentSheet.sheet;
|
|
let type = firstSelect.value;
|
|
let column = this.value;
|
|
let data = logSheet.groupLogsBy[type](title, sheet, column);
|
|
renderGroupByView(`${type}(${column})`, data)
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
})
|
|
|
|
function removeElement(element) {
|
|
element.parentNode.removeChild(element);
|
|
}
|
|
|
|
function clearElement(element) {
|
|
element.innerHTML = '';
|
|
return element;
|
|
}
|
|
|
|
let banner = document.getElementById('save_button')
|
|
function showSave() {
|
|
banner.dataset["unsaved"] = parseInt(banner.dataset["unsaved"]) + 1
|
|
banner.classList.remove('no-transformations');
|
|
banner.classList.add('hide');
|
|
setTimeout(() => {
|
|
banner.children[0].style.background = `#F${Math.max(9 - parseInt(banner.dataset["unsaved"]), 0)}0`;
|
|
banner.classList.add('no-transformations');
|
|
banner.classList.remove('hide');
|
|
}, 300)
|
|
}
|
|
|
|
function addPerson() {
|
|
try {
|
|
let floID = personFloIdInput.value.trim();
|
|
let name = personNameInput.value.trim();
|
|
let otherDetails = {}
|
|
let fields = additionalFields.querySelectorAll('.type, .value')
|
|
for (let i = 0; i < fields.length; i += 2)
|
|
otherDetails[fields[i].textContent] = fields[i + 1].textContent
|
|
console.log(fields, otherDetails)
|
|
logSheet.addPerson(floID, name, otherDetails)
|
|
peopleContainer.append(render.personCard(floID, logSheet.viewPerson(floID)))
|
|
hidePopup()
|
|
notify(`Added Person: ${floID}`)
|
|
showSave()
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
}
|
|
|
|
const sheetTitleInput = document.getElementById('sheet_title_input'),
|
|
sheetDescriptionInput = document.getElementById('sheet_description_input'),
|
|
sheetTypeSwitch = document.getElementById('sheet_type_switch')
|
|
|
|
function createSheet() {
|
|
try {
|
|
let title = sheetTitleInput.value.trim().replace(/ /g, "_");
|
|
let description = sheetDescriptionInput.value.trim();
|
|
let columns = []
|
|
let editors = []
|
|
columnsContainer.querySelectorAll('.column-title').forEach(column => columns.push(column.textContent))
|
|
if (!columns.length) {
|
|
notify('Every LogSheet should contain atleast 1 column.', 'error')
|
|
return
|
|
}
|
|
editorsContainer.querySelectorAll('.editor-address').forEach(editor => editors.push(editor.textContent))
|
|
if (!sheetTypeSwitch.checked)
|
|
editors = null;
|
|
else
|
|
if (!editors.length)
|
|
editors = floGlobals.subAdmins;
|
|
logSheet.createNewSheet(title, description, columns, editors)
|
|
let sheet = {}
|
|
sheet[title] = { editors }
|
|
renderSheetList(sheet)
|
|
hidePopup()
|
|
notify(`Created New Sheet: ${title}`)
|
|
showSave()
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
}
|
|
|
|
function copyToLogInput(floID) {
|
|
let input = document.forms['new-log'][0]
|
|
if (input.tagName === 'INPUT')
|
|
input.value = floID;
|
|
console.log(floID, input)
|
|
}
|
|
|
|
function enterLog(e) {
|
|
e.preventDefault()
|
|
let title = floGlobals.currentSheet.title;
|
|
let form = document.forms['new-log'],
|
|
allFormElements = document.querySelectorAll('input[form="new-log"]')
|
|
allFormElements.forEach(element => element.disabled = true)
|
|
if (form[0].tagName === 'INPUT') {
|
|
let floID = form[0].value;
|
|
let log = []
|
|
for (let i = 1; i < form.length - 1; i++)
|
|
log.push(form[i].value)
|
|
logSheet.enterLog(title, floID, log).then(result => {
|
|
allFormElements.forEach(element => element.disabled = false)
|
|
form.reset();
|
|
notify('Log entry successful', 'success')
|
|
let row = tableBody.insertRow(0);
|
|
row.insertCell().textContent = floID
|
|
row.insertCell().innerHTML = `<input type="text" class="grade-input" disabled>`
|
|
log.forEach(l => row.insertCell().textContent = l)
|
|
}).catch(error => {
|
|
notify(error, "error")
|
|
allFormElements.forEach(element => element.disabled = false)
|
|
})
|
|
}
|
|
}
|
|
|
|
function viewSheet(title) {
|
|
sheetContainer.classList.add('placeholder')
|
|
sheetHeading.classList.add('placeholder')
|
|
sheetType.classList.add('placeholder')
|
|
sheetDescription.classList.add('placeholder')
|
|
tableHeader.classList.add('hide')
|
|
showPage('sheet_page')
|
|
logSheet.refreshLogs(title).then(result => {
|
|
sheetContainer.classList.remove('placeholder')
|
|
sheetHeading.classList.remove('placeholder')
|
|
sheetType.classList.remove('placeholder')
|
|
sheetDescription.classList.remove('placeholder')
|
|
tableHeader.classList.remove('hide')
|
|
let data = logSheet.viewLogs(title)
|
|
renderSheetView(data.title, data.description, data.editors, data.attributes, data.sheet.reverse(),
|
|
!data.editors || data.editors.includes(myFloID), floGlobals.subAdmins.includes(myFloID))
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
|
|
function renderPersonCard(floID, details) {
|
|
let newCard = document.createElement("div");
|
|
newCard.classList.add("person-card"); { }
|
|
let frontSide = document.createElement("div");
|
|
let initalTag = document.createElement("span");
|
|
initalTag.textContent = details.name.charAt(0)
|
|
frontSide.appendChild(initalTag);
|
|
let nameTag = document.createTextNode(details.name);
|
|
frontSide.appendChild(nameTag);
|
|
let backSide = document.createElement("div");
|
|
let idTag = document.createElement("span");
|
|
idTag.textContent = floID;
|
|
backSide.appendChild(idTag);
|
|
for (d in details)
|
|
if (d !== "name") {
|
|
let detailTag = document.createElement("li")
|
|
detailTag.textContent = `${d}: ${details[d]}`
|
|
backSide.appendChild(detailTag)
|
|
}
|
|
newCard.appendChild(frontSide);
|
|
newCard.appendChild(backSide);
|
|
document.getElementById("people_container").appendChild(newCard);
|
|
}
|
|
|
|
const peopleContainer = document.getElementById("people_container")
|
|
|
|
function renderPersonList(personList = {}) {
|
|
for (person in personList)
|
|
frag.append(render.personCard(person, personList[person]))
|
|
peopleContainer.append(frag)
|
|
}
|
|
|
|
const sheetsContainer = document.getElementById("sheets_container")
|
|
function renderSheetList(sheetList) {
|
|
console.log(sheetList)
|
|
for (sheet in sheetList) {
|
|
frag.append(render.sheetCard(sheet, sheetList[sheet].editors))
|
|
}
|
|
if (floGlobals.subAdmins.includes(myFloID) && !document.getElementById('add_new_sheet')) {
|
|
let firstCard = document.createElement('div')
|
|
firstCard.id = 'add_new_sheet'
|
|
firstCard.classList.add('sheet-card')
|
|
firstCard.innerHTML = `
|
|
<div class="card">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<title>add</title>
|
|
<line x1="32" x2="32" y2="64" />
|
|
<line x1="64" y1="32" y2="32" />
|
|
</svg>
|
|
</div>
|
|
<h4>Add new sheet</h4>
|
|
`
|
|
frag.prepend(firstCard)
|
|
}
|
|
sheetsContainer.append(frag)
|
|
}
|
|
|
|
function createTh(text) {
|
|
let cell = document.createElement('th')
|
|
cell.textContent = text
|
|
return cell
|
|
}
|
|
function createCell(text) {
|
|
let cell = document.createElement('td')
|
|
/*if(text.split(/,|:| /).length > 5)
|
|
cell.classList.add('text-field')*/
|
|
cell.textContent = text
|
|
return cell
|
|
}
|
|
|
|
const sheetContainer = document.getElementById('sheet_container'),
|
|
sheetHeading = document.getElementById('sheet_heading'),
|
|
sheetType = document.getElementById('sheet_type'),
|
|
sheetDescription = document.getElementById('sheet_description'),
|
|
sheetEditors = document.getElementById('sheet_editors'),
|
|
tableHeader = sheetContainer.firstElementChild.firstElementChild,
|
|
tableBody = sheetContainer.firstElementChild.children[1]
|
|
let startingIndex = 0,
|
|
endingIndex = 0
|
|
|
|
function renderSheetView(title, description, editors, attributes, sheet, isWriteable, isSubAdmin, onlyRenderTable = false, lazyLoad = false) {
|
|
if (!lazyLoad)
|
|
floGlobals.currentSheet = {
|
|
title,
|
|
description,
|
|
editors,
|
|
attributes,
|
|
sheet,
|
|
isWriteable,
|
|
isSubAdmin,
|
|
}
|
|
const parseVectorClock = (vc) => {
|
|
vc = vc.split('_')
|
|
let time = new Date(parseInt(vc[0])).toString().slice(4, 24);
|
|
let floID = vc[1]
|
|
return `by ${floID} (${time})`
|
|
}
|
|
const createGradeField = (vc, grade) => {
|
|
let gradeField = document.createElement("input")
|
|
gradeField.setAttribute("type", "text")
|
|
gradeField.className = "grade-input"
|
|
gradeField.value = grade
|
|
if (!isWriteable || !isSubAdmin || vc.split('_')[1] == myFloID)
|
|
gradeField.disabled = true;
|
|
else {
|
|
gradeField.addEventListener("keypress", (event) => {
|
|
if (event.keyCode == 13) {
|
|
event.preventDefault();
|
|
gradeField.disabled = true;
|
|
logSheet.forwardLog(title, vc, gradeField.value)
|
|
.then(result => notify("Graded Log"))
|
|
.catch(error => notify("Grading failed: " + error, "error"))
|
|
.finally(_ => gradeField.disabled = false)
|
|
}
|
|
})
|
|
}
|
|
return gradeField
|
|
}
|
|
if (lazyLoad) {
|
|
startingIndex = sheet.length > endingIndex ? endingIndex : sheet.length
|
|
endingIndex = sheet.length - endingIndex > 20 ? endingIndex + 20 : sheet.length
|
|
} else {
|
|
startingIndex = 0
|
|
endingIndex = sheet.length > 20 ? 20 : sheet.length
|
|
}
|
|
if (!onlyRenderTable) {
|
|
//Add Sheet Details
|
|
sheetHeading.textContent = removeUnderscore(title);
|
|
if (editors) {
|
|
sheetType.textContent = 'Private'
|
|
editors.forEach(editor => {
|
|
let card = document.createElement('div')
|
|
card.className = 'editor'
|
|
card.textContent = editor;
|
|
frag.append(card)
|
|
})
|
|
sheetEditors.innerHTML = 'Maintained by: '
|
|
sheetEditors.append(frag)
|
|
}
|
|
else {
|
|
sheetType.textContent = 'Public'
|
|
sheetEditors.innerHTML = ''
|
|
}
|
|
sheetDescription.textContent = description;
|
|
clearElement(tableHeader)
|
|
let row = tableHeader.insertRow();
|
|
row.append(createTh("FLO ID"), createTh("Grade"));
|
|
attributes.forEach(a => row.append(createTh(a)))
|
|
//Add input fields if writable
|
|
if (isWriteable && !lazyLoad) {
|
|
let row = tableHeader.insertRow();
|
|
row.insertCell().innerHTML = `<input form="new-log" class="log-input" type="text">`;
|
|
row.insertCell().textContent = ``;
|
|
attributes.forEach(a => row.insertCell().innerHTML = `<input form="new-log" class="log-input" type="text">`);
|
|
}
|
|
}
|
|
if (onlyRenderTable) {
|
|
if (!lazyLoad)
|
|
tableBody.innerHTML = ''
|
|
}
|
|
for (let i = startingIndex; i < endingIndex; i++) {
|
|
let data = sheet[i]
|
|
if (!data.log || !data.floID || !data.vc)
|
|
continue;
|
|
let row = document.createElement('tr')
|
|
row.setAttribute("title", parseVectorClock(data.vc))
|
|
row.append(createCell(data.floID))
|
|
let gradeF = document.createElement('td')
|
|
gradeF.append(createGradeField(data.vc, data.grade))
|
|
row.append(gradeF)
|
|
data.log.forEach(l => row.append(createCell(l)))
|
|
frag.append(row)
|
|
}
|
|
tableBody.append(frag)
|
|
if (!onlyRenderTable) {
|
|
//Add options for groupBy
|
|
document.getElementById('group_by_title').textContent = `Group ${removeUnderscore(title)} by`;
|
|
document.getElementById('group_by_view').innerHTML =
|
|
`<table><thead><tr><th>FLO ID</th><th><i>null</i></th></tr></thead></table>`
|
|
let colSelect = clearElement(document.getElementById('group_by_menu').children[1]);
|
|
attributes.forEach(a => {
|
|
let opt = document.createElement('sm-option');
|
|
opt.setAttribute('value', a);
|
|
opt.textContent = a;
|
|
colSelect.appendChild(opt)
|
|
})
|
|
}
|
|
}
|
|
|
|
function renderGroupByView(groupName, groupData) {
|
|
let table = document.createElement("table")
|
|
let head = table.createTHead().insertRow(0)
|
|
head.insertCell(0).textContent = "FLO ID";
|
|
head.insertCell(1).textContent = groupName;
|
|
let tbody = document.createElement("tbody")
|
|
for (floID in groupData) {
|
|
let row = tbody.insertRow(0);
|
|
row.insertCell(0).textContent = floID
|
|
row.insertCell(1).textContent = groupData[floID]
|
|
}
|
|
table.appendChild(tbody);
|
|
clearElement(document.getElementById("group_by_view")).appendChild(table);
|
|
}
|
|
|
|
function sortTable(n, ascending) {
|
|
console.log(n)
|
|
if (ascending) {
|
|
if (n === 0)
|
|
floGlobals.currentSheet.sheet.sort((a, b) => a.floID.toLowerCase().localeCompare(b.floID.toLowerCase()))
|
|
else if (n === 1)
|
|
floGlobals.currentSheet.sheet.sort((a, b) => {
|
|
if (a.grade === b.grade) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return (a.grade < b.grade) ? -1 : 1;
|
|
}
|
|
})
|
|
else
|
|
floGlobals.currentSheet.sheet.sort((a, b) => {
|
|
if (isNaN(a.log[n - 2])) {
|
|
let x = (a.log[n - 2]) ? a.log[n - 2].toLowerCase() : a.log[n - 2]
|
|
let y = (b.log[n - 2]) ? b.log[n - 2].toLowerCase() : b.log[n - 2]
|
|
if (x === y) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return (x < y) ? -1 : 1;
|
|
}
|
|
}
|
|
else
|
|
return a.log[n - 2] - b.log[n - 2]
|
|
})
|
|
} else {
|
|
if (n === 0)
|
|
floGlobals.currentSheet.sheet.sort((a, b) => b.floID.toLowerCase().localeCompare(a.floID.toLowerCase()))
|
|
else if (n === 1)
|
|
floGlobals.currentSheet.sheet.sort((a, b) => {
|
|
if (a.grade === b.grade) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return (a.grade > b.grade) ? -1 : 1;
|
|
}
|
|
})
|
|
else
|
|
floGlobals.currentSheet.sheet.sort((a, b) => {
|
|
if (isNaN(a.log[n - 2])) {
|
|
let x = (a.log[n - 2]) ? a.log[n - 2].toLowerCase() : a.log[n - 2]
|
|
let y = (b.log[n - 2]) ? b.log[n - 2].toLowerCase() : b.log[n - 2]
|
|
if (x === y) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return (x > y) ? -1 : 1;
|
|
}
|
|
}
|
|
else
|
|
return b.log[n - 2] - a.log[n - 2]
|
|
})
|
|
}
|
|
let { title, description, editors, attributes, sheet, isWriteable, isSubAdmin } = floGlobals.currentSheet
|
|
renderSheetView(title, description, editors, attributes, sheet, isWriteable, isSubAdmin, true, false)
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |