1607 lines
57 KiB
HTML
1607 lines
57 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html class="no-js" lang="">
|
||
|
<head>
|
||
|
<style>
|
||
|
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
|
||
|
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
|
||
|
</style>
|
||
|
<meta charset="utf-8" />
|
||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||
|
<title>SaaSpal - SaaS and Software Landing Page Template</title>
|
||
|
<meta name="description" content="" />
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
<link
|
||
|
rel="shortcut icon"
|
||
|
type="image/x-icon"
|
||
|
href="assets/img/favicon.png"
|
||
|
/>
|
||
|
<!-- Place favicon.ico in the root directory -->
|
||
|
|
||
|
<!-- ======== CSS here ======== -->
|
||
|
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
|
||
|
<link rel="stylesheet" href="assets/css/lineicons.css" />
|
||
|
<link rel="stylesheet" href="assets/css/animate.css" />
|
||
|
<link rel="stylesheet" href="assets/css/main.css" />
|
||
|
</head>
|
||
|
<body>
|
||
|
<!--[if lte IE 9]>
|
||
|
<p class="browserupgrade">
|
||
|
You are using an <strong>outdated</strong> browser. Please
|
||
|
<a href="https://browsehappy.com/">upgrade your browser</a> to improve
|
||
|
your experience and security.
|
||
|
</p>
|
||
|
<![endif]-->
|
||
|
|
||
|
<!-- ======== preloader start ======== -->
|
||
|
<div class="preloader">
|
||
|
<div class="loader">
|
||
|
<div class="spinner">
|
||
|
<div class="spinner-container">
|
||
|
<div class="spinner-rotator">
|
||
|
<div class="spinner-left">
|
||
|
<div class="spinner-circle"></div>
|
||
|
</div>
|
||
|
<div class="spinner-right">
|
||
|
<div class="spinner-circle"></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<!-- preloader end -->
|
||
|
|
||
|
<!-- ======== header start ======== -->
|
||
|
<header class="header">
|
||
|
<div class="navbar-area">
|
||
|
<div class="container">
|
||
|
<div class="row align-items-center">
|
||
|
<div class="col-lg-12">
|
||
|
<nav class="navbar navbar-expand-lg">
|
||
|
<a class="navbar-brand" href="index.html">
|
||
|
<img src="assets/img/logo/logo.svg" alt="Logo" />
|
||
|
</a>
|
||
|
<button
|
||
|
class="navbar-toggler"
|
||
|
type="button"
|
||
|
data-bs-toggle="collapse"
|
||
|
data-bs-target="#navbarSupportedContent"
|
||
|
aria-controls="navbarSupportedContent"
|
||
|
aria-expanded="false"
|
||
|
aria-label="Toggle navigation"
|
||
|
>
|
||
|
<span class="toggler-icon"></span>
|
||
|
<span class="toggler-icon"></span>
|
||
|
<span class="toggler-icon"></span>
|
||
|
</button>
|
||
|
|
||
|
<div
|
||
|
class="collapse navbar-collapse sub-menu-bar"
|
||
|
id="navbarSupportedContent"
|
||
|
>
|
||
|
<ul id="nav" class="navbar-nav ms-auto">
|
||
|
<li class="nav-item">
|
||
|
<a class="page-scroll active" href="#home">Home</a>
|
||
|
</li>
|
||
|
<li class="nav-item">
|
||
|
<a class="page-scroll" href="#features">Features</a>
|
||
|
</li>
|
||
|
<li class="nav-item">
|
||
|
<a class="page-scroll" href="#about">About</a>
|
||
|
</li>
|
||
|
|
||
|
<li class="nav-item">
|
||
|
<a class="page-scroll" href="#why">Why</a>
|
||
|
</li>
|
||
|
<li class="nav-item">
|
||
|
<a href="javascript:void(0)">Pricing</a>
|
||
|
</li>
|
||
|
<li class="nav-item">
|
||
|
<a href="javascript:void(0)">Clients</a>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
<!-- navbar collapse -->
|
||
|
</nav>
|
||
|
<!-- navbar -->
|
||
|
</div>
|
||
|
</div>
|
||
|
<!-- row -->
|
||
|
</div>
|
||
|
<!-- container -->
|
||
|
</div>
|
||
|
<!-- navbar area -->
|
||
|
</header>
|
||
|
<!-- ======== header end ======== -->
|
||
|
|
||
|
<!-- ======== hero-section start ======== -->
|
||
|
<section id="home" class="hero-section">
|
||
|
<div class="container">
|
||
|
<div class="row align-items-center position-relative">
|
||
|
<div class="col-lg-6">
|
||
|
<div class="hero-content">
|
||
|
<h1 class="wow fadeInUp" data-wow-delay=".4s">
|
||
|
Your using free lite version
|
||
|
</h1>
|
||
|
<p class="wow fadeInUp" data-wow-delay=".6s">
|
||
|
Please, purchase full version to get all sections, features and
|
||
|
permission to remove footer credit.
|
||
|
</p>
|
||
|
<a
|
||
|
href="javascript:void(0)"
|
||
|
class="main-btn border-btn btn-hover wow fadeInUp"
|
||
|
data-wow-delay=".6s"
|
||
|
>Purchase Now</a
|
||
|
>
|
||
|
<a href="#features" class="scroll-bottom">
|
||
|
<i class="lni lni-arrow-down"></i
|
||
|
></a>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-6">
|
||
|
<div class="hero-img wow fadeInUp" data-wow-delay=".5s">
|
||
|
<img src="assets/img/hero/hero-img.png" alt="" />
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== hero-section end ======== -->
|
||
|
|
||
|
<!-- ======== feature-section start ======== -->
|
||
|
<section id="features" class="feature-section pt-120">
|
||
|
<div class="container">
|
||
|
<div class="row justify-content-center">
|
||
|
<div class="col-lg-4 col-md-8 col-sm-10">
|
||
|
<div class="single-feature">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-bootstrap"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Bootstrap 5</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-8 col-sm-10">
|
||
|
<div class="single-feature">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-layout"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Clean Design</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-8 col-sm-10">
|
||
|
<div class="single-feature">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-coffee-cup"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Easy to Use</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== feature-section end ======== -->
|
||
|
|
||
|
<!-- ======== about-section start ======== -->
|
||
|
<section id="about" class="about-section pt-150">
|
||
|
<div class="container">
|
||
|
<div class="row align-items-center">
|
||
|
<div class="col-xl-6 col-lg-6">
|
||
|
<div class="about-img">
|
||
|
<img src="assets/img/about/about-1.png" alt="" class="w-100" />
|
||
|
<img
|
||
|
src="assets/img/about/about-left-shape.svg"
|
||
|
alt=""
|
||
|
class="shape shape-1"
|
||
|
/>
|
||
|
<img
|
||
|
src="assets/img/about/left-dots.svg"
|
||
|
alt=""
|
||
|
class="shape shape-2"
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-xl-6 col-lg-6">
|
||
|
<div class="about-content">
|
||
|
<div class="section-title mb-30">
|
||
|
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
|
||
|
Perfect Solution Thriving Online Business
|
||
|
</h2>
|
||
|
<p class="wow fadeInUp" data-wow-delay=".4s">
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
dinonumy eirmod tempor invidunt ut labore et dolore magna
|
||
|
aliquyam erat, sed diam voluptua. At vero eos et accusam et
|
||
|
justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
||
|
sea takimata sanctus est Lorem.Lorem ipsum dolor sit amet.
|
||
|
</p>
|
||
|
</div>
|
||
|
<a
|
||
|
href="javascript:void(0)"
|
||
|
class="main-btn btn-hover border-btn wow fadeInUp"
|
||
|
data-wow-delay=".6s"
|
||
|
>Discover More</a
|
||
|
>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== about-section end ======== -->
|
||
|
|
||
|
<!-- ======== about2-section start ======== -->
|
||
|
<section id="about" class="about-section pt-150">
|
||
|
<div class="container">
|
||
|
<div class="row align-items-center">
|
||
|
<div class="col-xl-6 col-lg-6">
|
||
|
<div class="about-content">
|
||
|
<div class="section-title mb-30">
|
||
|
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
|
||
|
Easy to Use with Tons of Awesome Features
|
||
|
</h2>
|
||
|
<p class="wow fadeInUp" data-wow-delay=".4s">
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore et dolore magna
|
||
|
aliquyam erat, sed diam voluptua.
|
||
|
</p>
|
||
|
</div>
|
||
|
<ul>
|
||
|
<li>Quick Access</li>
|
||
|
<li>Easily to Manage</li>
|
||
|
<li>24/7 Support</li>
|
||
|
</ul>
|
||
|
<a
|
||
|
href="javascript:void(0)"
|
||
|
class="main-btn btn-hover border-btn wow fadeInUp"
|
||
|
data-wow-delay=".6s"
|
||
|
>Learn More</a
|
||
|
>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-xl-6 col-lg-6 order-first order-lg-last">
|
||
|
<div class="about-img-2">
|
||
|
<img src="assets/img/about/about-2.png" alt="" class="w-100" />
|
||
|
<img
|
||
|
src="assets/img/about/about-right-shape.svg"
|
||
|
alt=""
|
||
|
class="shape shape-1"
|
||
|
/>
|
||
|
<img
|
||
|
src="assets/img/about/right-dots.svg"
|
||
|
alt=""
|
||
|
class="shape shape-2"
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== about2-section end ======== -->
|
||
|
|
||
|
<!-- ======== feature-section start ======== -->
|
||
|
<section id="why" class="feature-extended-section pt-100">
|
||
|
<div class="feature-extended-wrapper">
|
||
|
<div class="container">
|
||
|
<div class="row justify-content-center">
|
||
|
<div class="col-xxl-5 col-xl-6 col-lg-8 col-md-9">
|
||
|
<div class="section-title text-center mb-60">
|
||
|
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
|
||
|
Why Choose SaaSpal
|
||
|
</h2>
|
||
|
<p class="wow fadeInUp" data-wow-delay=".4s">
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore et dolore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-display"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>SaaS Focused</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-leaf"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Awesome Design</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-grid-alt"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Ready to Use</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-javascript"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Vanilla JS</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-layers"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Essential Sections</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-4 col-md-6">
|
||
|
<div class="single-feature-extended">
|
||
|
<div class="icon">
|
||
|
<i class="lni lni-rocket"></i>
|
||
|
</div>
|
||
|
<div class="content">
|
||
|
<h3>Highly Optimized</h3>
|
||
|
<p>
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor invidunt ut labore
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== feature-section end ======== -->
|
||
|
|
||
|
<!-- ======== subscribe-section start ======== -->
|
||
|
<section id="contact" class="subscribe-section pt-120">
|
||
|
<div class="container">
|
||
|
<div class="subscribe-wrapper img-bg">
|
||
|
<div class="row align-items-center">
|
||
|
<div class="col-xl-6 col-lg-7">
|
||
|
<div class="section-title mb-15">
|
||
|
<h2 class="text-white mb-25">Subscribe Our Newsletter</h2>
|
||
|
<p class="text-white pr-5">
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
diam nonumy eirmod tempor
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-xl-6 col-lg-5">
|
||
|
<form action="#" class="subscribe-form">
|
||
|
<input
|
||
|
type="email"
|
||
|
name="subs-email"
|
||
|
id="subs-email"
|
||
|
placeholder="Your Email"
|
||
|
/>
|
||
|
<button type="submit" class="main-btn btn-hover">
|
||
|
Subscribe
|
||
|
</button>
|
||
|
</form>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<!-- ======== subscribe-section end ======== -->
|
||
|
|
||
|
<!-- ======== footer start ======== -->
|
||
|
<footer class="footer">
|
||
|
<div class="container">
|
||
|
<div class="widget-wrapper">
|
||
|
<div class="row">
|
||
|
<div class="col-xl-4 col-lg-4 col-md-6">
|
||
|
<div class="footer-widget">
|
||
|
<div class="logo mb-30">
|
||
|
<a href="index.html">
|
||
|
<img src="assets/img/logo/logo.svg" alt="" />
|
||
|
</a>
|
||
|
</div>
|
||
|
<p class="desc mb-30 text-white">
|
||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||
|
dinonumy eirmod tempor invidunt.
|
||
|
</p>
|
||
|
<ul class="socials">
|
||
|
<li>
|
||
|
<a href="jvascript:void(0)">
|
||
|
<i class="lni lni-facebook-filled"></i>
|
||
|
</a>
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="jvascript:void(0)">
|
||
|
<i class="lni lni-twitter-filled"></i>
|
||
|
</a>
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="jvascript:void(0)">
|
||
|
<i class="lni lni-instagram-filled"></i>
|
||
|
</a>
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="jvascript:void(0)">
|
||
|
<i class="lni lni-linkedin-original"></i>
|
||
|
</a>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="col-xl-2 col-lg-2 col-md-6">
|
||
|
<div class="footer-widget">
|
||
|
<h3>About Us</h3>
|
||
|
<ul class="links">
|
||
|
<li><a href="javascript:void(0)">Home</a></li>
|
||
|
<li><a href="javascript:void(0)">Feature</a></li>
|
||
|
<li><a href="javascript:void(0)">About</a></li>
|
||
|
<li><a href="javascript:void(0)">Testimonials</a></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="col-xl-3 col-lg-3 col-md-6">
|
||
|
<div class="footer-widget">
|
||
|
<h3>Features</h3>
|
||
|
<ul class="links">
|
||
|
<li><a href="javascript:void(0)">How it works</a></li>
|
||
|
<li><a href="javascript:void(0)">Privacy policy</a></li>
|
||
|
<li><a href="javascript:void(0)">Terms of service</a></li>
|
||
|
<li><a href="javascript:void(0)">Refund policy</a></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="col-xl-3 col-lg-3 col-md-6">
|
||
|
<div class="footer-widget">
|
||
|
<h3>Other Products</h3>
|
||
|
<ul class="links">
|
||
|
<li><a href="jvascript:void(0)">Accounting Software</a></li>
|
||
|
<li><a href="jvascript:void(0)">Billing Software</a></li>
|
||
|
<li><a href="jvascript:void(0)">Booking System</a></li>
|
||
|
<li><a href="jvascript:void(0)">Tracking System</a></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</footer>
|
||
|
<!-- ======== footer end ======== -->
|
||
|
|
||
|
<!-- ======== scroll-top ======== -->
|
||
|
<a href="#" class="scroll-top btn-hover">
|
||
|
<i class="lni lni-chevron-up"></i>
|
||
|
</a>
|
||
|
|
||
|
<!-- ======== JS here ======== -->
|
||
|
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
||
|
<script src="assets/js/wow.min.js"></script>
|
||
|
<script src="assets/js/main.js"></script>
|
||
|
|
||
|
<template id="x-widget-template">
|
||
|
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.5/dist/html2canvas.min.js"></script>
|
||
|
|
||
|
<style>
|
||
|
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
|
||
|
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
|
||
|
|
||
|
:host {
|
||
|
all: initial !important;
|
||
|
position: relative !important;
|
||
|
z-index: 9998 !important;
|
||
|
--unit-spacing: 8px;
|
||
|
--icon-default-dim: 60px;
|
||
|
--form-default-width: 450px;
|
||
|
--form-default-height: 580px;
|
||
|
--side-margin: calc(var(--unit-spacing) * 2);
|
||
|
--site-falcon-purple-1: #AE55E7;
|
||
|
--site-falcon-purple-2: #a544e3;
|
||
|
--site-falcon-blue: #0098FF;
|
||
|
--site-falcon-red: #FF4031;
|
||
|
--site-falcon-grey-1: #ececec;
|
||
|
--site-falcon-grey-2: #e0e0e0;
|
||
|
--site-falcon-grey-3: #d3d3d3;
|
||
|
--site-falcon-grey-4: #5A5A5A;
|
||
|
--form-element-border-size: 2px;
|
||
|
--form-border-radius: var(--unit-spacing);
|
||
|
}
|
||
|
|
||
|
* {
|
||
|
box-sizing: border-box;
|
||
|
}
|
||
|
|
||
|
#icon {
|
||
|
width: var(--icon-default-dim);
|
||
|
height: var(--icon-default-dim);
|
||
|
position: fixed;
|
||
|
visibility: visible;
|
||
|
opacity: 1;
|
||
|
transition: top 0.3s ease 0s, left 0.3s ease 0s, scale 0.2s ease, transform 0.5s ease, visibility 0s linear 0.3s, opacity 0.3s linear;
|
||
|
}
|
||
|
|
||
|
#icon.location-top-left {
|
||
|
left: var(--side-margin);
|
||
|
top: var(--side-margin);
|
||
|
}
|
||
|
|
||
|
#icon.location-top-right {
|
||
|
left: calc(100% - var(--icon-default-dim) - var(--side-margin));
|
||
|
top: var(--side-margin);
|
||
|
}
|
||
|
|
||
|
#icon.location-bottom-left {
|
||
|
left: var(--side-margin);
|
||
|
top: calc(100% - var(--icon-default-dim) - var(--side-margin));
|
||
|
}
|
||
|
|
||
|
#icon.location-bottom-right {
|
||
|
left: calc(100% - var(--icon-default-dim) - var(--side-margin));
|
||
|
top: calc(100% - var(--icon-default-dim) - var(--side-margin));
|
||
|
}
|
||
|
|
||
|
#icon:hover {
|
||
|
scale: 1.05;
|
||
|
}
|
||
|
|
||
|
#icon > img {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
}
|
||
|
|
||
|
#form {
|
||
|
width: var(--form-default-width);
|
||
|
height: var(--form-default-height);
|
||
|
background-color: white;
|
||
|
border: 0px;
|
||
|
border-radius: var(--form-border-radius);
|
||
|
box-shadow: 0 calc(var(--unit-spacing) / 2) calc(var(--unit-spacing) * 2) rgb(0 0 0 / 25%);
|
||
|
position: fixed;
|
||
|
visibility: visible;
|
||
|
opacity: 1;
|
||
|
transition: transform 0.4s ease, visibility 0s linear 0.3s, opacity 0.3s linear;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
}
|
||
|
|
||
|
#form.location-top-left {
|
||
|
left: var(--side-margin);
|
||
|
top: var(--side-margin);
|
||
|
}
|
||
|
|
||
|
#form.location-top-right {
|
||
|
left: calc(100% - var(--form-default-width) - var(--side-margin));
|
||
|
top: var(--side-margin);
|
||
|
}
|
||
|
|
||
|
#form.location-bottom-left {
|
||
|
left: var(--side-margin);
|
||
|
top: calc(100% - var(--form-default-height) - var(--side-margin));
|
||
|
}
|
||
|
|
||
|
#form.location-bottom-right {
|
||
|
left: calc(100% - var(--form-default-width) - var(--side-margin));
|
||
|
top: calc(100% - var(--form-default-height) - var(--side-margin));
|
||
|
}
|
||
|
|
||
|
#form-header {
|
||
|
top: 0px;
|
||
|
width: 100%;
|
||
|
height: calc(var(--unit-spacing) * 8);
|
||
|
background-image: linear-gradient(var(--site-falcon-purple-2), var(--site-falcon-purple-1));
|
||
|
border-radius: var(--form-border-radius) var(--form-border-radius) 0px 0px;
|
||
|
border: 0px;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
display: flex;
|
||
|
}
|
||
|
|
||
|
.form-header-button {
|
||
|
cursor: pointer;
|
||
|
user-select: none;
|
||
|
color: white;
|
||
|
margin-left: calc(var(--unit-spacing) * 2);
|
||
|
font-size: 26px;
|
||
|
transition: scale 0.2s ease;
|
||
|
}
|
||
|
|
||
|
.form-header-button:hover {
|
||
|
scale: 1.1;
|
||
|
}
|
||
|
|
||
|
#form-button-help {
|
||
|
margin-left: calc(var(--unit-spacing) * 2);
|
||
|
}
|
||
|
|
||
|
#form-button-close {
|
||
|
margin-right: calc(var(--unit-spacing) * 2);
|
||
|
}
|
||
|
|
||
|
#form-title {
|
||
|
font-family: "Alexandria", sans-serif;
|
||
|
font-weight: 400;
|
||
|
font-size: 24px;
|
||
|
color: white;
|
||
|
text-align: center;
|
||
|
user-select: none;
|
||
|
flex-grow: 1;
|
||
|
}
|
||
|
|
||
|
#form-content-container {
|
||
|
width: 100%;
|
||
|
align-items: center;
|
||
|
padding-left: calc(var(--unit-spacing) * 4);
|
||
|
padding-right: calc(var(--unit-spacing) * 4);
|
||
|
padding: calc(var(--unit-spacing) * 4);
|
||
|
flex-grow: 1;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
justify-content: space-between;
|
||
|
}
|
||
|
|
||
|
#input-name-container {
|
||
|
width: 100%;
|
||
|
position: relative;
|
||
|
}
|
||
|
|
||
|
.md-input {
|
||
|
width: 100%;
|
||
|
border: var(--form-element-border-size) solid var(--site-falcon-grey-3);
|
||
|
border-radius: calc(var(--unit-spacing) / 2);
|
||
|
padding: 16px;
|
||
|
}
|
||
|
|
||
|
#input-name {
|
||
|
height: calc(var(--unit-spacing) * 6);
|
||
|
}
|
||
|
|
||
|
.label-md-input {
|
||
|
position: absolute;
|
||
|
top: 0;
|
||
|
left: calc(var(--unit-spacing) * 2);
|
||
|
display: flex;
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
|
||
|
#input-name + .label-md-input {
|
||
|
bottom: 0;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
#input-description + .label-md-input {
|
||
|
margin-top: calc(var(--unit-spacing) * 2);
|
||
|
}
|
||
|
|
||
|
#input-name:not(#input-name[value=""]), #input-description:not(#input-description[value=""]) {
|
||
|
font-size: 16px;
|
||
|
}
|
||
|
|
||
|
.md-input, .label-md-input > div {
|
||
|
font-family: "Alexandria", sans-serif;
|
||
|
font-weight: 300;
|
||
|
font-size: 16px;
|
||
|
}
|
||
|
|
||
|
.label-md-input > div {
|
||
|
transition: all 0.15s ease-out, visibility 0s;
|
||
|
color: grey;
|
||
|
}
|
||
|
|
||
|
.md-input:focus {
|
||
|
outline: none;
|
||
|
border: var(--form-element-border-size) solid var(--site-falcon-purple-1);
|
||
|
}
|
||
|
|
||
|
#input-name:focus + .label-md-input > div, #input-name:not(#input-name[value=""]) + .label-md-input > div {
|
||
|
font-size: 14px;
|
||
|
transform: translate(0, -150%);
|
||
|
background-color: white;
|
||
|
padding-left: calc(var(--unit-spacing) / 2);
|
||
|
padding-right: calc(var(--unit-spacing) / 2);
|
||
|
}
|
||
|
|
||
|
#input-description:focus + .label-md-input > div, #input-description:not(#input-description[value=""]) + .label-md-input > div {
|
||
|
font-size: 14px;
|
||
|
transform: translate(0, -150%);
|
||
|
background-color: white;
|
||
|
padding-left: calc(var(--unit-spacing) / 2);
|
||
|
padding-right: calc(var(--unit-spacing) / 2);
|
||
|
}
|
||
|
|
||
|
.md-input:focus + .label-md-input > div {
|
||
|
color: var(--site-falcon-purple-1);
|
||
|
}
|
||
|
|
||
|
#select-severity-container {
|
||
|
width: 100%;
|
||
|
display: grid;
|
||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||
|
grid-column-gap: 0px;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
.severity-option {
|
||
|
margin: 0px;
|
||
|
height: calc(var(--unit-spacing) * 6);
|
||
|
background-color: var(--site-falcon-grey-1);
|
||
|
border: var(--form-element-border-size) solid var(--site-falcon-grey-3);
|
||
|
border-left: 0px;
|
||
|
display: flex;
|
||
|
flex-direction: row;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
cursor: pointer;
|
||
|
user-select: none;
|
||
|
transition: background-color 0.15s ease;
|
||
|
}
|
||
|
|
||
|
.severity-option .material-symbols-rounded {
|
||
|
color: var(--site-falcon-grey-4);
|
||
|
font-size: 26px;
|
||
|
}
|
||
|
|
||
|
.severity-option .material-symbols-rounded:not(:first-child) {
|
||
|
margin-left: -14px;
|
||
|
}
|
||
|
|
||
|
.severity-option:hover:not(.selected) {
|
||
|
background-color: var(--site-falcon-grey-2);
|
||
|
}
|
||
|
|
||
|
.severity-option.selected {
|
||
|
background-color: var(--site-falcon-grey-3);
|
||
|
}
|
||
|
|
||
|
#select-severity-container > :first-child {
|
||
|
border-left: var(--form-element-border-size) solid var(--site-falcon-grey-3);
|
||
|
border-radius: calc(var(--unit-spacing) / 2) 0px 0px calc(var(--unit-spacing) / 2);
|
||
|
}
|
||
|
|
||
|
#select-severity-container :last-child {
|
||
|
border-radius: 0px calc(var(--unit-spacing) / 2) calc(var(--unit-spacing) / 2) 0px;
|
||
|
}
|
||
|
|
||
|
#input-description-container {
|
||
|
width: 100%;
|
||
|
position: relative;
|
||
|
}
|
||
|
|
||
|
#input-description {
|
||
|
height: calc(var(--unit-spacing) * 19);
|
||
|
resize: none;
|
||
|
}
|
||
|
|
||
|
#icon:not(.hidden), #form:not(.hidden) {
|
||
|
transition-delay: 0s;
|
||
|
}
|
||
|
|
||
|
#icon.hidden, #form.hidden {
|
||
|
pointer-events: none;
|
||
|
visibility: hidden;
|
||
|
opacity: 0;
|
||
|
}
|
||
|
|
||
|
#icon.hidden.location-bottom-left, #form.hidden.location-bottom-left,
|
||
|
#icon.hidden.location-bottom-right, #form.hidden.location-bottom-right {
|
||
|
transform: translateY(100%);
|
||
|
}
|
||
|
|
||
|
#icon.hidden.location-top-left, #form.hidden.location-top-left,
|
||
|
#icon.hidden.location-top-right, #form.hidden.location-top-right {
|
||
|
transform: translateY(-100%);
|
||
|
}
|
||
|
|
||
|
#icon.hidden {
|
||
|
scale: 1.05;
|
||
|
}
|
||
|
|
||
|
#container-snapshot {
|
||
|
width: 100%;
|
||
|
display: grid;
|
||
|
grid-template-columns: 1fr;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
#container-snapshot.snapshot-taken {
|
||
|
grid-template-columns: 7fr 1fr 1fr;
|
||
|
}
|
||
|
|
||
|
#container-snapshot .material-symbols-rounded.snapshot-taken {
|
||
|
display: block;
|
||
|
user-select: none;
|
||
|
}
|
||
|
|
||
|
#container-snapshot .material-symbols-rounded:hover {
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
#button-snapshot {
|
||
|
width: 100%;
|
||
|
height: calc(var(--unit-spacing) * 6);
|
||
|
border-radius: calc(var(--unit-spacing) / 2);
|
||
|
border: var(--form-element-border-size) dashed var(--site-falcon-grey-3);
|
||
|
background-color: var(--site-falcon-grey-1);
|
||
|
color: var(--site-falcon-grey-4);
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
cursor: pointer;
|
||
|
user-select: none;
|
||
|
transition: background-color 0.15s ease;
|
||
|
}
|
||
|
|
||
|
#button-snapshot .material-symbols-outlined {
|
||
|
font-size: 30px;
|
||
|
}
|
||
|
|
||
|
#button-snapshot:hover {
|
||
|
background-color: var(--site-falcon-grey-3);
|
||
|
}
|
||
|
|
||
|
#container-snapshot > .material-symbols-rounded {
|
||
|
display: none;
|
||
|
margin-left: auto;
|
||
|
font-size: 30px;
|
||
|
}
|
||
|
|
||
|
#button-view-snapshot {
|
||
|
color: var(--site-falcon-blue);
|
||
|
}
|
||
|
|
||
|
#button-delete-snapshot {
|
||
|
color: var(--site-falcon-red);
|
||
|
}
|
||
|
|
||
|
#footer-buttons-container {
|
||
|
width: 100%;
|
||
|
display: flex;
|
||
|
flex-direction: row;
|
||
|
}
|
||
|
|
||
|
#footer-buttons-container .form-footer-button {
|
||
|
width: 46%;
|
||
|
height: calc(var(--unit-spacing) * 6);
|
||
|
border-radius: calc(var(--unit-spacing) / 2);
|
||
|
font-family: "Alexandria", sans-serif;
|
||
|
font-weight: 400;
|
||
|
font-size: 20px;
|
||
|
border: 0px;
|
||
|
cursor: pointer;
|
||
|
user-select: none;
|
||
|
transition: background-color 0.15s ease;
|
||
|
}
|
||
|
|
||
|
#button-clear-form {
|
||
|
margin-right: auto;
|
||
|
color: var(--site-falcon-grey-4);
|
||
|
background-color: var(--site-falcon-grey-1);
|
||
|
}
|
||
|
|
||
|
#button-clear-form:hover {
|
||
|
background-color: var(--site-falcon-grey-2);
|
||
|
}
|
||
|
|
||
|
#button-submit {
|
||
|
margin-left: auto;
|
||
|
color: white;
|
||
|
background-color: var(--site-falcon-purple-1);
|
||
|
}
|
||
|
|
||
|
#button-submit:hover {
|
||
|
background-color: var(--site-falcon-purple-2);
|
||
|
}
|
||
|
|
||
|
#canvas-snapshot {
|
||
|
position: fixed;
|
||
|
left: 0px;
|
||
|
top: 0px;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
visibility: hidden;
|
||
|
cursor: crosshair;
|
||
|
}
|
||
|
|
||
|
#canvas-snapshot.visible {
|
||
|
visibility: visible;
|
||
|
}
|
||
|
</style>
|
||
|
|
||
|
<canvas id="canvas-snapshot">
|
||
|
|
||
|
</canvas>
|
||
|
|
||
|
<div id="icon">
|
||
|
<img src="icon_open.png">
|
||
|
</div>
|
||
|
|
||
|
<div id="form">
|
||
|
<div id="form-header">
|
||
|
<span title="View Help" id="form-button-help" class="material-symbols-rounded form-header-button">
|
||
|
help
|
||
|
</span>
|
||
|
<div id="form-title">
|
||
|
Add Feedback
|
||
|
</div>
|
||
|
<span id="form-button-close" class="material-symbols-rounded form-header-button">
|
||
|
close
|
||
|
</span>
|
||
|
</div>
|
||
|
<div id="form-content-container">
|
||
|
<div id="select-severity-container">
|
||
|
<div title="General Comment" class="severity-option selected" id="option-no-severity">
|
||
|
<span class="material-symbols-rounded">
|
||
|
info
|
||
|
</span>
|
||
|
</div>
|
||
|
<div title="Low Priority" class="severity-option" id="option-low-severity">
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
</div>
|
||
|
<div title="Medium Priority" class="severity-option" id="option-medium-severity">
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
</div>
|
||
|
<div title="High Priority" class="severity-option" id="option-high-severity">
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
<span class="material-symbols-rounded">
|
||
|
priority_high
|
||
|
</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="input-name-container">
|
||
|
<input
|
||
|
type="text"
|
||
|
id="input-name"
|
||
|
class="md-input"
|
||
|
name="name"
|
||
|
value=""
|
||
|
aria-labelledby="label-input-name"
|
||
|
/>
|
||
|
<label for="name" class="label-md-input">
|
||
|
<div>Name</div>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div id="input-description-container">
|
||
|
<textarea
|
||
|
type="text"
|
||
|
id="input-description"
|
||
|
class="md-input"
|
||
|
name="description"
|
||
|
value=""
|
||
|
aria-labelledby="label-input-description"
|
||
|
></textarea>
|
||
|
<label for="description" class="label-md-input">
|
||
|
<div>Description</div>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div id="container-snapshot">
|
||
|
<button title="Take Snapshot" id="button-snapshot" class="taken">
|
||
|
<span class="material-symbols-outlined">
|
||
|
photo_camera
|
||
|
</span>
|
||
|
</button>
|
||
|
<span title="View Snapshot" id="button-view-snapshot" class="material-symbols-rounded">
|
||
|
image
|
||
|
</span>
|
||
|
<span title="Delete Snapshot" id="button-delete-snapshot" class="material-symbols-rounded">
|
||
|
delete
|
||
|
</span>
|
||
|
</div>
|
||
|
<div id="footer-buttons-container">
|
||
|
<button id="button-clear-form" class="form-footer-button">
|
||
|
Clear
|
||
|
</button>
|
||
|
<button id="button-submit" class="form-footer-button">
|
||
|
Submit
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
class XWidget extends HTMLElement {
|
||
|
#icon;
|
||
|
#form;
|
||
|
#canvas;
|
||
|
#canvasCtx;
|
||
|
#location;
|
||
|
|
||
|
constructor() {
|
||
|
super();
|
||
|
|
||
|
const shadowRoot = this.attachShadow({mode: 'open'});
|
||
|
shadowRoot.appendChild(document.getElementById("x-widget-template").content.cloneNode(true));
|
||
|
|
||
|
this.#icon = this.#createIcon();
|
||
|
this.#form = this.#createForm();
|
||
|
this.#canvas = this.#createCanvas();
|
||
|
this.#canvasCtx = this.#canvas.getContext("2d");
|
||
|
|
||
|
this.#location = "bottom-right";
|
||
|
}
|
||
|
|
||
|
connectedCallback() {
|
||
|
this.#renderInitial();
|
||
|
|
||
|
window.addEventListener("keydown", (e) => this.#windowKeyPressed(e));
|
||
|
}
|
||
|
|
||
|
#renderInitial() {
|
||
|
if (this.isConnected) {
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#createIcon() {
|
||
|
let icon = this.shadowRoot.getElementById("icon");
|
||
|
|
||
|
icon.addEventListener("pointerdown", (e) => this.#onIconPointerDown(e));
|
||
|
icon.addEventListener("pointermove", (e) => this.#onIconPointerMove(e));
|
||
|
icon.addEventListener("pointerup", (e) => this.#onIconPointerUp(e));
|
||
|
icon.addEventListener("pointerover", (e) => this.#onIconPointerOver(e));
|
||
|
icon.addEventListener("pointerout", (e) => this.#onIconPointerOut(e));
|
||
|
|
||
|
icon.pointerDown = false;
|
||
|
icon.moving = false;
|
||
|
|
||
|
return icon;
|
||
|
}
|
||
|
|
||
|
#createCanvas() {
|
||
|
let canvas = this.shadowRoot.getElementById("canvas-snapshot");
|
||
|
|
||
|
canvas.addEventListener("pointerdown", (e) => this.#onCanvasPointerDown(e));
|
||
|
canvas.addEventListener("pointermove", (e) => this.#onCanvasPointerMove(e));
|
||
|
canvas.addEventListener("pointerup", async (e) => await this.#onCanvasPointerUpAsync(e));
|
||
|
|
||
|
canvas.pointerDown = null;
|
||
|
canvas.initialX = null;
|
||
|
canvas.initialY = null;
|
||
|
|
||
|
return canvas;
|
||
|
}
|
||
|
|
||
|
#createForm() {
|
||
|
let form = this.shadowRoot.getElementById("form");
|
||
|
|
||
|
let buttonHelp = this.shadowRoot.getElementById("form-button-help");
|
||
|
buttonHelp.addEventListener("click", (e) => this.#onFormHelpButtonClicked(e));
|
||
|
|
||
|
let buttonClose = this.shadowRoot.getElementById("form-button-close");
|
||
|
buttonClose.addEventListener("click", (e) => this.#onFormCloseButtonClicked(e));
|
||
|
|
||
|
let inputName = this.shadowRoot.getElementById("input-name");
|
||
|
inputName.addEventListener("input", (e) => this.#onNameInput(e));
|
||
|
|
||
|
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
|
||
|
|
||
|
for (let i = 0; i < selectSeverityContainer.childElementCount; i++) {
|
||
|
let severityOption = selectSeverityContainer.children[i];
|
||
|
severityOption.addEventListener("click", (e) => this.#OnSeverityOptionClicked(e));
|
||
|
}
|
||
|
|
||
|
let inputDescription = this.shadowRoot.getElementById("input-description");
|
||
|
inputDescription.addEventListener("input", (e) => this.#onDescriptionInput(e));
|
||
|
|
||
|
let buttonSnapshot = this.shadowRoot.getElementById("button-snapshot");
|
||
|
buttonSnapshot.addEventListener("click", (e) => this.#onSnapshotButtonClicked(e));
|
||
|
|
||
|
let buttonViewSnapshot = this.shadowRoot.getElementById("button-view-snapshot");
|
||
|
buttonViewSnapshot.addEventListener("click", (e) => this.#onViewSnapshotButtonClicked(e));
|
||
|
|
||
|
let buttonDeleteSnapshot = this.shadowRoot.getElementById("button-delete-snapshot");
|
||
|
buttonDeleteSnapshot.addEventListener("click", (e) => this.#onDeleteSnapshotButtonClicked(e));
|
||
|
|
||
|
let buttonClearForm = this.shadowRoot.getElementById("button-clear-form");
|
||
|
buttonClearForm.addEventListener("click", (e) => this.#onClearFormButtonClicked(e));
|
||
|
|
||
|
let buttonSubmit = this.shadowRoot.getElementById("button-submit");
|
||
|
buttonSubmit.addEventListener("click", (e) => this.#onSubmitButtonClicked(e));
|
||
|
|
||
|
return form;
|
||
|
}
|
||
|
|
||
|
#renderIcon(parameters={}) {
|
||
|
if (this.#location == "top-left") {
|
||
|
removeClassByPrefix(this.#icon, "location");
|
||
|
this.#icon.classList.add("location-top-left");
|
||
|
}
|
||
|
else if (this.#location == "top-right") {
|
||
|
removeClassByPrefix(this.#icon, "location");
|
||
|
this.#icon.classList.add("location-top-right");
|
||
|
}
|
||
|
else if (this.#location == "bottom-left") {
|
||
|
removeClassByPrefix(this.#icon, "location");
|
||
|
this.#icon.classList.add("location-bottom-left");
|
||
|
}
|
||
|
else if (this.#location == "bottom-right") {
|
||
|
removeClassByPrefix(this.#icon, "location");
|
||
|
this.#icon.classList.add("location-bottom-right");
|
||
|
}
|
||
|
|
||
|
if (parameters["visibility"]) {
|
||
|
if (parameters["visibility"] == "visible") {
|
||
|
this.#icon.classList.remove("hidden");
|
||
|
}
|
||
|
else if (parameters["visibility"] == "hidden") {
|
||
|
this.#icon.classList.add("hidden");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#renderForm(parameters={}) {
|
||
|
if (this.#location == "top-left") {
|
||
|
removeClassByPrefix(this.#form, "location");
|
||
|
this.#form.classList.add("location-top-left");
|
||
|
} else if (this.#location == "top-right") {
|
||
|
removeClassByPrefix(this.#form, "location");
|
||
|
this.#form.classList.add("location-top-right");
|
||
|
} else if (this.#location == "bottom-left") {
|
||
|
removeClassByPrefix(this.#form, "location");
|
||
|
this.#form.classList.add("location-bottom-left");
|
||
|
} else if (this.#location == "bottom-right") {
|
||
|
removeClassByPrefix(this.#form, "location");
|
||
|
this.#form.classList.add("location-bottom-right");
|
||
|
}
|
||
|
|
||
|
if (parameters["visibility"]) {
|
||
|
if (parameters["visibility"] == "visible") {
|
||
|
this.#form.classList.remove("hidden");
|
||
|
}
|
||
|
else if (parameters["visibility"] == "hidden") {
|
||
|
this.#form.classList.add("hidden");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#onFormHelpButtonClicked(e) {
|
||
|
console.log("help pressed");
|
||
|
}
|
||
|
|
||
|
#onFormCloseButtonClicked(e) {
|
||
|
if (!this.#form.classList.contains("hidden")) {
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#onNameInput(e) {
|
||
|
let inputName = this.shadowRoot.getElementById("input-name");
|
||
|
|
||
|
inputName.setAttribute('value', inputName.value);
|
||
|
}
|
||
|
|
||
|
#onDescriptionInput(e) {
|
||
|
let inputDescription = this.shadowRoot.getElementById("input-description");
|
||
|
|
||
|
inputDescription.setAttribute('value', inputDescription.value);
|
||
|
}
|
||
|
|
||
|
#OnSeverityOptionClicked(e) {
|
||
|
let selectedSeverityOption = e.currentTarget;
|
||
|
|
||
|
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
|
||
|
|
||
|
for (let i = 0; i < selectSeverityContainer.childElementCount; i++) {
|
||
|
let severityOption = selectSeverityContainer.children[i];
|
||
|
|
||
|
if (severityOption == selectedSeverityOption) {
|
||
|
if (!severityOption.classList.contains("selected")) {
|
||
|
severityOption.classList.add("selected");
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
severityOption.classList.remove("selected");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#onSnapshotButtonClicked(e) {
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
|
||
|
this.#setBlankCanvas();
|
||
|
this.#canvas.classList.add("visible");
|
||
|
}
|
||
|
|
||
|
#setBlankCanvas() {
|
||
|
this.#canvas.width = this.#canvas.clientWidth;
|
||
|
this.#canvas.height = this.#canvas.clientHeight;
|
||
|
|
||
|
this.#canvasCtx.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
|
||
|
|
||
|
this.#canvasCtx.globalAlpha = 0.5;
|
||
|
this.#canvasCtx.fillStyle = "white";
|
||
|
this.#canvasCtx.beginPath();
|
||
|
this.#canvasCtx.rect(0, 0, this.#canvas.width, this.#canvas.height);
|
||
|
this.#canvasCtx.fill();
|
||
|
this.#canvasCtx.closePath();
|
||
|
|
||
|
this.#setCanvasBrush(2, "#FF4031");
|
||
|
}
|
||
|
|
||
|
#setCanvasBrush(width, colour) {
|
||
|
this.#canvasCtx.globalAlpha = 1;
|
||
|
this.#canvasCtx.lineWidth = width;
|
||
|
this.#canvasCtx.strokeStyle = colour;
|
||
|
}
|
||
|
|
||
|
#onCanvasPointerDown(e) {
|
||
|
if (e.button !== 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
this.#canvas.initialX = e.offsetX;
|
||
|
this.#canvas.initialY = e.offsetY;
|
||
|
this.#canvas.pointerDown = true;
|
||
|
|
||
|
this.#renderIcon({visibility: "hidden"});
|
||
|
|
||
|
this.#canvas.setPointerCapture(e.pointerId);
|
||
|
}
|
||
|
|
||
|
#onCanvasPointerMove(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if (this.#canvas.pointerDown) {
|
||
|
let offsetX;
|
||
|
let offsetY;
|
||
|
|
||
|
if (e.offsetX < 0) {
|
||
|
offsetX = 0;
|
||
|
}
|
||
|
else if (e.offsetX > this.#canvas.width - 1) {
|
||
|
offsetX = this.#canvas.width - 1;
|
||
|
}
|
||
|
else {
|
||
|
offsetX = e.offsetX;
|
||
|
}
|
||
|
|
||
|
if (e.offsetY < 0) {
|
||
|
offsetY = 0;
|
||
|
}
|
||
|
else if (e.offsetY > this.#canvas.height - 1) {
|
||
|
offsetY = this.#canvas.height - 1;
|
||
|
}
|
||
|
else {
|
||
|
offsetY = e.offsetY;
|
||
|
}
|
||
|
|
||
|
let width = offsetX - this.#canvas.initialX;
|
||
|
let height = offsetY - this.#canvas.initialY;
|
||
|
|
||
|
this.#setBlankCanvas();
|
||
|
this.#canvasCtx.beginPath();
|
||
|
this.#canvasCtx.clearRect(this.#canvas.initialX, this.#canvas.initialY, width, height);
|
||
|
this.#canvasCtx.rect(this.#canvas.initialX, this.#canvas.initialY, width, height);
|
||
|
this.#canvasCtx.stroke();
|
||
|
this.#canvasCtx.closePath();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async #onCanvasPointerUpAsync(e) {
|
||
|
if (e.button !== 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if (this.#canvas.pointerDown) {
|
||
|
let offsetX;
|
||
|
let offsetY;
|
||
|
|
||
|
if (e.offsetX < 0) {
|
||
|
offsetX = 0;
|
||
|
}
|
||
|
else if (e.offsetX > this.#canvas.width - 1) {
|
||
|
offsetX = this.#canvas.width - 1;
|
||
|
}
|
||
|
else {
|
||
|
offsetX = e.offsetX;
|
||
|
}
|
||
|
|
||
|
if (e.offsetY < 0) {
|
||
|
offsetY = 0;
|
||
|
}
|
||
|
else if (e.offsetY > this.#canvas.height - 1) {
|
||
|
offsetY = this.#canvas.height - 1;
|
||
|
}
|
||
|
else {
|
||
|
offsetY = e.offsetY;
|
||
|
}
|
||
|
|
||
|
if (Math.abs(offsetX - this.#canvas.initialX) > 10 && Math.abs(offsetY - this.#canvas.initialY) > 10) {
|
||
|
await this.#takeSnapshotAsync(offsetX, offsetY);
|
||
|
|
||
|
this.shadowRoot.getElementById("container-snapshot").classList.add("snapshot-taken");
|
||
|
this.shadowRoot.getElementById("button-view-snapshot").classList.add("snapshot-taken");
|
||
|
this.shadowRoot.getElementById("button-delete-snapshot").classList.add("snapshot-taken");
|
||
|
}
|
||
|
|
||
|
this.#canvas.initialX = null;
|
||
|
this.#canvas.initialY = null;
|
||
|
this.#canvas.pointerDown = false;
|
||
|
|
||
|
this.#canvas.classList.remove("visible");
|
||
|
this.#setBlankCanvas();
|
||
|
this.#renderForm({visibility: "visible"});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async #takeSnapshotAsync(offsetX, offsetY) {
|
||
|
let snapshotCanvas = await html2canvas(document.body, {
|
||
|
width: Math.abs(this.#canvas.initialX - offsetX),
|
||
|
height: Math.abs(this.#canvas.initialY - offsetY),
|
||
|
x: Math.min(this.#canvas.initialX, offsetX),
|
||
|
y: Math.min(this.#canvas.initialY, offsetY)
|
||
|
})
|
||
|
|
||
|
let base64image = snapshotCanvas.toDataURL("image/png");
|
||
|
this.#form.snapshotBase64 = base64image;
|
||
|
}
|
||
|
|
||
|
#onViewSnapshotButtonClicked(e) {
|
||
|
if (this.#form.snapshotBase64) {
|
||
|
let image = new Image();
|
||
|
image.src = this.#form.snapshotBase64;
|
||
|
|
||
|
let w = window.open("_blank");
|
||
|
w.document.write(image.outerHTML);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#onDeleteSnapshotButtonClicked(e) {
|
||
|
this.#deleteSnapshot();
|
||
|
}
|
||
|
|
||
|
#deleteSnapshot() {
|
||
|
this.#form.snapshot = null;
|
||
|
|
||
|
this.shadowRoot.getElementById("container-snapshot").classList.remove("snapshot-taken");
|
||
|
this.shadowRoot.getElementById("button-view-snapshot").classList.remove("snapshot-taken");
|
||
|
this.shadowRoot.getElementById("button-delete-snapshot").classList.remove("snapshot-taken");
|
||
|
}
|
||
|
|
||
|
#onClearFormButtonClicked(e){
|
||
|
let inputName = this.shadowRoot.getElementById("input-name");
|
||
|
inputName.value = null;
|
||
|
inputName.setAttribute("value", "");
|
||
|
|
||
|
this.#resetForm();
|
||
|
}
|
||
|
|
||
|
#onSubmitButtonClicked(e) {
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
this.#resetForm();
|
||
|
}
|
||
|
|
||
|
#resetForm() {
|
||
|
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
|
||
|
|
||
|
selectSeverityContainer.children[0].classList.add("selected");
|
||
|
|
||
|
for (let i = 1; i < selectSeverityContainer.childElementCount; i++) {
|
||
|
let severityOption = selectSeverityContainer.children[i];
|
||
|
severityOption.classList.remove("selected");
|
||
|
}
|
||
|
|
||
|
let inputDescription = this.shadowRoot.getElementById("input-description");
|
||
|
inputDescription.value = null;
|
||
|
inputDescription.setAttribute("value", "");
|
||
|
|
||
|
this.#deleteSnapshot();
|
||
|
}
|
||
|
|
||
|
#onIconPointerOver(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
}
|
||
|
|
||
|
#onIconPointerOut(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
}
|
||
|
|
||
|
#onIconPointerDown(e) {
|
||
|
if (e.button !== 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
this.#icon.pointerDown = true;
|
||
|
|
||
|
this.#icon.setPointerCapture(e.pointerId);
|
||
|
}
|
||
|
|
||
|
#onIconPointerMove(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if (this.#icon.pointerDown) {
|
||
|
this.#icon.moving = true;
|
||
|
this.#icon.style.cursor = "grabbing";
|
||
|
this.#snapToCorner(e.clientX, e.clientY);
|
||
|
}
|
||
|
else {
|
||
|
this.#icon.style.cursor = "pointer";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#onIconPointerUp(e) {
|
||
|
if (e.button !== 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
this.#icon.style.cursor = "pointer";
|
||
|
this.#canvas.classList.remove("visible");
|
||
|
|
||
|
if (this.#icon.pointerDown && !this.#icon.moving) {
|
||
|
if (this.#form.classList.contains("hidden")) {
|
||
|
this.#renderIcon({visibility: "hidden"});
|
||
|
this.#renderForm({visibility: "visible"});
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
this.#icon.moving = false;
|
||
|
}
|
||
|
|
||
|
this.#icon.pointerDown = false;
|
||
|
|
||
|
this.#icon.releasePointerCapture(e.pointerId);
|
||
|
}
|
||
|
|
||
|
#snapToCorner(anchorX, anchorY) {
|
||
|
let locationChanged = false;
|
||
|
|
||
|
if (anchorX < window.innerWidth / 2) {
|
||
|
if (this.#location != "top-left" && anchorY < window.innerHeight / 2) {
|
||
|
this.#location = "top-left";
|
||
|
locationChanged = true;
|
||
|
}
|
||
|
else if (this.#location != "bottom-left" && anchorY > window.innerHeight - window.innerHeight / 2) {
|
||
|
this.#location = "bottom-left";
|
||
|
locationChanged = true;
|
||
|
}
|
||
|
}
|
||
|
else if (anchorX > window.innerWidth - window.innerWidth / 2) {
|
||
|
if (this.#location != "top-right" && anchorY < window.innerHeight / 2) {
|
||
|
this.#location = "top-right";
|
||
|
locationChanged = true;
|
||
|
}
|
||
|
else if (this.#location != "bottom-right" && anchorY > window.innerHeight - window.innerHeight / 2) {
|
||
|
this.#location = "bottom-right";
|
||
|
locationChanged = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (locationChanged) {
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#windowKeyPressed(e) {
|
||
|
if (e.keyCode == 27 && this.#canvas.classList.contains("visible")) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
this.#renderIcon({visibility: "hidden"});
|
||
|
this.#renderForm({visibility: "visible"});
|
||
|
|
||
|
this.#setBlankCanvas();
|
||
|
this.#canvas.classList.remove("visible");
|
||
|
this.#canvas.pointerDown = false;
|
||
|
}
|
||
|
else if (e.keyCode == 77 && e.ctrlKey) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
this.#renderIcon({visibility: "visible"});
|
||
|
this.#renderForm({visibility: "hidden"});
|
||
|
|
||
|
this.#setBlankCanvas();
|
||
|
this.#canvas.classList.add("visible");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function removeClassByPrefix(el, prefix) {
|
||
|
if (!el.classList) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let filteredClassList = [];
|
||
|
|
||
|
for (let i = 0; i < el.classList.length; i++) {
|
||
|
if (!el.classList[i].startsWith(prefix)) {
|
||
|
filteredClassList.push(el.classList[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
el.className = "";
|
||
|
|
||
|
for (let i = 0; i < filteredClassList.length; i++) {
|
||
|
el.classList.add(filteredClassList[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
customElements.define("x-widget", XWidget);
|
||
|
document.body.appendChild(document.createElement("x-widget"));
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|