added files
This commit is contained in:
parent
275839a2f7
commit
981dcb3789
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/*.log
|
54050
package-lock.json
generated
Normal file
54050
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
package.json
Normal file
44
package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "doorlock_pwa",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "preact build",
|
||||||
|
"serve": "sirv build --port 8080 --cors --single",
|
||||||
|
"dev": "preact watch",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "preact",
|
||||||
|
"ignorePatterns": [
|
||||||
|
"build/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"enzyme": "^3.10.0",
|
||||||
|
"enzyme-adapter-preact-pure": "^2.0.0",
|
||||||
|
"eslint": "^8.10.0",
|
||||||
|
"eslint-config-preact": "^1.1.0",
|
||||||
|
"jest": "^27.5.1",
|
||||||
|
"jest-preset-preact": "^1.0.0",
|
||||||
|
"node-sass": "^6.0.1",
|
||||||
|
"preact-cli": "^3.3.5",
|
||||||
|
"sass-loader": "^10.2.1",
|
||||||
|
"sirv-cli": "1.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"preact": "^10.3.2",
|
||||||
|
"preact-render-to-string": "^5.1.4",
|
||||||
|
"preact-router": "^3.2.1",
|
||||||
|
"sass": "^1.49.9"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-preset-preact",
|
||||||
|
"setupFiles": [
|
||||||
|
"<rootDir>/tests/__mocks__/browserMocks.js",
|
||||||
|
"<rootDir>/tests/__mocks__/setupTests.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
0
preact.config.js
Normal file
0
preact.config.js
Normal file
BIN
src/assets/favicon.ico
Normal file
BIN
src/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
10
src/assets/icons/delete-icon3.svg
Normal file
10
src/assets/icons/delete-icon3.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.2" baseProfile="tiny" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px" y="0px" viewBox="0 0 500 500" overflow="visible" xml:space="preserve">
|
||||||
|
<path fill="#CCCCCC" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M349.5,421.5h-199c-8.28,0-15-6.72-15-15
|
||||||
|
v-255h229v255C364.5,414.78,357.78,421.5,349.5,421.5z"/>
|
||||||
|
<path fill="#CCCCCC" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M386.81,99.5H314.5V80.38
|
||||||
|
c0-9.88-8.01-17.88-17.88-17.88h-92.24c-9.88,0-17.88,8.01-17.88,17.88V99.5h-73.31c-5.35,0-9.69,4.34-9.69,9.69v20.62
|
||||||
|
c0,5.35,4.34,9.69,9.69,9.69h273.62c5.35,0,9.69-4.34,9.69-9.69v-20.62C396.5,103.84,392.16,99.5,386.81,99.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 874 B |
12
src/assets/icons/edit-icon3.svg
Normal file
12
src/assets/icons/edit-icon3.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.2" baseProfile="tiny" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px" y="0px" viewBox="0 0 500 500" overflow="visible" xml:space="preserve">
|
||||||
|
<polyline fill="#CCCCCC" stroke="#000000" stroke-miterlimit="10" points="181.5,395.5 86.5,395.5 86.5,300.5 "/>
|
||||||
|
<g>
|
||||||
|
|
||||||
|
<rect x="355.37" y="40.63" transform="matrix(0.7071 -0.7071 0.7071 0.7071 31.9147 294.7168)" fill="#CCCCCC" stroke="#000000" stroke-width="1" stroke-miterlimit="9.9999" width="32.69" height="136.4"/>
|
||||||
|
|
||||||
|
<rect x="94.59" y="168.11" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -95.5641 241.9096)" fill="#CCCCCC" stroke="#000000" stroke-width="1" stroke-miterlimit="9.9999" width="299.29" height="136.4"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 881 B |
23
src/components/Pageselector/index.jsx
Normal file
23
src/components/Pageselector/index.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { h } from "preact";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Pageselector = (props) => {
|
||||||
|
|
||||||
|
let start = 1;
|
||||||
|
let end = 7;
|
||||||
|
return (
|
||||||
|
<div class="page-nav-bar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">1</a></li>
|
||||||
|
<li><a href="#">2</a></li>
|
||||||
|
<li><a href="#">3</a></li>
|
||||||
|
<li><a href="#">4</a></li>
|
||||||
|
<li><a href="#">5</a></li>
|
||||||
|
<li><a href="#">></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pageselector;
|
23
src/components/app.js
Normal file
23
src/components/app.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createRef, h } from 'preact';
|
||||||
|
import { Router } from 'preact-router';
|
||||||
|
import Header from './header';
|
||||||
|
import Login from '../routes/login';
|
||||||
|
import Profile from '../routes/profile';
|
||||||
|
import Menu from './menu';
|
||||||
|
import Users from '../routes/users';
|
||||||
|
const menu = createRef();
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<div id="wrapper">
|
||||||
|
<Header menu={menu} />
|
||||||
|
<Menu ref={menu} />
|
||||||
|
<Login />
|
||||||
|
<Router>
|
||||||
|
<Profile path="/profile/" user="me" />
|
||||||
|
<Profile path="/profile/:user" />
|
||||||
|
<Users path="/users" />
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default App;
|
10
src/components/breadcrumbs/index.jsx
Normal file
10
src/components/breadcrumbs/index.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import {h} from 'preact';
|
||||||
|
|
||||||
|
const Breadcrumbs = (props) =>{ if(props.items) return(
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<ul>
|
||||||
|
{props.items.map((text,i)=><li key={i}><a href="#">{text}</a> </li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
export default Breadcrumbs;
|
37
src/components/header/index.jsx
Normal file
37
src/components/header/index.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Component, createRef, h } from 'preact';
|
||||||
|
//import { Link } from 'preact-router/match';
|
||||||
|
|
||||||
|
class Header extends Component {
|
||||||
|
state = { shown: true }
|
||||||
|
hamburger = createRef();
|
||||||
|
togglemenu() {
|
||||||
|
if (this.props.menu.current) {
|
||||||
|
this.setState(prev => ({ shown: !prev.shown }));
|
||||||
|
this.props.menu.current.toggle(this.state);
|
||||||
|
if (this.state.shown) {
|
||||||
|
this.hamburger.current.classList.add('hamburger-active');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.hamburger.current.classList.remove('hamburger-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(props){
|
||||||
|
return (
|
||||||
|
<header className='header'>
|
||||||
|
<div className="container">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<div id="hamburger-button" ref={this.hamburger} className='hamburger' onClick={() => this.togglemenu(props)}>
|
||||||
|
<hr />
|
||||||
|
<hr />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Header;
|
37
src/components/menu/index.jsx
Normal file
37
src/components/menu/index.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Component, h } from 'preact';
|
||||||
|
|
||||||
|
class Menu extends Component {
|
||||||
|
|
||||||
|
menu_items = [
|
||||||
|
{ text: "Übersicht", path: "/" },
|
||||||
|
{ text: "Benutzer", path: "/user" },
|
||||||
|
{ text: "System", path: "/system" },
|
||||||
|
{ text: "Backup", path: "/backup" },
|
||||||
|
{ text: "Abmelden", path: "/" }
|
||||||
|
]
|
||||||
|
state = { shown: false }
|
||||||
|
toggle(val) {
|
||||||
|
this.setState(val);
|
||||||
|
}
|
||||||
|
getAlert() {
|
||||||
|
alert("getAlert from Child");
|
||||||
|
}
|
||||||
|
|
||||||
|
render(props, state) {
|
||||||
|
|
||||||
|
return (<div id="menu-screen" className='page' style={`display: ${state.shown ? 'flex' : 'none'}`}>
|
||||||
|
<div class="container">
|
||||||
|
<nav className='menu'>
|
||||||
|
<ul>
|
||||||
|
{this.menu_items.map((element,i) => (<li key={i}><a href={element.link}>{element.text}</a></li>))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Menu;
|
40
src/components/userlist/index.jsx
Normal file
40
src/components/userlist/index.jsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component } from "preact";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UserList extends Component {
|
||||||
|
|
||||||
|
|
||||||
|
deleteUser(user){
|
||||||
|
alert(`delete: ${user.uid}`);
|
||||||
|
}
|
||||||
|
editUser(user){
|
||||||
|
alert(`edit: ${user.uid}`);
|
||||||
|
}
|
||||||
|
displayUser(user,key){
|
||||||
|
return (
|
||||||
|
<div key={key} class="user-list-item">
|
||||||
|
<div class="user-attributes">
|
||||||
|
<span><b>UID:</b> {user.uid}</span> <span>(<b>Aktiv</b>)</span><br />
|
||||||
|
<span><b>Vorname:</b> {user.first_name}</span><br />
|
||||||
|
<span><b>Nachname:</b> {user.last_name}</span><br />
|
||||||
|
<span><b>RFID:</b> {user.rfid}</span>
|
||||||
|
<span><b>PIN:</b> {user.pin}</span>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-trash" onClick={()=>this.deleteUser(user)}>Löschen</button>
|
||||||
|
<button class="btn-edit" onClick={()=>this.editUser(user)}>Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {};
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.props.userlist&&this.props.userlist.map((user,i)=>this.displayUser(user,i))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default UserList
|
4
src/index.js
Normal file
4
src/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import './style/style.sass';
|
||||||
|
import App from './components/app';
|
||||||
|
|
||||||
|
export default App;
|
21
src/manifest.json
Normal file
21
src/manifest.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "doorlock_pwa",
|
||||||
|
"short_name": "doorlock_pwa",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"background_color": "#fff",
|
||||||
|
"theme_color": "#673ab8",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/android-chrome-192x192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/android-chrome-512x512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
src/routes/home/index.js
Normal file
11
src/routes/home/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import style from './style.css';
|
||||||
|
|
||||||
|
const Home = () => (
|
||||||
|
<div class={style.home}>
|
||||||
|
<h1>Home</h1>
|
||||||
|
<p>This is the Home component.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Home;
|
5
src/routes/home/style.css
Normal file
5
src/routes/home/style.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.home {
|
||||||
|
padding: 56px 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
39
src/routes/login/index.jsx
Normal file
39
src/routes/login/index.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Component, h } from 'preact';
|
||||||
|
import Breadcrumbs from '../../components/breadcrumbs';
|
||||||
|
|
||||||
|
class Login extends Component {
|
||||||
|
state = { name: '', password: '' };
|
||||||
|
navigation = ["Login"];
|
||||||
|
onSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(this.state);
|
||||||
|
this.setState({ name: '', password: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
return (<div id="login-screen" class="page">
|
||||||
|
<div class="container">
|
||||||
|
<Breadcrumbs items={this.navigation} />
|
||||||
|
|
||||||
|
<form id="login_form" onSubmit={this.onSubmit} >
|
||||||
|
<p>
|
||||||
|
Bitte melden Sie sich mit ihren Nutzerdaten an.
|
||||||
|
</p>
|
||||||
|
<div class="input-box">
|
||||||
|
<input id="name" type="text" placeholder="Username" onInput={e => this.setState(prev => ({ ...prev, name: e.target.value }))} value={this.state.name} />
|
||||||
|
<label for="name">Benutzername</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<input id="pass" type="password" placeholder="Passwort" onInput={e => this.setState(prev => ({ ...prev, password: e.target.value }))} value={this.state.password} />
|
||||||
|
<label for="pass">Password</label>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Submit" />
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
31
src/routes/profile/index.js
Normal file
31
src/routes/profile/index.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import {useEffect, useState} from "preact/hooks";
|
||||||
|
import style from './style.css';
|
||||||
|
|
||||||
|
// Note: `user` comes from the URL, courtesy of our router
|
||||||
|
const Profile = ({ user }) => {
|
||||||
|
const [time, setTime] = useState(Date.now());
|
||||||
|
const [count, setCount] = useState(10);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer = setInterval(() => setTime(Date.now()), 1000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={style.profile}>
|
||||||
|
<h1>Profile: {user}</h1>
|
||||||
|
<p>This is the user profile for a user named { user }.</p>
|
||||||
|
|
||||||
|
<div>Current time: {new Date(time).toLocaleString()}</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button onClick={() => setCount((count) => count + 1)}>Click Me</button>
|
||||||
|
{' '}
|
||||||
|
Clicked {count} times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Profile;
|
5
src/routes/profile/style.css
Normal file
5
src/routes/profile/style.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.profile {
|
||||||
|
padding: 56px 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
21
src/routes/users/index.jsx
Normal file
21
src/routes/users/index.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from "preact";
|
||||||
|
import UserList from "../../components/userlist";
|
||||||
|
import Pageselector from "../../components/Pageselector";
|
||||||
|
class Users extends Component {
|
||||||
|
userlist = [
|
||||||
|
{ uid: 1234, first_name: 'Max', last_name: 'Muster', rfid: 'D3A2E35E', pin: 1234 },
|
||||||
|
{ uid: 12341, first_name: 'Max1', last_name: 'Muster', rfid: 'D3A2E35E', pin: 1234 },
|
||||||
|
{ uid: 12342, first_name: 'Max2', last_name: 'Muster', rfid: 'D3A2E35E', pin: 1234 },
|
||||||
|
];
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="user-screen" class="page">
|
||||||
|
<div class="container">
|
||||||
|
<UserList userlist={this.userlist} />
|
||||||
|
<Pageselector />
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users;
|
52
src/style/_breadcrumbs.sass
Normal file
52
src/style/_breadcrumbs.sass
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
@mixin breadcrumbs
|
||||||
|
padding: 1em 0
|
||||||
|
ul
|
||||||
|
display: flex
|
||||||
|
background: #eee
|
||||||
|
box-shadow: inset 0 0 .3em #ccc, 0 0 .5em #ddd
|
||||||
|
padding: .3em
|
||||||
|
border: 0.05em solid #fff
|
||||||
|
border-radius: .3em
|
||||||
|
list-style-type: none
|
||||||
|
li
|
||||||
|
a
|
||||||
|
position: relative
|
||||||
|
display: inline-flex
|
||||||
|
vertical-align: middle
|
||||||
|
text-align: center
|
||||||
|
align-items: center
|
||||||
|
text-decoration: none
|
||||||
|
color: #fff
|
||||||
|
background: #ccc
|
||||||
|
height: 2em
|
||||||
|
padding: .5em .5em
|
||||||
|
&::after
|
||||||
|
content: ''
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 100%
|
||||||
|
border-top: 1em solid transparent
|
||||||
|
border-bottom: 1em solid transparent
|
||||||
|
border-left: 1em solid #ccc
|
||||||
|
|
||||||
|
z-index: 2
|
||||||
|
&:hover
|
||||||
|
background-color: #777
|
||||||
|
&::after
|
||||||
|
border-left: 1em solid #777
|
||||||
|
&:first-child a
|
||||||
|
border-top-left-radius: .3em
|
||||||
|
border-bottom-left-radius: .3em
|
||||||
|
|
||||||
|
&:not(:first-child) a
|
||||||
|
margin-left: .2em
|
||||||
|
padding-left: 1.5em
|
||||||
|
&::before
|
||||||
|
z-index: 1
|
||||||
|
content: ''
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0%
|
||||||
|
border-top: 1em solid transparent
|
||||||
|
border-bottom: 1em solid transparent
|
||||||
|
border-left: 1em solid #eee
|
39
src/style/_hamburger.sass
Normal file
39
src/style/_hamburger.sass
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@mixin hamburger-rect
|
||||||
|
width: 3em
|
||||||
|
height: .5em
|
||||||
|
border: none
|
||||||
|
margin: 0
|
||||||
|
background: #fff
|
||||||
|
border-radius: .2em
|
||||||
|
box-shadow: inset 0 0 .2em #000
|
||||||
|
position: relative
|
||||||
|
transition: 250ms all
|
||||||
|
@mixin hamburger-rect-active
|
||||||
|
background: black
|
||||||
|
box-shadow: 0 0 .1em #000
|
||||||
|
@mixin hamburger-rect-transparent
|
||||||
|
|
||||||
|
@mixin hamburger
|
||||||
|
width: 3em
|
||||||
|
display: inline-block
|
||||||
|
position: relative
|
||||||
|
float: right
|
||||||
|
hr
|
||||||
|
@include hamburger-rect
|
||||||
|
&:nth-child(1)
|
||||||
|
transform-origin: 0% 0%
|
||||||
|
&:nth-child(2)
|
||||||
|
margin: .5em 0
|
||||||
|
&:nth-child(3)
|
||||||
|
transform-origin: 0% 100%
|
||||||
|
|
||||||
|
&-active
|
||||||
|
hr:nth-child(1)
|
||||||
|
transform: translate(.25em, 0em) rotate(45deg)
|
||||||
|
@include hamburger-rect-active
|
||||||
|
hr:nth-child(2)
|
||||||
|
opacity: 0
|
||||||
|
transform: rotate(0deg) scale(0.2, 0.2)
|
||||||
|
hr:nth-child(3)
|
||||||
|
transform: translate(.25em, 0em) rotate(-45deg)
|
||||||
|
@include hamburger-rect-active
|
40
src/style/_login.sass
Normal file
40
src/style/_login.sass
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@mixin text-input
|
||||||
|
position: relative
|
||||||
|
input
|
||||||
|
display: block
|
||||||
|
width: 100%
|
||||||
|
border-radius: none
|
||||||
|
outline: none
|
||||||
|
border: none
|
||||||
|
font-size: 1.5em
|
||||||
|
padding: .7em
|
||||||
|
+ label
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
cursor: text
|
||||||
|
transition: 250ms all
|
||||||
|
|
||||||
|
&::placeholder
|
||||||
|
color: transparent
|
||||||
|
&:placeholder-shown + label
|
||||||
|
color: grey
|
||||||
|
font-size: 1.5em
|
||||||
|
position: absolute
|
||||||
|
padding: .7em
|
||||||
|
|
||||||
|
@mixin login-form
|
||||||
|
|
||||||
|
padding: 1em
|
||||||
|
margin: 0 auto
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
.input-box
|
||||||
|
@include text-input
|
||||||
|
p
|
||||||
|
font-size: 2em
|
||||||
|
|
||||||
|
input[type=submit]
|
||||||
|
border: none
|
||||||
|
border-radius: none
|
||||||
|
font-size: 2em
|
28
src/style/_menu.sass
Normal file
28
src/style/_menu.sass
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@mixin nav
|
||||||
|
ul
|
||||||
|
list-style: none
|
||||||
|
padding: 0
|
||||||
|
margin: 0
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
li a
|
||||||
|
display: block
|
||||||
|
position: relative
|
||||||
|
padding: 1em
|
||||||
|
font-size: 2em
|
||||||
|
font-style: none
|
||||||
|
text-decoration: none
|
||||||
|
color: #666
|
||||||
|
//text-shadow: 0 0 .2em #000
|
||||||
|
width: 100%
|
||||||
|
//background: #ccc
|
||||||
|
&::after
|
||||||
|
top: 100%
|
||||||
|
left: 0
|
||||||
|
z-index: 2
|
||||||
|
content: ''
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: .1em
|
||||||
|
background: #8a8080
|
||||||
|
//border-bottom-left-radius: 2em 1em
|
39
src/style/back/_hamburger.sass
Normal file
39
src/style/back/_hamburger.sass
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@mixin hamburger-rect
|
||||||
|
width: 3em
|
||||||
|
height: .5em
|
||||||
|
border: none
|
||||||
|
margin: 0
|
||||||
|
background: #fff
|
||||||
|
border-radius: .2em
|
||||||
|
box-shadow: inset 0 0 .2em #000
|
||||||
|
position: relative
|
||||||
|
transition: 250ms all
|
||||||
|
@mixin hamburger-rect-active
|
||||||
|
background: black
|
||||||
|
box-shadow: 0 0 .1em #000
|
||||||
|
@mixin hamburger-rect-transparent
|
||||||
|
|
||||||
|
@mixin hamburger
|
||||||
|
width: 3em
|
||||||
|
display: inline-block
|
||||||
|
position: relative
|
||||||
|
float: right
|
||||||
|
hr
|
||||||
|
@include hamburger-rect
|
||||||
|
&:nth-child(1)
|
||||||
|
transform-origin: 0% 0%
|
||||||
|
&:nth-child(2)
|
||||||
|
margin: .5em 0
|
||||||
|
&:nth-child(3)
|
||||||
|
transform-origin: 0% 100%
|
||||||
|
|
||||||
|
.activated
|
||||||
|
hr:nth-child(1)
|
||||||
|
transform: translate(.25em, 0em) rotate(45deg)
|
||||||
|
@include hamburger-rect-active
|
||||||
|
hr:nth-child(2)
|
||||||
|
opacity: 0
|
||||||
|
transform: rotate(0deg) scale(0.2, 0.2)
|
||||||
|
hr:nth-child(3)
|
||||||
|
transform: translate(.25em, 0em) rotate(-45deg)
|
||||||
|
@include hamburger-rect-active
|
32
src/style/back/index.sass
Normal file
32
src/style/back/index.sass
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
*
|
||||||
|
//border: red 1px dotted
|
||||||
|
box-sizing: border-box
|
||||||
|
html
|
||||||
|
font-family: Helvetica, sans-serif
|
||||||
|
font-size: 16px
|
||||||
|
body
|
||||||
|
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
background-color: white
|
||||||
|
//min-height: 100vh
|
||||||
|
width: 100%
|
||||||
|
#wrapper
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: flex-start
|
||||||
|
align-items: stretch
|
||||||
|
min-height: 100%
|
||||||
|
width: 100%
|
||||||
|
.container
|
||||||
|
width: clamp(5ch, 100%,75ch)
|
||||||
|
margin: 0 auto
|
||||||
|
|
||||||
|
|
||||||
|
.page
|
||||||
|
//overflow: auto
|
||||||
|
display: flex
|
||||||
|
background: white
|
||||||
|
width: 100%
|
||||||
|
padding-top: 3em
|
||||||
|
|
21
src/style/back/style.sass
Normal file
21
src/style/back/style.sass
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
@use 'hamburger'
|
||||||
|
.header
|
||||||
|
z-index: 100
|
||||||
|
position: fixed
|
||||||
|
width: 100%
|
||||||
|
height: 4em
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: flex-end
|
||||||
|
padding: .5em 0
|
||||||
|
margin: 0 0 0 0
|
||||||
|
background: linear-gradient(#9e9e9e 0% , #d1d1d1 100%)
|
||||||
|
box-shadow: 0 0 .2em #444
|
||||||
|
h1
|
||||||
|
display: inline-block
|
||||||
|
margin: 0 0
|
||||||
|
color: white
|
||||||
|
.hamburger
|
||||||
|
font-size: 1em
|
||||||
|
@include hamburger.hamburger
|
146
src/style/style.sass
Normal file
146
src/style/style.sass
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
@use 'hamburger'
|
||||||
|
@use 'menu'
|
||||||
|
@use 'login'
|
||||||
|
@use 'breadcrumbs'
|
||||||
|
*
|
||||||
|
//border: red 1px dotted
|
||||||
|
box-sizing: border-box
|
||||||
|
html
|
||||||
|
font-family: Helvetica, sans-serif
|
||||||
|
font-size: 16px
|
||||||
|
body
|
||||||
|
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
background-color: white
|
||||||
|
//min-height: 100vh
|
||||||
|
width: 100%
|
||||||
|
#wrapper
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: flex-start
|
||||||
|
align-items: stretch
|
||||||
|
min-height: 100%
|
||||||
|
width: 100%
|
||||||
|
.header
|
||||||
|
z-index: 100
|
||||||
|
position: fixed
|
||||||
|
width: 100%
|
||||||
|
height: 4em
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: flex-end
|
||||||
|
padding: .5em 0
|
||||||
|
margin: 0 0 0 0
|
||||||
|
background: linear-gradient(#9e9e9e 0% , #d1d1d1 100%)
|
||||||
|
box-shadow: 0 0 .2em #444
|
||||||
|
h1
|
||||||
|
display: inline-block
|
||||||
|
margin: 0 0
|
||||||
|
color: white
|
||||||
|
.hamburger
|
||||||
|
font-size: 1em
|
||||||
|
@include hamburger.hamburger
|
||||||
|
|
||||||
|
.container
|
||||||
|
width: clamp(5ch, 100%,75ch)
|
||||||
|
margin: 0 auto
|
||||||
|
.breadcrumb
|
||||||
|
@include breadcrumbs.breadcrumbs
|
||||||
|
|
||||||
|
.page
|
||||||
|
//overflow: auto
|
||||||
|
display: flex
|
||||||
|
background: white
|
||||||
|
width: 100%
|
||||||
|
padding-top: 3em
|
||||||
|
.menu
|
||||||
|
@include menu.nav
|
||||||
|
|
||||||
|
#login_form
|
||||||
|
@include login.login-form
|
||||||
|
|
||||||
|
.page-nav-bar
|
||||||
|
text-align: center
|
||||||
|
//background-color: #ccc
|
||||||
|
margin: 1em
|
||||||
|
ul
|
||||||
|
list-style: none
|
||||||
|
justify-content: center
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
display: flex
|
||||||
|
li a
|
||||||
|
text-decoration: none
|
||||||
|
font-weight: bold
|
||||||
|
padding: .2em .5em
|
||||||
|
margin: .3em
|
||||||
|
border-radius: .3em
|
||||||
|
background: #eee
|
||||||
|
color: #ccc
|
||||||
|
border: .1em solid #ccc
|
||||||
|
&:hover
|
||||||
|
color: #333
|
||||||
|
background: #fff
|
||||||
|
border: .1em solid #ccc
|
||||||
|
.user-list-item
|
||||||
|
overflow: hidden
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
background: #eee
|
||||||
|
padding: .5em 0
|
||||||
|
border-radius: .3em
|
||||||
|
&:not(:last-child)
|
||||||
|
margin-bottom: .5em
|
||||||
|
.user-attributes
|
||||||
|
padding: .5em
|
||||||
|
.btn-group
|
||||||
|
margin-left: auto
|
||||||
|
display: inline-flex
|
||||||
|
flex-direction: column
|
||||||
|
align-self: flex-end
|
||||||
|
justify-self: flex-end
|
||||||
|
@mixin button()
|
||||||
|
position: relative
|
||||||
|
border: none
|
||||||
|
margin-left: auto
|
||||||
|
background: #eee
|
||||||
|
//background-size: 80% 80%
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: left
|
||||||
|
background-origin: border-box
|
||||||
|
transition: 150ms background-size, 200ms padding, 100ms width
|
||||||
|
padding: 0em
|
||||||
|
padding-left: 3em
|
||||||
|
width: 3em
|
||||||
|
height: 3em
|
||||||
|
border-bottom-left-radius: 1.5em
|
||||||
|
border-top-left-radius: 1.5em
|
||||||
|
&:not(:last-of-type)
|
||||||
|
margin-bottom: .5em
|
||||||
|
&:hover
|
||||||
|
width: auto
|
||||||
|
filter: invert(120%)
|
||||||
|
padding-right: .5em
|
||||||
|
background-color: #000
|
||||||
|
color: #ccc
|
||||||
|
box-shadow: 0 0 .5em #333
|
||||||
|
background-position: 00%
|
||||||
|
//background-size: 90% 90%
|
||||||
|
&::before
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
content: ''
|
||||||
|
border-radius: 50%
|
||||||
|
//border: .1em solid #333
|
||||||
|
|
||||||
|
.btn-trash
|
||||||
|
@include button()
|
||||||
|
background-image: url("../assets/icons/delete-icon3.svg")
|
||||||
|
|
||||||
|
.btn-edit
|
||||||
|
@include button()
|
||||||
|
background-image: url("../assets/icons/edit-icon3.svg")
|
4
src/sw.js
Normal file
4
src/sw.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { getFiles, setupPrecaching, setupRouting } from 'preact-cli/sw/';
|
||||||
|
|
||||||
|
setupRouting();
|
||||||
|
setupPrecaching(getFiles());
|
15
src/template.html
Normal file
15
src/template.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title><% preact.title %></title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png">
|
||||||
|
<% preact.headEnd %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% preact.bodyEnd %>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
tests/__mocks__/browserMocks.js
Normal file
21
tests/__mocks__/browserMocks.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage
|
||||||
|
/**
|
||||||
|
* An example how to mock localStorage is given below 👇
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Mocks localStorage
|
||||||
|
const localStorageMock = (function() {
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getItem: (key) => store[key] || null,
|
||||||
|
setItem: (key, value) => store[key] = value.toString(),
|
||||||
|
clear: () => store = {}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: localStorageMock
|
||||||
|
}); */
|
3
tests/__mocks__/fileMocks.js
Normal file
3
tests/__mocks__/fileMocks.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// This fixed an error related to the CSS and loading gif breaking my Jest test
|
||||||
|
// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
|
||||||
|
module.exports = 'test-file-stub';
|
6
tests/__mocks__/setupTests.js
Normal file
6
tests/__mocks__/setupTests.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { configure } from 'enzyme';
|
||||||
|
import Adapter from 'enzyme-adapter-preact-pure';
|
||||||
|
|
||||||
|
configure({
|
||||||
|
adapter: new Adapter()
|
||||||
|
});
|
12
tests/header.test.js
Normal file
12
tests/header.test.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import Header from '../src/components/header';
|
||||||
|
// See: https://github.com/preactjs/enzyme-adapter-preact-pure
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
describe('Initial Test of the Header', () => {
|
||||||
|
test('Header renders 3 nav items', () => {
|
||||||
|
const context = shallow(<Header />);
|
||||||
|
expect(context.find('h1').text()).toBe('Preact App');
|
||||||
|
expect(context.find('Link').length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
68
webpack.config.js
Normal file
68
webpack.config.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
// App directory
|
||||||
|
const appDirectory = fs.realpathSync(process.cwd());
|
||||||
|
|
||||||
|
// Gets absolute path of file within app directory
|
||||||
|
const resolveAppPath = relativePath => path.resolve(appDirectory, relativePath);
|
||||||
|
|
||||||
|
// Host
|
||||||
|
const host = process.env.HOST || 'localhost';
|
||||||
|
|
||||||
|
// Required for babel-preset-react-app
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
// Environment mode
|
||||||
|
mode: 'development',
|
||||||
|
|
||||||
|
// Entry point of app
|
||||||
|
entry: resolveAppPath('src'),
|
||||||
|
|
||||||
|
output: {
|
||||||
|
|
||||||
|
// Development filename output
|
||||||
|
filename: 'static/js/bundle.js',
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
|
||||||
|
// Serve index.html as the base
|
||||||
|
|
||||||
|
// Enable compression
|
||||||
|
compress: true,
|
||||||
|
|
||||||
|
// Enable hot reloading
|
||||||
|
hot: true,
|
||||||
|
|
||||||
|
host,
|
||||||
|
|
||||||
|
port: 8080,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// Re-generate index.html with injected script tag.
|
||||||
|
// The injected script tag contains a src value of the
|
||||||
|
// filename output defined above.
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
inject: true,
|
||||||
|
template: resolveAppPath('public/index.html'),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: [
|
||||||
|
// Creates `style` nodes from JS strings
|
||||||
|
"style-loader",
|
||||||
|
// Translates CSS into CommonJS
|
||||||
|
"css-loader",
|
||||||
|
// Compiles Sass to CSS
|
||||||
|
"sass-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user