first commit

This commit is contained in:
Romulus21
2021-12-17 15:37:05 +01:00
commit 2d49672d20
43 changed files with 39806 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
/build
/*.log

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/plantes.iml" filepath="$PROJECT_DIR$/.idea/plantes.iml" />
</modules>
</component>
</project>

8
.idea/plantes.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# plantes
## CLI Commands
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# test the production build locally
npm run serve
# run tests with jest and enzyme
npm run test
```
For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md).

38841
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"private": true,
"name": "plantes",
"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": "^6.0.1",
"eslint-config-preact": "^1.1.0",
"jest": "^24.9.0",
"jest-preset-preact": "^1.0.0",
"preact-cli": "^3.0.0",
"preact-cli-tailwind": "^3.0.0",
"sirv-cli": "1.0.3"
},
"dependencies": {
"preact": "^10.3.2",
"preact-render-to-string": "^5.1.4",
"preact-router": "^3.2.1",
"tailwindcss": "^2.2.19"
},
"jest": {
"preset": "jest-preset-preact",
"setupFiles": [
"<rootDir>/tests/__mocks__/browserMocks.js",
"<rootDir>/tests/__mocks__/setupTests.js"
]
}
}

5
preact.config.js Normal file
View File

@@ -0,0 +1,5 @@
const tailwind = require('preact-cli-tailwind');
module.exports = (config, env, helpers) => {
config = tailwind(config, env, helpers);
return config;
};

26
src/Contexts.js Normal file
View File

@@ -0,0 +1,26 @@
import { createContext } from "preact";
import { useEffect, useState } from "preact/hooks";
import usePlants from "./hooks/PlantsHook";
import { useLocalStorage } from "./hooks/LocalStorageHook"
import useUser from "./hooks/UserHook";
export const UserContext = createContext()
export const PlantsContext = createContext()
export default function ContextsProviders({children}) {
const [data, setData] = useLocalStorage('data', {})
const [user, setUser] = useUser(data, setData)
const {plants, addPlant, editPlant, removePlant, addAction, doneTask, history} = usePlants(data, setData)
useEffect(() => {
console.log('first', user);
}, [user])
return <UserContext.Provider value={[user, setUser]}>
<PlantsContext.Provider value={{plants, addPlant, editPlant, removePlant, addAction, doneTask, history}}>
{children}
</PlantsContext.Provider>
</UserContext.Provider>
}

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

38
src/components/App.js Normal file
View File

@@ -0,0 +1,38 @@
import { h } from 'preact';
import { Router } from 'preact-router';
import ContextsProviders from '../Contexts';
import Header from './Header';
// Code-splitting is automated for `routes` directory
import Home from '../routes/Home';
import Plant from '../routes/Plant';
import Profile from '../routes/Profile';
import {useEffect} from "preact/hooks";
const App = () => {
useEffect(() => {
if (!Notification) {
alert('Le navigateur ne supporte pas les notifications.');
} else if (Notification.permission !== 'granted') {
Notification.requestPermission();
}
}, [])
return (
<div id="app" class="h-screen overflow-auto flex flex-col">
<ContextsProviders>
<Header />
<main className="flex-1 dark:bg-gray-800 dark:text-white">
<Router>
<Home path="/" />
<Plant path="plant/:id" />
<Profile path="/profile" />
</Router>
</main>
</ContextsProviders>
</div>
)}
export default App;

10
src/components/Button.js Normal file
View File

@@ -0,0 +1,10 @@
import { classNames } from "../utilities/classNames"
export const Button = ({ children, className = "", type = "text", ...props }) => {
return (
<button type={type} className={classNames("border px-2 py-1 shadow rounded", className)} {...props}>
{children}
</button>
)
}

59
src/components/Form.js Normal file
View File

@@ -0,0 +1,59 @@
import {classNames} from "../utilities/classNames";
export const InputField = ({children, name, type = "text", ...props}) => {
const id = props.id ?? name
const classStyle = props.className ?? ''
if (props.className) {
delete props.className
}
return <fieldset className={classNames(classStyle)}>
{children && <label htmlFor={id} className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{ children }
</label>}
<input id={id}
name={name}
type={type}
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 rounded-md dark:bg-gray-500"
{...props}/>
</fieldset>
}
export const TextAreaField = ({children, name, ...props}) => {
const id = props.id ?? name
const classStyle = props.className ?? ''
if (props.className) {
delete props.className
}
return <fieldset className={classNames(classStyle)}>
{children && <label htmlFor={id} className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{ children }
</label>}
<textarea id={id}
name={name}
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 dark:bg-gray-500 rounded-md"
{...props}
/>
</fieldset>
}
export const SelectField = ({children, name, options, className = '', ...props}) => {
const id = props.id ?? name
return <fieldset className={className}>
{children && <label htmlFor={id} className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{ children }
</label>}
<select id={id} name={name} className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 dark:bg-gray-500 rounded-md" {...props}>
{options.map((option, index) => <option key={index} value={option}
className="capitalize">{option}</option>)}
</select>
</fieldset>
}

29
src/components/Header.js Normal file
View File

@@ -0,0 +1,29 @@
import { h } from "preact"
import { Link } from "preact-router/match"
const Header = () => {
return (
<header class="bg-green-700 text-white flex justify-between items-center p-2 text-lg">
<Link href="/" class="font-bold text-xl">
Plantes
</Link>
<nav class="flex">
<NavLink path="/profile">Me</NavLink>
</nav>
</header>
)
}
export default Header
const NavLink = ({ path, children }) => {
return (
<Link
href={path}
activeClassName="font-bold bg-green-800"
class="py-1 px-2 rounded"
>
{children}
</Link>
)
}

28
src/components/Modals.js Normal file
View File

@@ -0,0 +1,28 @@
import { classNames } from "../utilities/classNames"
export const Modal = ({children, isOpen, customClose = false, ...props}) => {
const handleClose = e => props.onChange(e)
return <>
{isOpen &&
<div className="overlay fixed block top-0 bottom-0 left-0 right-0 z-10 bg-gray-800 bg-opacity-80" onClick={handleClose}>
<div className="h-full flex justify-center items-center">
<div className="flex flex-col bg-white dark:bg-gray-700 dark:text-white rounded p-2 min-h-48 min-w-48" {...props}>
<div className="flex-1">
{children}
</div>
{!customClose &&<div className="flex justify-end">
<button type="button" className="bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 hover:dark:bg-gray-800 px-2 py-1 rounded shadow close-button" onClick={handleClose}>Close</button>
</div>}
</div>
</div>
</div>
}
</>
}
export const ModalTitle = ({children, ...props}) => {
return <div className="bg-green-700 text-white text-2xl font-bold text-center -mx-2 -mt-2 p-2 rounded-tl rounded-tr" {...props}>{children}</div>
}

View File

@@ -0,0 +1,9 @@
import { classNames } from "../utilities/classNames";
export const PageLayout = ({children, ...props}) => {
return <div class={classNames("p-2", props.class ?? "")}>
{children}
</div>
}

17
src/components/Plants.js Normal file
View File

@@ -0,0 +1,17 @@
import { Link } from "preact-router/match"
import {getPicture} from "../utilities/pictures";
export const PlantThumb = ({ plant, children }) => {
return <Link href={`/plant/${plant.id}`} class="block h-48">
<div className="bg-green-400 relative rounded shadow-lg flex flex-col">
<img src={getPicture(plant.id)} alt="" className="object-cover h-48 w-full rounded" />
<div className="bg-green-700 text-white p-2 text-center absolute bottom-0 w-full rounded-bl rounded-br">
{children}
</div>
<div title="Actions" className="absolute right-2 top-2 rounded-full flex justify-center items-center bg-green-700 w-6 h-6">
{plant.actions.length}
</div>
</div>
</Link>
}

27
src/components/Tasks.js Normal file
View File

@@ -0,0 +1,27 @@
import {Button} from "./Button"
import {useContext} from "preact/hooks"
import {PlantsContext} from "../Contexts"
export const Tasks = () => {
const { plants, doneTask, history } = useContext(PlantsContext)
const taskIsRequired = (action) => {
if (history()[action.id]) {
let lastTask = new Date(history()[action.id])
return lastTask.addDays(Number(action.frequency)) < (new Date())
}
return true
}
return <div className="mb-5">
<h1>Tasks</h1>
<div>
{plants.map(plant => plant.actions.filter(action => taskIsRequired(action)).map(action => <div className="flex items-center gap-2">
<span className="capitalize"><b>{plant.name}</b> {action.action_type}</span>
<span> every {action.frequency} days {action.id}</span>
<span><Button onClick={() => doneTask(action.id)}>Done</Button></span>
</div>))}
</div>
</div>
}

View File

@@ -0,0 +1,26 @@
import { useState } from "preact/hooks"
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.warn('localstorageHook', error)
return initialValue
}
})
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.warn('localstorageHook', error)
}
}
return [storedValue, setValue]
}

76
src/hooks/PlantsHook.js Normal file
View File

@@ -0,0 +1,76 @@
import { useEffect, useState } from "preact/hooks"
import {route} from "preact-router";
const usePlants = (data, setData) => {
const [plants, setPlants] = useState([])
const [tasks, setTasks] = useState([])
useEffect(() =>{
setPlants(data.plants ?? [])
}, [])
const addPlant = (plantForm) => {
let plant = plantForm
let maxId = Math.max.apply(Math, plants.map(function(elem) { return elem.id; }))
plant.id = maxId === -Infinity ? 1 : maxId + 1
plant.actions = []
setPlants([...plants, plant])
setData({...data, plants: [...plants, plant]})
}
const editPlant = (plant) => {
let plantIndex = plants.findIndex(item => item.id === plant.id)
plants[plantIndex] = plant
savePlants(plants)
}
const removePlant = (plant) => {
plants.splice(plants.findIndex(item => item.id === plant.id), 1)
savePlants(plants)
route('/', true)
}
const addAction = (plant, action) => {
action.id = action.action_type + '-' + plant.id
let plantIndex = plants.findIndex(item => item.id === plant.id)
let actionIndex = plant.actions.findIndex(item => item.action_type === action.action_type)
console.log(actionIndex, plantIndex, plant, plants, action)
actionIndex >= 0 ? plant.actions[actionIndex] = action
: plant.actions.push(action)
editPlant(plant)
}
const savePlants = (plants) => {
setPlants(plants)
setData({...data, plants: plants})
}
const doneTask = (actionId) => {
let history = data.history ?? {}
history[actionId] = new Date()
setData({...data, history: history})
notifyMe()
}
const history = () => data.history ?? {}
const notifyMe = () => {
if (!('Notification' in window)) {
alert('Ce navigateur ne prend pas en charge la notification de bureau')
} else if (Notification.permission === 'granted') {
// Si tout va bien, créons une notification
const notification = new Notification('Salut toi!')
} else if (Notification.permission !== 'denied') {
Notification.requestPermission().then((permission) => {
// Si l'utilisateur accepte, créons une notification
if (permission === 'granted') {
const notification = new Notification('Salut toi!')
}
})
}
}
return {plants, addPlant, editPlant, removePlant, addAction, doneTask, history}
}
export default usePlants

17
src/hooks/UserHook.js Normal file
View File

@@ -0,0 +1,17 @@
import { useEffect, useState } from "preact/hooks"
const useUser = (data, setData) => {
const [user, setUser] = useState(data.user ?? {name: "me", dark_mode: false})
useEffect(() =>{
setData({...data, user: user})
console.log('in', user);
document.querySelector('html').classList.toggle('dark', user.dark_mode)
}, [user])
return [user, setUser]
}
export default useUser

15
src/index.js Normal file
View File

@@ -0,0 +1,15 @@
import App from './components/App';
import './style';
Date.prototype.addDays = function (days) {
let date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
}
Date.prototype.toFrDate = function() {
let month = ((this.getMonth() + 1 < 10) ? '0' : '') + (this.getMonth() + 1)
return `${this.getDate()}/${month}/${this.getFullYear()}`
}
export default App;

24
src/manifest.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "plantes",
"short_name": "plantes",
"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"
}
],
"_version": "1.1.0",
"sap.app": {},
"sap.ui": {}
}

71
src/routes/Home.js Normal file
View File

@@ -0,0 +1,71 @@
import { createPortal } from "preact/compat"
import { useContext, useState } from "preact/hooks"
import { Button } from "../components/Button"
import {Modal, ModalTitle} from "../components/Modals"
import { PageLayout } from "../components/PageLayout"
import { PlantThumb } from "../components/Plants"
import { PlantsContext } from "../Contexts"
import {InputField, TextAreaField} from "../components/Form"
import {Tasks} from "../components/Tasks"
export const Home = () => {
const [addModal, setAddModal] = useState(false)
const [plantForm, setPlantForm] = useState({})
const app = document.getElementById("app")
const { plants, addPlant } = useContext(PlantsContext)
const handleSubmit = (e) => {
e.preventDefault()
e.stopPropagation()
console.log(plantForm)
addPlant(plantForm)
setAddModal(false)
}
const handleCloseAddModal = (e) => {
if (e.target.classList.contains("overlay") ||
e.target.classList.contains("close-button")) {
setAddModal(false)
}
}
return (
<PageLayout class="relative">
<Tasks />
<h1>Plants</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
{plants.map((plant) => (
<PlantThumb plant={plant}>{plant.name}</PlantThumb>
))}
</div>
<div className="w-full sticky bottom-5 flex justify-end">
<div onClick={() => setAddModal(true)}
className="rounded-full w-16 h-16 flex items-center justify-center cursor-pointer bg-green-800 hover-bg-green-900 text-white">
Add +
</div>
</div>
{createPortal(
<Modal isOpen={addModal} onChange={handleCloseAddModal}>
<ModalTitle>
Add Plant
</ModalTitle>
<form onSubmit={handleSubmit}>
<InputField name="name" className="mb-2 mt-5" onChange={(e) => setPlantForm({ ...plantForm, name: e.target.value }) }>Name</InputField>
<TextAreaField name="description" className="mb-5" onChange={(e) => setPlantForm({ ...plantForm, description: e.target.value })}>Description</TextAreaField>
<Button type="submit" className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
Add
</Button>
</form>
</Modal>,
app
)}
</PageLayout>
)
}
export default Home

100
src/routes/Plant.js Normal file
View File

@@ -0,0 +1,100 @@
import {createPortal, useRef} from "preact/compat";
import { useContext, useEffect, useState } from "preact/hooks";
import { Button } from "../components/Button";
import {Modal, ModalTitle} from "../components/Modals";
import { PageLayout } from "../components/PageLayout"
import { PlantsContext } from "../Contexts";
import {InputField, SelectField} from "../components/Form";
import {getPicture, storePicture} from "../utilities/pictures";
const Plant = ({id}) => {
const [addModal, setAddModal] = useState(false)
const {plants, removePlant, addAction, doneTask, history} = useContext(PlantsContext)
const [plant, setPlant] = useState({})
const [actionForm, setActionForm] = useState({})
const [image, setImage] = useState(localStorage.getItem("image" + id) ?? '')
const pictureName = 'picture-' + id
const picture = useRef(null)
const action_types = ['watering', 'spraying', 'bathing']
useEffect(() => {
const plantFind = plants.find(plant => plant.id === Number(id))
setPlant(plantFind)
}, [])
useEffect(() => {
setImage(localStorage.getItem(pictureName))
}, [picture])
const handleSubmit = (e) => {
e.preventDefault()
e.stopPropagation()
if (!actionForm.action_type) {
actionForm.action_type = action_types[0]
}
console.log("my event", e, actionForm)
addAction(plant, actionForm)
setAddModal(false)
}
const handleCloseAddModal = (e) => {
if (e.target.classList.contains("overlay") || e.target.classList.contains("close-button")) {
setAddModal(false)
}
}
const addPicture = e => storePicture(document.getElementById("input-file"), id, setImage)
return <PageLayout>
<div className="flex justify-between">
<h1>{ plant.name }</h1>
<Button onClick={() => removePlant(plant)}>Delete</Button>
</div>
<p>{ plant.description }</p>
<div>
<img id="picture" ref={picture} src={getPicture(id)} alt=""/>
<img id="output" src="" alt=""/>
<input id="input-file" type="file" name="picture" onChange={addPicture}/>
</div>
<div>
<h2>Actions</h2>
{plant.actions && plant.actions.map(action => {
let isDone = false
let lastTask = false
if (history()[action.id]) {
lastTask = new Date(history()[action.id])
isDone = lastTask.addDays(Number(action.frequency)) < (new Date())
}
console.log(lastTask, isDone)
return <div key={action.action_type} className="flex items-center gap-2">
<span className="capitalize">{action.action_type}</span>
<span>evey {action.frequency} days</span>
<span><Button onClick={() => doneTask(action.id)}>Done</Button></span>
<span>last task {lastTask ? lastTask.toFrDate() : 'never'}</span>
</div>
})}
<Button className="bg-red-500" onClick={() => setAddModal(true)}>Add/Edit</Button>
</div>
{createPortal(
<Modal isOpen={addModal} onChange={handleCloseAddModal}>
<ModalTitle>Add Action</ModalTitle>
<form onSubmit={handleSubmit}>
<SelectField name="action_type" className="mb-2 mt-5" options={action_types} onChange={(e) => setActionForm({ ...actionForm, action_type: e.target.value })}>Name</SelectField>
<InputField name="frequency" className="mb-5" onChange={(e) => setActionForm({ ...actionForm, frequency: e.target.value })}>Frequency</InputField>
<Button type="submit" className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
Add
</Button>
</form>
</Modal>,
app
)}
</PageLayout>
}
export default Plant

24
src/routes/Profile.js Normal file
View File

@@ -0,0 +1,24 @@
import { useContext, useEffect, useState } from "preact/hooks";
import { PageLayout } from "../components/PageLayout";
import { UserContext } from "../Contexts";
export default function Profile() {
const [user, setUser] = useContext(UserContext)
const [darkMode, setDarkMode] = useState(false)
useEffect(() => {
setDarkMode(user.dark_mode)
}, [])
useEffect(() => {
setUser({...user, dark_mode: darkMode})
}, [darkMode])
return <PageLayout>
<h1 className="dark:text-red-600">Profile</h1>
<label htmlFor="dark_mode">
Dark Mode
<input type="checkbox" id="dark_mode" name="dark_mode" value={darkMode} onChange={() => setDarkMode(!darkMode)} />
</label>
</PageLayout>
}

13
src/style/index.css Normal file
View File

@@ -0,0 +1,13 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
@apply text-2xl font-bold mt-5 mb-2;
}
h2 {
@apply text-xl font-bold mt-5 mb-2;
}
}

55
src/sw.js Normal file
View File

@@ -0,0 +1,55 @@
import { getFiles, setupPrecaching, setupRouting } from 'preact-cli/sw/';
setupRouting();
setupPrecaching(getFiles());
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
const BASE = location.protocol + '//' + location.host
const PREFIX = "V1"
const CACHED_FILES = [
`${BASE}/sw.js`,
`${BASE}/js/app.js`,
`${BASE}/css/app.css`,
`${BASE}/offline.html`,
]
self.addEventListener('install', (event) => {
self.skipWaiting()
event.waitUntil(
(async () => {
const cache = await caches.open(PREFIX)
await cache.addAll(CACHED_FILES)
})()
)
console.log(`${PREFIX} Install`)
})
self.addEventListener('activate', (event) => {
clients.claim()
event.waitUntil((async() => {
const keys = await caches.keys()
await Promise.all(
keys.map(key => {
if (!key.includes(PREFIX)) {
return caches.delete(key)
}
})
)
})())
console.log(`${PREFIX} Activate`)
})
const delay = 1000 * 60 * 60 * 24
console.log(localStorage.getItem('data'))
wait(delay)
.then(() => {
// do thing
}).catch(err => console.log(err))

15
src/template.html Normal file
View 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>

View File

@@ -0,0 +1,4 @@
export function classNames() {
// console.log(Object.values(arguments), Object.values(arguments).join(' '));
return Object.values(arguments).join(' ')
}

108
src/utilities/pictures.js Normal file
View File

@@ -0,0 +1,108 @@
const pictureName = id => {
return 'picture-' + id
}
export const getPicture = id => {
return localStorage.getItem('picture-' + id ?? '')
}
export const storePicture = (file, id, setter) => {
var filesToUploads = document.getElementById("input-file").files;
file = filesToUploads[0];
if (file) {
const reader = new FileReader()
reader.onload = function (e) {
console.log(e)
var img = document.createElement("img");
img.src = e.target.result;
// Create your canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 500;
var MAX_HEIGHT = 500;
var width = img.width;
var height = img.height;
// Add the resizing logic
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
//Specify the resizing result
canvas.width = width;
canvas.height = height;
let ctx2 = canvas.getContext("2d");
ctx2.drawImage(img, 0, 0, width, height);
console.log(img, ctx2)
let dataurl = canvas.toDataURL(file.type);
console.log(dataurl)
localStorage.setItem(pictureName(id), dataurl)
setter(localStorage.getItem(pictureName(id)))
document.getElementById("output").src = dataurl;
};
reader.readAsDataURL(file);
}
/*
reader.addEventListener("load", function () {
let img = document.createElement("img");
img.src = reader.result
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 500;
var MAX_HEIGHT = 500;
var width = img.width;
var height = img.height;
// Add the resizing logic
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
//Specify the resizing result
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
let dataurl = canvas.toDataURL(file.type);
console.log(dataurl)
// document.getElementById('picture').src = dataurl;
localStorage.setItem(pictureName(id), dataurl)
// localStorage.setItem(pictureName(id), reader.result)
setter(localStorage.getItem(pictureName(id)))
}, false);
if (imgPath) {
reader.readAsDataURL(imgPath)
}
*/
}

29
tailwind.config.js Normal file
View File

@@ -0,0 +1,29 @@
module.exports = {
mode: 'jit',
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: 'class', // or 'media' or 'class'
theme: {
extend: {
minHeight: {
'10': '2.5rem',
'12': '3rem',
'24': '6rem',
'32': '9rem',
'48': '12rem',
'80': '20rem',
},
minWidth: {
'10': '2.5rem',
'12': '3rem',
'24': '6rem',
'32': '9rem',
'48': '12rem',
'80': '20rem',
},
},
},
variants: {
extend: {},
},
plugins: [],
}

View 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
}); */

View 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';

View 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
View 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);
});
});