Init
This commit is contained in:
commit
2421fcf900
4
.env.example
Normal file
4
.env.example
Normal file
@ -0,0 +1,4 @@
|
||||
AUTH0_DOMAIN = your.domain.auth0.com
|
||||
AUTH0_API_AUDIENCE = your.api.audience
|
||||
AUTH0_ISSUER = https://your.domain.auth0.com/
|
||||
AUTH0_ALGORITHMS = RS256
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.env
|
||||
venv
|
||||
.venv
|
||||
.idea
|
86
README.md
Normal file
86
README.md
Normal file
@ -0,0 +1,86 @@
|
||||
# Auth0 + Python + FastAPI API Seed
|
||||
|
||||
This is the seed project you need to use if you're going to create an API using FastAPI in Python and Auth0. If you just want to create a Regular Python WebApp, please check [this project](https://github.com/auth0-samples/auth0-python-web-app/tree/master/01-Login)
|
||||
|
||||
## Running the example
|
||||
|
||||
In order to run the example you need to have `python3` (any version higher than `3.6`) and `pip3` installed.
|
||||
|
||||
### Configuration
|
||||
|
||||
The configuration you'll need is mostly information from Auth0, you'll need both the tentant domain and the API information.
|
||||
|
||||
This app reads its configuration information from a `.env` file by default.
|
||||
|
||||
To create a `.env` file you can copy the `.env.example` file and fill the values accordingly:
|
||||
|
||||
```console
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Alternatively you can use environment variables to define your application's settings (remember to update the values accordingly):
|
||||
|
||||
```console
|
||||
export AUTH0_DOMAIN='your.domain.auth0.com'
|
||||
export AUTH0_API_AUDIENCE='your.api.audience'
|
||||
export AUTH0_ISSUER='https://your.domain.auth0.com'
|
||||
export AUTH0_ALGORITHMS='RS256'
|
||||
```
|
||||
|
||||
### Spin up the server
|
||||
|
||||
Once you've set your environment information below you'll find the commands you'll need.
|
||||
|
||||
1. Create and activate a python environment:
|
||||
|
||||
```console
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
2. Install the needed dependencies with:
|
||||
|
||||
```console
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Start the server with the following:
|
||||
|
||||
```console
|
||||
uvicorn application.main:app
|
||||
```
|
||||
|
||||
4. Try calling [http://localhost:8000/api/public](http://localhost:8000/api/public)
|
||||
|
||||
```
|
||||
curl -X 'GET' \
|
||||
'http://localhost:8000/api/public' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
## API documentation
|
||||
|
||||
Access [http://localhost:8000/docs](http://localhost:8000/docs). From there you'll see all endpoints and can test your API
|
||||
|
||||
### Testing the API
|
||||
|
||||
#### Private endpoint
|
||||
|
||||
You can then try to do a GET to [http://localhost:8000/api/private](http://localhost:8000/api/private) which will throw an error if you don't send an access token signed with RS256 with the appropriate issuer and audience in the Authorization header.
|
||||
|
||||
```console
|
||||
curl -X 'GET' \
|
||||
'http://localhost:8000/api/private' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer <FILL YOUR TOKEN HERE>'
|
||||
```
|
||||
|
||||
#### Private-Scoped endpoint
|
||||
|
||||
You can also try to do a GET to [http://localhost:8000/api/private-scoped](http://localhost:8000/api/private-scoped) which will throw an error if you don't send an access token with the scope `read:messages` signed with RS256 with the appropriate issuer and audience in the Authorization header.
|
||||
|
||||
```console
|
||||
curl -X 'GET' \
|
||||
'http://localhost:8000/api/private-scoped' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: Bearer <FILL YOUR TOKEN WITH SCOPES HERE>'
|
||||
```
|
BIN
application/.DS_Store
vendored
Normal file
BIN
application/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
application/IBMPlexMono-Bold.ttf
Normal file
BIN
application/IBMPlexMono-Bold.ttf
Normal file
Binary file not shown.
BIN
application/IBMPlexMono-Medium.ttf
Normal file
BIN
application/IBMPlexMono-Medium.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono.zip
Normal file
BIN
application/IBM_Plex_Mono.zip
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/.DS_Store
vendored
Normal file
BIN
application/IBM_Plex_Mono/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-Italic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-Italic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-Light.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-Light.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-Regular.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-Regular.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-Thin.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-Thin.ttf
Normal file
Binary file not shown.
BIN
application/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf
Normal file
BIN
application/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf
Normal file
Binary file not shown.
93
application/IBM_Plex_Mono/OFL.txt
Normal file
93
application/IBM_Plex_Mono/OFL.txt
Normal file
@ -0,0 +1,93 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
application/__pycache__/config.cpython-311.pyc
Normal file
BIN
application/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/database.cpython-311.pyc
Normal file
BIN
application/__pycache__/database.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/main.cpython-311.pyc
Normal file
BIN
application/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/sudoku_generator.cpython-311.pyc
Normal file
BIN
application/__pycache__/sudoku_generator.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/thermal_print.cpython-311.pyc
Normal file
BIN
application/__pycache__/thermal_print.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/utils.cpython-311.pyc
Normal file
BIN
application/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/wordsearch_generator.cpython-311.pyc
Normal file
BIN
application/__pycache__/wordsearch_generator.cpython-311.pyc
Normal file
Binary file not shown.
18
application/config.py
Normal file
18
application/config.py
Normal file
@ -0,0 +1,18 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
auth0_domain: str
|
||||
auth0_api_audience: str
|
||||
auth0_issuer: str
|
||||
auth0_algorithms: str
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_settings():
|
||||
return Settings()
|
1
application/data.json
Normal file
1
application/data.json
Normal file
File diff suppressed because one or more lines are too long
73
application/database.py
Normal file
73
application/database.py
Normal file
@ -0,0 +1,73 @@
|
||||
import json
|
||||
import requests
|
||||
# from datetime import datetime
|
||||
import time
|
||||
import random
|
||||
|
||||
class TodoDatabase:
|
||||
def __init__(self, filename="data.json"):
|
||||
self.filename = filename
|
||||
self.database = {}
|
||||
try:
|
||||
with open(self.filename) as f:
|
||||
self.database = json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
def save_todo(self, date, todo, save=True):
|
||||
if date not in self.database:
|
||||
# if not len(self.database[date]) > 0:
|
||||
self.database[date] = []
|
||||
if todo.recurring:
|
||||
self.database["recurring"].append(todo.model_dump())
|
||||
self.database[date].append(todo.model_dump())
|
||||
|
||||
if save:
|
||||
self._save_database()
|
||||
|
||||
def save_todos(self, date, todos):
|
||||
for todo in todos:
|
||||
self.save_todo(date, todo, save=False)
|
||||
self._save_database()
|
||||
|
||||
def remove_todos(self, date):
|
||||
if date in self.database:
|
||||
del self.database[date]
|
||||
|
||||
def read_todos(self, date):
|
||||
# if self.datebase[date] is None:
|
||||
# return []
|
||||
if date not in self.database:
|
||||
return []
|
||||
return self.database[date]
|
||||
|
||||
def _update_quotes(self):
|
||||
r = requests.get("https://zenquotes.io/api/quotes")
|
||||
res = r.json()
|
||||
|
||||
# if "quotes" not in self.database:
|
||||
self.database["quotes"] = res
|
||||
self.database["quotes_last_updated"] = time.time()
|
||||
self._save_database()
|
||||
|
||||
def get_random_quote(self):
|
||||
# print(time.time())
|
||||
if time.time()-self.database["quotes_last_updated"] > 86400:
|
||||
self._update_quotes()
|
||||
quote = random.choice(self.database["quotes"])
|
||||
|
||||
return [quote['q'], "- " + quote['a']]
|
||||
|
||||
|
||||
def _save_database(self):
|
||||
with open(self.filename, 'w') as f:
|
||||
json.dump(self.database, f)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
data = TodoDatabase()
|
||||
# data._update_quotes()
|
||||
print(data.get_random_quote())
|
||||
|
||||
|
||||
|
148
application/main.py
Normal file
148
application/main.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""Python FastAPI Auth0 integration example
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
from fastapi import FastAPI, Security
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .utils import VerifyToken
|
||||
from .database import TodoDatabase
|
||||
from .thermal_print import ThermalPrinter
|
||||
|
||||
# from wsgiref import simple_server
|
||||
|
||||
# Creates app instance
|
||||
app = FastAPI()
|
||||
auth = VerifyToken()
|
||||
data = TodoDatabase()
|
||||
# printer = ThermalPrinter()
|
||||
|
||||
|
||||
|
||||
class Todo(BaseModel):
|
||||
time: str
|
||||
task: str
|
||||
recurring: bool
|
||||
|
||||
class TodoList(BaseModel):
|
||||
date: str #Always current date OLD TEXT: #Either current date in %Y-%m-%d format or "recurring" for a recurring task
|
||||
todos: list[Todo]
|
||||
|
||||
|
||||
class TodoDate(BaseModel):
|
||||
date: str
|
||||
# date: str = datetime.today().strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
# @app.get("/api/public")
|
||||
# def public():
|
||||
# """No access token required to access this route"""
|
||||
|
||||
# result = {
|
||||
# "status": "success",
|
||||
# "msg": ("Hello from a public endpoint! You don't need to be "
|
||||
# "authenticated to see this.")
|
||||
# }
|
||||
# return result
|
||||
|
||||
|
||||
@app.get("/api/todos/get")
|
||||
def get_todos(date: str = datetime.today().strftime('%Y-%m-%d'), auth_result: str = Security(auth.verify)):
|
||||
"""A valid access token is required to access this route"""
|
||||
|
||||
return {
|
||||
"todos": data.read_todos(date)
|
||||
}
|
||||
|
||||
# result = {
|
||||
# "todos": [
|
||||
# {"time": "1:00pm to 2:00pm", "text": "Read a book"},
|
||||
# {"time": "2:00pm to 3:00pm", "text": "Go for a walk"}
|
||||
# ]
|
||||
# } #TODO: replace with database access
|
||||
|
||||
# return result
|
||||
|
||||
|
||||
# return auth_result
|
||||
|
||||
|
||||
|
||||
@app.post("/api/todos/write")
|
||||
def write_todos(todos: TodoList, auth_result: str = Security(auth.verify)):
|
||||
"""A valid access token is required to access this route"""
|
||||
|
||||
# result = {
|
||||
# "todos": [
|
||||
# {"time": "1:00pm to 2:00pm", "text": "Read a book"},
|
||||
# {"time": "2:00pm to 3:00pm", "text": "Go for a walk"}
|
||||
# ]
|
||||
# } #TODO: replace with database write
|
||||
|
||||
# return result
|
||||
|
||||
# data.save_todo()
|
||||
|
||||
print(todos)
|
||||
|
||||
# if todos.date != "recurring":
|
||||
# data.remove_todos(todos.date)
|
||||
data.remove_todos(todos.date)
|
||||
data.save_todos(todos.date, todos.todos)
|
||||
|
||||
return {"result": todos.date}
|
||||
|
||||
# return todos
|
||||
|
||||
|
||||
# @app.post("/api/todos/write_recurring")
|
||||
# def write_todos_recurring(todos: TodoList, auth_result: str = Security(auth.verify)):
|
||||
|
||||
# data.save_todos("recurring", todos.todos)
|
||||
|
||||
# return {"result": "success"}
|
||||
|
||||
|
||||
@app.get("/api/todos/print")
|
||||
def print_todos(date: str = datetime.today().strftime('%Y-%m-%d'), auth_result: str = Security(auth.verify)):
|
||||
"""A valid access token is required to access this route"""
|
||||
|
||||
# result = {
|
||||
# "todos": [
|
||||
# {"time": "1:00pm to 2:00pm", "text": "Read a book"},
|
||||
# {"time": "2:00pm to 3:00pm", "text": "Go for a walk"}
|
||||
# ]
|
||||
# } #TODO: replace with access to database if no todos provided from request, otherwise print request data
|
||||
|
||||
todos = data.get_todos(date)
|
||||
printer.print_todos()
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
# class SaveTodos:
|
||||
# def on_post(self, req, resp):
|
||||
# pass #Handle web interface sending updated todo list
|
||||
|
||||
# class PrintTodos:
|
||||
# def on_post(self, req, resp):
|
||||
# pass #Handle web interface requesting a print action
|
||||
|
||||
|
||||
# @app.get("/api/get_todos-scoped")
|
||||
# def private_scoped(auth_result: str = Security(auth.verify, scopes=['todos:all'])):
|
||||
# """A valid access token and an appropriate scope are required to access
|
||||
# this route
|
||||
# """
|
||||
|
||||
# return auth_result
|
||||
|
||||
|
||||
app.mount("/", StaticFiles(directory="application/static",html = True), name="static")
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# print('ay')
|
||||
# httpd = simple_server.make_server('127.0.0.1', 8000, app)
|
||||
# httpd.serve_forever()
|
311
application/static/index.html
Normal file
311
application/static/index.html
Normal file
@ -0,0 +1,311 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Test</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="loginPanel" class="flex flex-row items-center justify-center fixed w-full h-full overflow-y-hidden overflow-x-hidden bg-white">
|
||||
|
||||
<button id='loginButton' class="p-2 pl-8 pr-8 bg-purple-500 rounded-lg text-white text-xl font-semibold">Login</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <div id="testToken"></div> -->
|
||||
|
||||
|
||||
<!-- nice colour: bg-gray-800 -->
|
||||
<div id="timePickerWindow" class="absolute w-full h-full overflow-y-hidden top-0 left-0 hidden">
|
||||
<div class="flex flex-row w-full justify-center items-center text-center h-screen">
|
||||
<div class="absolute w-full h-full bg-slate-800 opacity-40 top-0 left-0"></div>
|
||||
<div class="bg-white inline-block text-left rounded-lg overflow-hidden align-bottom transition-all transform shadow-2xl sm:my-8 sm:align-middle sm:max-w-xl sm:w-full">
|
||||
<div class="flex flex-col items-center pt-6 pr-6 pb-6 pl-6">
|
||||
<p class="text-2xl font-semibold leading-none tracking-tighter lg:text-3xl">When?</p>
|
||||
<!-- <p class="mt-3 text-base leading-relaxed text-center">I am a fullstack software developer with ReactJS for frontend and NodeJS for backend</p> -->
|
||||
<!-- <div class="rese"> -->
|
||||
<div id="radios">
|
||||
<input id="rad1" type="radio" name="radioBtn" checked>
|
||||
<label class="labels" for="rad1">
|
||||
<span class="flex flex-col">
|
||||
<span class="font-semibold">Start</span>
|
||||
<span id="startTimeLabel">12:59 am</span>
|
||||
</span>
|
||||
</label>
|
||||
<input id="rad2" type="radio" name="radioBtn">
|
||||
<label class="labels" for="rad2">
|
||||
<span class="flex flex-col">
|
||||
<span class="font-semibold">End</span>
|
||||
<span id="endTimeLabel">12:59 pm</span>
|
||||
</span>
|
||||
</label>
|
||||
<input id="rad3" type="radio" name="radioBtn">
|
||||
<!-- <label class="labels" for="rad3">Third Option</label>
|
||||
-->
|
||||
<div id="bckgrnd"></div>
|
||||
</div>
|
||||
<div class="mt-6 w-full h-full">
|
||||
<div class="flex flex-row space-x-1 items-center justify-center">
|
||||
<input id="timeInput" class="h-11 rounded-lg p-4" style="background-color:rgba(239,239,240,1);" type="time" name="time" value="12:59">
|
||||
<!-- <input id="hourInput" class="bg-blue-400 z-99 w-11 h-11 text-center text-xl font-semibold" type="number" inputmode="numeric" value="12" />
|
||||
<span class="text-xl font-semibold">:</span>
|
||||
<input id="minuteInput" class="bg-blue-400 z-99 w-11 h-11 text-center text-xl font-semibold" type="number" inputmode="numeric" value="59" />
|
||||
<input id="amPmInput" class="bg-blue-400 z-99 w-11 h-11 text-center text-xl font-semibold" type="text" value="AM" /> -->
|
||||
</div>
|
||||
<div id="quickSelectButtons" class="flex flex-row space-x-1 items-center justify-center flex-wrap">
|
||||
<ul class="donate-now flex flex-col items-center justify-center sm:flex-row space-y-7 sm:space-y-0 sm:space-x-4 mt-12 mb-3">
|
||||
<li>
|
||||
<input class="quickButton" type="radio" id="t15" name="amount" />
|
||||
<label class="cursor-pointer border-2 border-slate-300 p-3 pl-5 pr-5 rounded-lg hover:border-indigo-300" for="t15">15 min</label>
|
||||
</li>
|
||||
<li>
|
||||
<input class="quickButton" type="radio" id="t30" name="amount" />
|
||||
<label class="cursor-pointer border-2 border-slate-300 p-3 pl-5 pr-5 rounded-lg hover:border-indigo-300" for="t30">30 min</label>
|
||||
</li>
|
||||
<li>
|
||||
<input class="quickButton" type="radio" id="t60" name="amount" checked="checked" />
|
||||
<label class="cursor-pointer border-2 border-slate-300 p-3 pl-5 pr-5 rounded-lg hover:border-indigo-300" for="t60">1 hour</label>
|
||||
</li>
|
||||
<li>
|
||||
<input class="quickButton" type="radio" id="t120" name="amount" />
|
||||
<label class="cursor-pointer border-2 border-slate-300 p-3 pl-5 pr-5 rounded-lg hover:border-indigo-300" for="t120">2 hours</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mt-6">
|
||||
<a id="saveButton" onclick="saveButtonListener()" class="cursor-pointer flex text-center items-center justify-center w-full pt-4 pr-10 pb-4 pl-10 text-base
|
||||
font-medium text-white bg-indigo-600 rounded-xl transition duration-500 ease-in-out transform
|
||||
hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save</a>
|
||||
<!-- <input class="bg-blue-400 z-99" type="number" inputmode="numeric"/> -->
|
||||
<!-- <input type="number" pattern="[0-9]*" /> -->
|
||||
<!-- <input class="w-full bg-slate-200 absolute top-0 left-0 z-50" type="number" name="test" inputmode="decimal"> -->
|
||||
</div>
|
||||
<div>
|
||||
<!-- <input type="number" inputmode="numeric"/> -->
|
||||
<!-- <input type="number" pattern="\d*"/> -->
|
||||
<!-- <input class="fixed bg-slate-200 top-0 left-0 z-50" type="number" name="test" inputmode="decimal"> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="p-2 bg-white shadow-md rounded-lg border m-2" id="table">
|
||||
<!-- <input type="time"> -->
|
||||
<h2 class="font-semibold text-2xl ml-2">Tasks • <span id="date-view">Sat Aug 12, 2023</span></h2>
|
||||
<!-- <h2 class="font-normal text-sm ml-3">Last Synced: <span id="date-view">Sat Aug 12, 2023</span></h2> -->
|
||||
<li class="task-row p-2 m-1 shadow-md border bg-white min-h-11 rounded-lg flex space-x-2 flex-col" data-editor-shown="false" data-recurring="false">
|
||||
<!-- <a class="handle pr-2 text-2xl justify-center items-center content-center">☰</a> -->
|
||||
<div class="flex grow space-x-2 h-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 handle cursor-grab shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
<span id="timeslot" class="font-semibold text-sm shrink-0 grow-0 h-fit bg-slate-200 text-slate-600 rounded-md p-0.5 pl-1.5 pr-1.5">12pm</span>
|
||||
<span id="task" class="flex grow">Walk to school while jumping on one leg and scratching your head.</span>
|
||||
</div>
|
||||
<div id="expanded-info" class="flex flex-row space-x-1 items-center hidden mt-6 space-x-3">
|
||||
<div class="timePickerWindowButton hover:bg-slate-200 cursor-pointer rounded-md flex flex-row space-x-1 items-center pr-1">
|
||||
<span class="w-8 h-8 flex space-x-2 space-y-2 items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span id="timeButtonDisplay" class="text-sm font-semibold">12:15pm to 1:15pm</span>
|
||||
</div>
|
||||
|
||||
<div class="recurringButton hover:bg-slate-200 cursor-pointer rounded-md flex flex-row space-x-1 items-center">
|
||||
<span class="w-8 h-8 flex space-x-2 space-y-2 items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-black">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- font-semibold text-sm shrink-0 grow-0 h-fit bg-slate-200 text-slate-600 rounded-md p-0.5 pl-1.5 pr-1.5 -->
|
||||
<div class="p-2 m-1 font-semibold text-slate-600 align-text-top">
|
||||
<div onclick="addTask();" class="cursor-pointer flex flex-row space-x-1 hover:bg-slate-200 w-fit p-2 rounded-lg text-center items-center">
|
||||
<span class="">+</span>
|
||||
<span>Add Task</span>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
<!-- jsDelivr :: Sortable :: Latest (https://www.jsdelivr.com/package/npm/sortablejs) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var ta = document.getElementById("table")
|
||||
Sortable.create(ta, {
|
||||
handle: '.handle',
|
||||
animation: 150
|
||||
});
|
||||
</script>
|
||||
<!-- jsDelivr :: Swiper :: Latest (https://www.jsdelivr.com/package/npm/swiper) -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var defaults = {
|
||||
pagination: '.swiper-pagination',
|
||||
slidesPerView: 3,
|
||||
freeMode: true,
|
||||
freeModeSticky: false,
|
||||
freeModeMomentumRatio: 0.25,
|
||||
freeModeVelocityRatio: 0.25,
|
||||
freeModeMinimumVelocity: 0.1,
|
||||
mousewheelControl: true,
|
||||
mousewheelSensitivity: 0.5,
|
||||
loop: false,
|
||||
loopAdditionalSlides: 5,
|
||||
direction: 'vertical',
|
||||
slideToClickedSlide: true,
|
||||
centeredSlides: true
|
||||
};
|
||||
|
||||
new Swiper(
|
||||
'.swiper-container.hours',
|
||||
Object.assign({}, defaults, { initialSlide: 13})
|
||||
);
|
||||
|
||||
new Swiper(
|
||||
'.swiper-container.minutes',
|
||||
Object.assign({}, defaults, { initialSlide: 37})
|
||||
);
|
||||
|
||||
new Swiper('.swiper-container.seconds', defaults);
|
||||
</script> -->
|
||||
<!-- jsDelivr :: Day.js :: Latest (https://www.jsdelivr.com/package/npm/dayjs) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/customParseFormat.js"></script>
|
||||
<script type="text/javascript">
|
||||
dayjs.extend(window.dayjs_plugin_customParseFormat)
|
||||
|
||||
let now = dayjs()
|
||||
document.getElementById("startTimeLabel").innerHTML = now.format("h:mm a")
|
||||
document.getElementById("endTimeLabel").innerHTML = now.add(1, 'h').format("h:mm a")
|
||||
document.getElementById("timeInput").value = dayjs().format("HH:mm")
|
||||
|
||||
|
||||
// console.log(dayjs().format("HH:mm"))
|
||||
// console.log(dayjs().add(1, 'h').format("HH:mm"))
|
||||
</script>
|
||||
|
||||
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.0/auth0-spa-js.production.js"></script>
|
||||
<script>
|
||||
|
||||
// $('#getTokenPopup').click(async () => {
|
||||
// const token = await auth0.getTokenWithPopup({
|
||||
// authorizationParams: {
|
||||
// audience: 'https://mydomain/api/',
|
||||
// scope: 'read:rules'
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// import { createAuth0Client } from '@auth0/auth0-spa-js';
|
||||
let jToken = ""
|
||||
|
||||
function loginAction(e) {
|
||||
auth0.createAuth0Client({
|
||||
domain: 'dev-kazsp1tz0e7t5d07.us.auth0.com',
|
||||
clientId: 'Pw2MvNmIAJUA4THZzsZTEeqkXnCTHYr3'
|
||||
}).then(a0 => {
|
||||
const token = a0.getTokenWithPopup({
|
||||
authorizationParams: {
|
||||
audience: 'https://RecieptTodos.imsam.ca',
|
||||
scope: 'todos:all'
|
||||
}
|
||||
});
|
||||
token.then(t => {
|
||||
document.getElementById("loginPanel").classList.add("hidden")
|
||||
console.log(t)
|
||||
jToken = t
|
||||
|
||||
// document.getElementById("testToken").innerHTML = t
|
||||
|
||||
fetch('/api/todos/get?' + new URLSearchParams({
|
||||
date: dayjs().format("YYYY-MM-DD"),
|
||||
}), {
|
||||
method: 'GET',
|
||||
withCredentials: true,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Authorization': "Bearer " + t,
|
||||
}
|
||||
}).then(re => {
|
||||
re.json().then(jso => {
|
||||
console.log(jso)
|
||||
for (let todo of jso.todos) {
|
||||
console.log(todo)
|
||||
// function addTask(defaultTimeRange="", defaultTaskText="New Task", editable=true) {
|
||||
addTask(defaultTimeRange=todo.time, defaultTaskText=todo.text, editable=false)
|
||||
}
|
||||
})
|
||||
// var items = JSON.parse(re.json())
|
||||
// console.log(items.json)
|
||||
})
|
||||
|
||||
//TODO: Make call with token to backend to load all current todos
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
document.getElementById("loginButton").addEventListener('click', loginAction)
|
||||
// document.getElementById("loginButton").addEventListener('click', (e) => {
|
||||
// auth0.createAuth0Client({
|
||||
// domain: 'dev-kazsp1tz0e7t5d07.us.auth0.com',
|
||||
// clientId: 'Pw2MvNmIAJUA4THZzsZTEeqkXnCTHYr3'
|
||||
// }).then(a0 => {
|
||||
// const token = a0.getTokenWithPopup({
|
||||
// authorizationParams: {
|
||||
// audience: 'https://RecieptTodos.imsam.ca',
|
||||
// scope: 'todos:all'
|
||||
// }
|
||||
// });
|
||||
// token.then(t => {
|
||||
// document.getElementById("loginPanel").classList.add("hidden")
|
||||
// console.log(t)
|
||||
// jToken = t
|
||||
|
||||
// // document.getElementById("testToken").innerHTML = t
|
||||
|
||||
// fetch('/api/todos/get?' + new URLSearchParams({
|
||||
// date: dayjs().format("YYYY-MM-DD"),
|
||||
// }), {
|
||||
// method: 'GET',
|
||||
// withCredentials: true,
|
||||
// credentials: 'include',
|
||||
// headers: {
|
||||
// 'Authorization': "Bearer " + t,
|
||||
// }
|
||||
// }).then(re => {
|
||||
// re.json().then(jso => {
|
||||
// console.log(jso)
|
||||
// for (let todo of jso.todos) {
|
||||
// console.log(todo)
|
||||
// // function addTask(defaultTimeRange="", defaultTaskText="New Task", editable=true) {
|
||||
// addTask(defaultTimeRange=todo.time, defaultTaskText=todo.text, editable=false)
|
||||
// }
|
||||
// })
|
||||
// // var items = JSON.parse(re.json())
|
||||
// // console.log(items.json)
|
||||
// })
|
||||
|
||||
// //TODO: Make call with token to backend to load all current todos
|
||||
// })
|
||||
// });
|
||||
|
||||
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
<script src="scripts.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
601
application/static/scripts.js
Normal file
601
application/static/scripts.js
Normal file
@ -0,0 +1,601 @@
|
||||
// <script type="text/javascript">
|
||||
|
||||
/*
|
||||
|
||||
<li class="task-row p-2 m-1 shadow-md border bg-white rounded-lg flex items-center space-x-1" data-editor-shown="false">
|
||||
<!-- <a class="handle pr-2 text-2xl justify-center items-center content-center">☰</a> -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 handle mr-2 cursor-grab shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
|
||||
|
||||
<span id="timeslot" class="font-semibold shrink-0">11:00-12:30 • </span>
|
||||
<span id="task" class="flex grow shrink">Row 4</span>
|
||||
</li>
|
||||
|
||||
*/
|
||||
|
||||
function singleClickListener(e) {
|
||||
// console.log(e)
|
||||
// e.target.style.height += 100;
|
||||
// e.target.classList.remove("h-11")
|
||||
// e.target.classList.add("h-24")
|
||||
|
||||
console.log(e.target.tagName)
|
||||
console.log(e.target.parentElement.tagName)
|
||||
|
||||
|
||||
if (e.target.tagName !== "LI" && e.target.parentElement.tagName !== "LI" && e.target.parentElement.parentElement.tagName !== "LI") return;
|
||||
|
||||
if (e.target.tagName === "SPAN" && e.target.contentEditable === "true") return;
|
||||
|
||||
|
||||
let activeEle = e.target
|
||||
if (e.target.parentElement.tagName === "LI") {
|
||||
activeEle = e.target.parentElement
|
||||
}
|
||||
if (e.target.parentElement.parentElement.tagName == "LI") {
|
||||
activeEle = e.target.parentElement.parentElement
|
||||
}
|
||||
|
||||
|
||||
|
||||
// var height = activeEle.offsetHeight;
|
||||
|
||||
|
||||
if (activeEle.dataset.expandedView === 'true') {
|
||||
// var newHeight = height - 75;
|
||||
activeEle.style.height = null;
|
||||
activeEle.dataset.expandedView = false
|
||||
activeEle.querySelector("#task").contentEditable = "false"
|
||||
activeEle.querySelector("#expanded-info").classList.add("hidden")
|
||||
|
||||
// activeEle.querySelector("#task").blur()
|
||||
// activeEle.classList.add("h-44")
|
||||
// activeEle.classList.remove("h-44")
|
||||
|
||||
} else {
|
||||
// var newHeight = height + 75;
|
||||
activeEle.style.height = (activeEle.offsetHeight + 75) + 'px';
|
||||
activeEle.dataset.expandedView = true
|
||||
activeEle.querySelector("#task").contentEditable = "true"
|
||||
activeEle.querySelector("#task").focus()
|
||||
selectText(activeEle.querySelector("#task"))
|
||||
activeEle.querySelector("#expanded-info").classList.remove("hidden")
|
||||
}
|
||||
|
||||
|
||||
// console.log(newHeight)
|
||||
// activeEle.style.height = newHeight + 'px';
|
||||
|
||||
}
|
||||
|
||||
// let focOutFired = false
|
||||
let triggerFocOutEvent = true
|
||||
|
||||
function onClickListener(e) {
|
||||
// if (!focOutFired) return;
|
||||
|
||||
// focOutFired = false
|
||||
triggerFocOutEvent = false
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
|
||||
function blurOrKeypress(e) {
|
||||
// split up largely for readability
|
||||
console.log(e)
|
||||
// console.log(document.activeElement)
|
||||
if (!triggerFocOutEvent) {
|
||||
triggerFocOutEvent = true
|
||||
return
|
||||
}
|
||||
if (e.target.tagName !== "SPAN" && e.target.id !== "task") return false;
|
||||
if ((e.type === 'keypress' && e.code != 'Enter' && !e.ctrlKey) || e.target.parentElement.parentElement.dataset.expandedView !== "true") return false;
|
||||
|
||||
// if (e.type === "focusout") focOutFired = true;
|
||||
|
||||
// if () {}
|
||||
|
||||
// if (e.target.parentElement.parentElement.dataset.expandedView === "true") {
|
||||
// e.target.contentEditable = "false"
|
||||
// e.target.parentElement.parentElement.style.height = null;
|
||||
// e.target.parentElement.parentElement.dataset.expandedView = false
|
||||
// e.target.parentElement.parentElement.querySelector("#expanded-info").classList.add("hidden")
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (e.type === 'keypress' && (e.code == 'Enter' || e.ctrlKey)) {
|
||||
e.target.contentEditable = "false"
|
||||
e.target.blur()
|
||||
e.target.parentElement.parentElement.style.height = null;
|
||||
e.target.parentElement.parentElement.dataset.expandedView = false
|
||||
e.target.parentElement.parentElement.querySelector("#expanded-info").classList.add("hidden")
|
||||
|
||||
|
||||
if (e.target.innerHTML === "") {
|
||||
e.target.parentElement.parentElement.parentElement.removeChild(e.target.parentElement.parentElement)
|
||||
// return
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// if (e.target.tagName !== 'INPUT') return false;
|
||||
// if (e.type === 'keypress' && e.code != 'Enter' && !e.ctrlKey) return false;
|
||||
|
||||
// // a parent, a row, and a newly minted text node walk into a bar...
|
||||
// const parent = e.target.parentElement;
|
||||
// const row = parent.parentElement;
|
||||
// const text = document.createTextNode(e.target.value || e.target.placeholder);
|
||||
// /* .isConnected refers to it's state in the DOM. this was some work to try and stop an error that was
|
||||
// ocurring due to this being simultaneously the 'blur' 'keypress' event handler. Alas, it didn't.
|
||||
// If the error is really an issue, then wrapping the parent.replaceChild in a try/catch block should solve it for you.*/
|
||||
// if (e.target.isConnected) {
|
||||
// // use the dataset key + the textarea's value to update the definitions.
|
||||
// // definitions[row.dataset.key] = e.target.value;
|
||||
// // write those to the local storage
|
||||
// // localStorage.setItem('definitions', JSON.stringify(definitions));
|
||||
|
||||
// // Or, if you are using a database, you would use some variety of AJAX/XHR call here.
|
||||
|
||||
// // get rid of our text element
|
||||
// parent.replaceChild(text, e.target);
|
||||
// // reset the editorshown value in case we need to update this again
|
||||
// row.dataset.editorShown = false;
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
// <li class="task-row p-2 m-1 shadow-md border bg-white min-h-11 rounded-lg flex space-x-2 flex-col" data-editor-shown="false">
|
||||
// <!-- <a class="handle pr-2 text-2xl justify-center items-center content-center">☰</a> -->
|
||||
// <div class="flex grow space-x-2 h-full">
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 handle cursor-grab shrink-0">
|
||||
// <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
// </svg>
|
||||
|
||||
|
||||
// <span id="timeslot" class="font-semibold text-sm shrink-0 grow-0 h-fit bg-slate-200 text-slate-600 rounded-md p-0.5 pl-1.5 pr-1.5">1pm</span>
|
||||
// <span id="task" class="flex grow">Walk to school while jumping on one leg and scratching your head.</span>
|
||||
// </div>
|
||||
|
||||
// <div id="expanded-info" class="flex flex-row space-x-1 items-center hidden mt-6">
|
||||
// <div class="timePickerWindowButton hover:bg-slate-200 cursor-pointer rounded-md flex flex-row space-x-1 items-center pr-1">
|
||||
// <span class="w-8 h-8 flex space-x-2 space-y-2 items-center justify-center">
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
// <path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
// </svg>
|
||||
// </span>
|
||||
// <span id="timeButtonDisplay" class="text-sm font-semibold">12:15pm to 1:15pm</span>
|
||||
// </div>
|
||||
|
||||
// </div>
|
||||
// </li>
|
||||
|
||||
function selectText(node) {
|
||||
/* Helper function slightly modified from: https://stackoverflow.com/a/987376 */
|
||||
|
||||
// const node = document.getElementById(nodeId);
|
||||
|
||||
if (document.body.createTextRange) {
|
||||
const range = document.body.createTextRange();
|
||||
range.moveToElementText(node);
|
||||
range.select();
|
||||
} else if (window.getSelection) {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(node);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
} else {
|
||||
console.warn("Could not select text in node: Unsupported browser.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addTask(defaultTimeRange="", defaultTaskText="New Task", editable=true) {
|
||||
/* defaultTimeRange must be in format h:mma to h:mma (e.g: 1:15pm to 2:15pm) */
|
||||
let rows = document.getElementById("table")
|
||||
|
||||
const row = document.createElement("li")
|
||||
row.dataset.editorShown = false
|
||||
row.dataset.recurring = false
|
||||
row.className = "task-row p-2 m-1 shadow-md border bg-white min-h-11 rounded-lg flex space-x-2 flex-col"
|
||||
|
||||
let firstContainer = document.createElement("div")
|
||||
firstContainer.className = "flex grow space-x-2 h-full"
|
||||
firstContainer.innerHTML += "<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6 handle cursor-grab shrink-0'><path stroke-linecap='round' stroke-linejoin='round' d='M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5' /></svg>"
|
||||
|
||||
let timeslotSpan = document.createElement("span")
|
||||
timeslotSpan.id = "timeslot"
|
||||
timeslotSpan.className = "font-semibold text-sm shrink-0 grow-0 h-fit bg-slate-200 text-slate-600 rounded-md p-0.5 pl-1.5 pr-1.5 hidden"
|
||||
if (defaultTimeRange !== "") {
|
||||
timeslotSpan.classList.remove("hidden")
|
||||
let parsedTime = dayjs(defaultTimeRange.split(' to ')[0], 'h:mma')
|
||||
|
||||
timeslotSpan.innerHTML = parsedTime.format("h:mma")
|
||||
if (parsedTime.minute() == 0) {
|
||||
timeslotSpan.innerHTML = parsedTime.format("ha")
|
||||
}
|
||||
}
|
||||
firstContainer.appendChild(timeslotSpan)
|
||||
|
||||
let taskSpan = document.createElement("span")
|
||||
taskSpan.id = "task"
|
||||
taskSpan.className = "flex grow"
|
||||
taskSpan.innerHTML = defaultTaskText
|
||||
taskSpan.contentEditable = editable
|
||||
firstContainer.appendChild(taskSpan)
|
||||
|
||||
row.appendChild(firstContainer)
|
||||
|
||||
|
||||
let secondContainer = document.createElement("div")
|
||||
secondContainer.id = "expanded-info"
|
||||
secondContainer.className = "flex flex-row space-x-1 items-center mt-6 hidden"
|
||||
|
||||
let timePickerWindowButton = document.createElement("div")
|
||||
timePickerWindowButton.className = "timePickerWindowButton hover:bg-slate-200 cursor-pointer rounded-md flex flex-row space-x-1 items-center pr-1"
|
||||
secondContainer.appendChild(timePickerWindowButton)
|
||||
|
||||
let clockSpan = document.createElement("span")
|
||||
clockSpan.className = "w-8 h-8 flex space-x-2 space-y-2 items-center justify-center"
|
||||
clockSpan.innerHTML += "<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'><path stroke-linecap='round' stroke-linejoin='round' d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z' /></svg>"
|
||||
timePickerWindowButton.appendChild(clockSpan)
|
||||
|
||||
let timeButtonDisplay = document.createElement("span")
|
||||
timeButtonDisplay.id = "timeButtonDisplay"
|
||||
timeButtonDisplay.className = "text-sm font-semibold"
|
||||
timeButtonDisplay.innerHTML = defaultTimeRange
|
||||
timePickerWindowButton.appendChild(timeButtonDisplay)
|
||||
|
||||
row.appendChild(secondContainer)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// row.innerHTML += "<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6 handle mr-2 cursor-grab shrink-0'><path stroke-linecap='round' stroke-linejoin='round' d='M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5'/></svg>"
|
||||
|
||||
// const t = document.createElement("span")
|
||||
// t.id = "timeslot"
|
||||
// t.className = "font-semibold shrink-0"
|
||||
// t.innerHTML = "xx:xx-xx:xx • "
|
||||
// const ed = document.createElement("span")
|
||||
// ed.id = "task"
|
||||
// ed.className = "flex grow shrink"
|
||||
// ed.innerHTML = "New Task"
|
||||
|
||||
// row.appendChild(t)
|
||||
// row.appendChild(ed)
|
||||
|
||||
// const ta = document.createElement('input')
|
||||
// ta.classList.add('grow')
|
||||
// ta.placeholder = ed.innerHTML
|
||||
// ed.replaceChild(ta, ed.firstChild)
|
||||
|
||||
rows.insertBefore(row, rows.lastElementChild)
|
||||
|
||||
|
||||
if (editable) {
|
||||
row.style.height = (row.offsetHeight + 75) + 'px';
|
||||
row.dataset.expandedView = true
|
||||
// row.querySelector("#task").contentEditable = "true"
|
||||
// row.querySelector("#task").focus()
|
||||
taskSpan.focus()
|
||||
selectText(taskSpan)
|
||||
secondContainer.classList.remove("hidden")
|
||||
// row.querySelector("#expanded-info").classList.remove("hidden")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
row.addEventListener('keypress', blurOrKeypress);
|
||||
row.addEventListener('focusout', blurOrKeypress);
|
||||
secondContainer.addEventListener('mousedown', onClickListener)
|
||||
timePickerWindowButton.addEventListener("click", showTimePickerWindow)
|
||||
timeslotSpan.addEventListener("mousedown", timeSlotShowTimePickerButton)
|
||||
row.addEventListener('click', singleClickListener);
|
||||
|
||||
// ed.firstChild.focus()
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function dblListener(e) {
|
||||
|
||||
let tarEle = e.target
|
||||
const parent = tarEle.parentElement;
|
||||
|
||||
if (tarEle.tagName === "LI" && tarEle.dataset.editorShown !== 'true') {
|
||||
const ed = tarEle.querySelector('span:last-child')
|
||||
const ta = document.createElement('input')
|
||||
ta.classList.add('grow')
|
||||
ta.placeholder = ed.innerHTML
|
||||
ed.replaceChild(ta, ed.firstChild)
|
||||
ed.firstChild.focus()
|
||||
tarEle.dataset.editorShown = true
|
||||
return
|
||||
}
|
||||
|
||||
if (parent.tagName !== 'LI' || parent.dataset.editorShown === 'true' || tarEle.dataset.editorShown === 'true') return;
|
||||
|
||||
|
||||
const ed = parent.querySelector('span:last-child')
|
||||
const ta = document.createElement('input')
|
||||
ta.classList.add('grow')
|
||||
// ta.classList.add('w-1/2')
|
||||
ta.placeholder = ed.innerHTML
|
||||
ed.replaceChild(ta, ed.firstChild)
|
||||
ed.firstChild.focus()
|
||||
parent.dataset.editorShown = true
|
||||
|
||||
}
|
||||
|
||||
|
||||
function timeListener(e) {
|
||||
console.log(e.target.value)
|
||||
let parsedDate = dayjs(e.target.value, 'HH:mm')
|
||||
let activeZone = "startTimeLabel"
|
||||
|
||||
if (document.getElementById("rad2").checked) {
|
||||
activeZone = "endTimeLabel"
|
||||
}
|
||||
|
||||
document.getElementById(activeZone).innerHTML = parsedDate.format('h:mm a')
|
||||
// document.getElementById("endTimeLabel").innerHTML = parsedDate.add().format('h:mm a')
|
||||
|
||||
// console.log(dayjs(e.target.value, 'HH:mm').add(1, 'h').format("HH:mm"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
function endTimeListener(e) {
|
||||
if (!e.target.checked) return;
|
||||
|
||||
document.getElementById("quickSelectButtons").classList.add("hidden")
|
||||
document.getElementById("timeInput").value = dayjs(document.getElementById("endTimeLabel").innerHTML, 'h:mm a').format('HH:mm')
|
||||
}
|
||||
|
||||
function startTimeListener(e, triggeredFromCode=false) {
|
||||
if (!triggeredFromCode) {
|
||||
if (!e.target.checked) return;
|
||||
}
|
||||
|
||||
document.getElementById("quickSelectButtons").classList.remove("hidden")
|
||||
document.getElementById("timeInput").value = dayjs(document.getElementById("startTimeLabel").innerHTML, 'h:mm a').format('HH:mm')
|
||||
let startTime = dayjs(document.getElementById("startTimeLabel").innerHTML, 'h:mm a')
|
||||
let endTime = dayjs(document.getElementById("endTimeLabel").innerHTML, 'h:mm a')
|
||||
let idToAmount = {
|
||||
"t15": 15,
|
||||
"t30": 30,
|
||||
"t60": 60,
|
||||
"t120": 120
|
||||
}
|
||||
for (let button of document.getElementsByClassName("quickButton")) {
|
||||
if (endTime.isSame(startTime.add(idToAmount[button.id], 'm'))) {
|
||||
button.checked = 'checked'
|
||||
return
|
||||
}
|
||||
button.checked = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function quickButtonListener(e) {
|
||||
if (!e.target.checked) return;
|
||||
let idToAmount = {
|
||||
"t15": 15,
|
||||
"t30": 30,
|
||||
"t60": 60,
|
||||
"t120": 120
|
||||
}
|
||||
|
||||
console.log(e.target.id)
|
||||
console.log(document.getElementById("startTimeLabel").innerHTML)
|
||||
console.log(dayjs(document.getElementById("startTimeLabel").innerHTML, "h:mm a"))
|
||||
document.getElementById("endTimeLabel").innerHTML = dayjs(document.getElementById("startTimeLabel").innerHTML, "h:mm a").add(idToAmount[e.target.id], 'm').format('h:mm a')
|
||||
}
|
||||
|
||||
|
||||
function saveButtonListener(e) {
|
||||
let startTime = dayjs(document.getElementById("startTimeLabel").innerHTML, 'h:mm a')
|
||||
let endTime = dayjs(document.getElementById("endTimeLabel").innerHTML, 'h:mm a')
|
||||
console.log('??')
|
||||
console.log(document.getElementsByClassName("timePickerWindowButton"))
|
||||
|
||||
startTimeListener("", triggeredFromCode=true)
|
||||
document.getElementById("rad1").checked = "clicked"
|
||||
document.getElementById("rad2").checked = null
|
||||
document
|
||||
|
||||
for (let b of document.getElementsByClassName("timePickerWindowButton")) {
|
||||
// let b = bu.querySelector("#timeButtonDisplay")
|
||||
console.log('==')
|
||||
console.log(b)
|
||||
console.log(b.dataset.timepickershown)
|
||||
console.log('===')
|
||||
|
||||
|
||||
if (b.dataset.timepickershown === 'true') {
|
||||
b.dataset.timepickershown = false
|
||||
b.querySelector("#timeButtonDisplay").innerHTML = startTime.format('h:mma') + " to " + endTime.format('h:mma')
|
||||
b.parentElement.parentElement.querySelector("#timeslot").innerHTML = startTime.format('ha')
|
||||
console.log(startTime.minute())
|
||||
if (startTime.minute() != 0) {
|
||||
b.parentElement.parentElement.querySelector("#timeslot").innerHTML = startTime.format('h:mma')
|
||||
}
|
||||
b.parentElement.parentElement.querySelector("#timeslot").classList.remove("hidden")
|
||||
document.getElementById("timePickerWindow").classList.add("hidden")
|
||||
|
||||
|
||||
b.parentElement.parentElement.querySelector("#task").contentEditable = "false"
|
||||
b.parentElement.parentElement.querySelector("#task").blur()
|
||||
b.parentElement.parentElement.style.height = null;
|
||||
b.parentElement.parentElement.dataset.expandedView = false
|
||||
b.parentElement.parentElement.querySelector("#expanded-info").classList.add("hidden")
|
||||
|
||||
return
|
||||
}
|
||||
// b.addEventListener("click", showTimePickerWindow)
|
||||
}
|
||||
|
||||
// console.log(startTime.format('ha'))
|
||||
// console.log(startTime.format('h:mma') + " to " + endTime.format('h:mma'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// document.getElementById("timePickerWindowButton")
|
||||
function showTimePickerWindow(e) {
|
||||
// console.log(e.target)
|
||||
// console.log(e.target.dataset.timepickershown)
|
||||
// if (e.target.dataset.timepickershown === 'true') return;
|
||||
|
||||
// e.target.dataset.timepickershown = true
|
||||
|
||||
console.log(e.target)
|
||||
console.log(e.target.dataset.timepickershown)
|
||||
if (this.dataset.timepickershown === 'true') return;
|
||||
|
||||
this.dataset.timepickershown = true
|
||||
|
||||
// document.getElementById("")
|
||||
let times = this.querySelector("#timeButtonDisplay").innerHTML.split(" to ")
|
||||
if (times[0] == "") {
|
||||
times[0] = dayjs().format("h:mma")
|
||||
times[1] = dayjs().add(1, 'h').format("h:mma")
|
||||
}
|
||||
console.log(times)
|
||||
// console.log(startTime)
|
||||
// console.log(endTime)
|
||||
|
||||
document.getElementById("startTimeLabel").innerHTML = dayjs(times[0], 'h:mma').format("h:mm a")
|
||||
document.getElementById("endTimeLabel").innerHTML = dayjs(times[1], 'h:mma').format("h:mm a")
|
||||
|
||||
document.getElementById("timeInput").value = dayjs(times[0], 'h:mma').format("HH:mm")
|
||||
document.getElementById("timePickerWindow").classList.remove("hidden")
|
||||
|
||||
}
|
||||
|
||||
|
||||
function timeSlotShowTimePickerButton(e) {
|
||||
console.log('yay!')
|
||||
if (this.parentElement.parentElement.querySelector(".timePickerWindowButton").dataset.timepickershown === 'true') return;
|
||||
console.log('woohoo')
|
||||
this.parentElement.parentElement.querySelector(".timePickerWindowButton").dataset.timepickershown = true
|
||||
|
||||
let times = this.parentElement.parentElement.querySelector(".timePickerWindowButton").querySelector("#timeButtonDisplay").innerHTML.split(" to ")
|
||||
if (times[0] == "") {
|
||||
times[0] = dayjs().format("h:mma")
|
||||
times[1] = dayjs().add(1, 'h').format("h:mma")
|
||||
}
|
||||
console.log(times)
|
||||
// console.log(startTime)
|
||||
// console.log(endTime)
|
||||
|
||||
document.getElementById("startTimeLabel").innerHTML = dayjs(times[0], 'h:mma').format("h:mm a")
|
||||
document.getElementById("endTimeLabel").innerHTML = dayjs(times[1], 'h:mma').format("h:mm a")
|
||||
|
||||
document.getElementById("timeInput").value = dayjs(times[0], 'h:mma').format("HH:mm")
|
||||
document.getElementById("timePickerWindow").classList.remove("hidden")
|
||||
|
||||
}
|
||||
|
||||
|
||||
function recurringButtonListener(e) {
|
||||
|
||||
// TODO: change colour to green
|
||||
//
|
||||
|
||||
if (this.parentElement.parentElement.dataset.recurring === "true") {
|
||||
this.querySelector("svg").classList.add("text-black")
|
||||
this.querySelector("svg").classList.remove("text-lime-600")
|
||||
this.parentElement.parentElement.dataset.recurring = false
|
||||
return
|
||||
}
|
||||
this.querySelector("svg").classList.remove("text-black")
|
||||
this.querySelector("svg").classList.add("text-lime-600")
|
||||
this.parentElement.parentElement.dataset.recurring = true
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
for (let rb of document.getElementsByClassName("recurringButton")) {
|
||||
rb.addEventListener("click", recurringButtonListener)
|
||||
}
|
||||
|
||||
|
||||
let rows = document.getElementsByClassName("task-row");
|
||||
console.log('hmm');
|
||||
for (let row of rows) {
|
||||
console.log(row.querySelector("#timeslot"))
|
||||
row.querySelector("#timeslot").addEventListener("mousedown", timeSlotShowTimePickerButton)
|
||||
|
||||
row.addEventListener('keypress', blurOrKeypress);
|
||||
row.addEventListener('focusout', blurOrKeypress);
|
||||
// row.addEventListener('dblclick', dblListener)
|
||||
// row.addEventListener('mousedown', onClickListener)
|
||||
let ei = row.querySelector("#expanded-info")
|
||||
if (ei != null) {
|
||||
ei.addEventListener('mousedown', onClickListener)
|
||||
}
|
||||
|
||||
row.addEventListener('click', singleClickListener);
|
||||
}
|
||||
|
||||
for (let button of document.getElementsByClassName("quickButton")) {
|
||||
button.addEventListener("change", quickButtonListener)
|
||||
}
|
||||
|
||||
for (let b of document.getElementsByClassName("timePickerWindowButton")) {
|
||||
b.addEventListener("click", showTimePickerWindow)
|
||||
}
|
||||
|
||||
document.getElementById("timeInput").addEventListener("blur", timeListener)
|
||||
document.getElementById("rad1").addEventListener("change", startTimeListener)
|
||||
document.getElementById("rad2").addEventListener("change", endTimeListener)
|
||||
|
||||
|
||||
document.getElementById("date-view").innerHTML = dayjs().format("ddd MMM D, YYYY")
|
||||
|
||||
// document.getElementById("rad1").addEventListener("click", )
|
||||
|
||||
// document.getElementById("timeInput").showPicker()
|
||||
|
||||
// hourInput
|
||||
// minuteInput
|
||||
// amPmInput
|
||||
|
||||
// let hIn = document.getElementById("hourInput")
|
||||
// let mIn = document.getElementById("minuteInput")
|
||||
// let apIn = document.getElementById("amPmInput")
|
||||
|
||||
// hIn.addEventListener("keydown", timeInputListener);
|
||||
// mIn.addEventListener("keydown", timeInputListener);
|
||||
// apIn.addEventListener("keydown", timeInputListener);
|
||||
|
||||
// function timeInputListener(e) {
|
||||
// let tarEle = e.target
|
||||
|
||||
// if (tarEle.id === "hourInput") {
|
||||
// if (parseInt(tarEle.value) > 12) {
|
||||
|
||||
// }
|
||||
// }
|
||||
// if (tarEle.id === "minuteInput") {
|
||||
|
||||
// }
|
||||
// if (tarEle.id === "amPmInput") {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
196
application/static/styles.css
Normal file
196
application/static/styles.css
Normal file
@ -0,0 +1,196 @@
|
||||
#radios {
|
||||
position: relative;
|
||||
background-color:rgba(239,239,240,1);
|
||||
z-index:5;
|
||||
width: 245.5px;
|
||||
border-radius: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#bckgrnd,
|
||||
.labels {
|
||||
width: 120px;
|
||||
height: 66px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
margin-right: -3px;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.labels {
|
||||
padding-top: 7.5px;
|
||||
}
|
||||
|
||||
#bckgrnd {
|
||||
width: 115px;
|
||||
height: 55px;
|
||||
background-color: white;
|
||||
border: .5px solid rgba(0,0,0,0.04);
|
||||
box-shadow: 0 3px 8px 0 rgba(0,0,0,0.12), 0 3px 1px 0 rgba(0,0,0,0.04);
|
||||
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
border-radius: 7px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#rad1:checked ~ #bckgrnd {
|
||||
transform: translateX(0);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#rad2:checked ~ #bckgrnd {
|
||||
transform: translateX(120px);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.picker {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
overflow: hidden;
|
||||
margin: 1rem auto 0;
|
||||
outline: 1px solid #ccc;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.swiper-container {
|
||||
width: 80px;
|
||||
height: 210px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
opacity: 0.25;
|
||||
transition: opacity 0.3s ease;
|
||||
cursor: default;
|
||||
font-weight: bold;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.swiper-slide-prev,
|
||||
.swiper-slide-next {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swiper-slide-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vizor {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
transform: translateY(-50%);
|
||||
font-size: 2rem;
|
||||
line-height: 62px;
|
||||
}
|
||||
|
||||
.vizor:before,
|
||||
.vizor:after {
|
||||
content: ':';
|
||||
display: inline-block;
|
||||
line-height: inherit;
|
||||
height: 100%;
|
||||
position:absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.vizor:before {
|
||||
left: 95px;
|
||||
}
|
||||
|
||||
.vizor:after {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.arrows .swiper-container:after,
|
||||
.arrows .swiper-container:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 4px;
|
||||
border-color: transparent;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.arrows .swiper-container:before {
|
||||
top: 0.5rem;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: currentColor;
|
||||
}
|
||||
|
||||
.arrows .swiper-container:after {
|
||||
bottom: 0.5rem;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: currentColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*.donate-now {*/
|
||||
/* list-style-type: none;*/
|
||||
/* margin: 25px 0 0 0;*/
|
||||
/* padding: 0;*/
|
||||
/*}*/
|
||||
|
||||
/*.donate-now li {
|
||||
float: left;
|
||||
margin: 0 5px 0 0;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
}*/
|
||||
|
||||
/*.donate-now label,
|
||||
.donate-now input {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}*/
|
||||
|
||||
/*.donate-now input[type="radio"] {
|
||||
opacity: 0.01;
|
||||
z-index: 100;
|
||||
}*/
|
||||
|
||||
.donate-now input[type="radio"]:checked+label { /*, .Checked+label */
|
||||
border-color: rgb(79 70 229);
|
||||
}
|
||||
|
||||
/*.donate-now label {
|
||||
padding: 5px;
|
||||
border: 1px solid #CCC;
|
||||
cursor: pointer;
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.donate-now label:hover {
|
||||
background: #DDD;
|
||||
}
|
BIN
application/sudoku.png
Normal file
BIN
application/sudoku.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
190
application/sudoku_generator.py
Normal file
190
application/sudoku_generator.py
Normal file
@ -0,0 +1,190 @@
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
|
||||
def generate_sudoku():
|
||||
base = 3
|
||||
side = base*base
|
||||
|
||||
# pattern for a baseline valid solution
|
||||
def pattern(r,c): return (base*(r%base)+r//base+c)%side
|
||||
|
||||
# randomize rows, columns and numbers (of valid base pattern)
|
||||
from random import sample
|
||||
def shuffle(s): return sample(s,len(s))
|
||||
rBase = range(base)
|
||||
rows = [ g*base + r for g in shuffle(rBase) for r in shuffle(rBase) ]
|
||||
cols = [ g*base + c for g in shuffle(rBase) for c in shuffle(rBase) ]
|
||||
nums = shuffle(range(1,base*base+1))
|
||||
|
||||
# produce board using randomized baseline pattern
|
||||
board = [ [nums[pattern(r,c)] for c in cols] for r in rows ]
|
||||
|
||||
# for line in board: print(line)
|
||||
# solution = board
|
||||
# print(solution)
|
||||
|
||||
|
||||
squares = side*side
|
||||
empties = squares * 3//5
|
||||
# empties = squares * diff
|
||||
# print(squares)
|
||||
# print(empties)
|
||||
# print(squares * 13//20)
|
||||
for p in sample(range(squares),empties):
|
||||
board[p//side][p%side] = 0
|
||||
|
||||
# numSize = len(str(side))
|
||||
# for line in board:
|
||||
# print(*(f"{n or '.':{numSize}} " for n in line))
|
||||
|
||||
|
||||
def expandLine(line):
|
||||
# return line[0]+line[5:9].join([line[1:5]*(base-1)]*base)+line[9:13]
|
||||
# return line[0]+line[3:7].join([line[1:3]*(base-1)]*base)+line[3:5]
|
||||
# return line[0]+line[3:5].join([line[1:3]*(base-1)]*base)+line[5:7]
|
||||
return line[0]+line[4:7].join([line[1:4]*(base-1)]*base)+line[7:10]
|
||||
# line0 = expandLine("╔═══╤═══╦═══╗")
|
||||
# line1 = expandLine("║ . │ . ║ . ║")
|
||||
# line2 = expandLine("╟───┼───╫───╢")
|
||||
# line3 = expandLine("╠═══╪═══╬═══╣")
|
||||
# line4 = expandLine("╚═══╧═══╩═══╝")
|
||||
# line0 = expandLine("╔═╤═╦═╗")
|
||||
# line1 = expandLine("║.│.║.║")
|
||||
# line2 = expandLine("╟─┼─╫─╢")
|
||||
# line3 = expandLine("╠═╪═╬═╣")
|
||||
# line4 = expandLine("╚═╧═╩═╝")
|
||||
# line0 = expandLine("╔═╤═╦═╗")
|
||||
# line1 = expandLine("║.│.║.║")
|
||||
# line2 = expandLine("╟─┼─╫─╢")
|
||||
# line3 = expandLine("╠═╪═╬═╣")
|
||||
# line4 = expandLine("╚═╧═╩═╝")
|
||||
line0 = expandLine("╔══╤══╦══╗")
|
||||
line1 = expandLine("║. │. ║. ║")
|
||||
line2 = expandLine("╟──┼──╫──╢")
|
||||
line3 = expandLine("╠══╪══╬══╣")
|
||||
line4 = expandLine("╚══╧══╩══╝")
|
||||
|
||||
symbol = " 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
nums = [ [""]+[symbol[n] for n in row] for row in board ]
|
||||
output = []
|
||||
# print(line0)
|
||||
output.append(line0)
|
||||
for r in range(1,side+1):
|
||||
output.append("".join(n+s for n,s in zip(nums[r-1],line1.split("."))))
|
||||
output.append([line2,line3,line4][(r%side==0)+(r%base==0)])
|
||||
# print( "".join(n+s for n,s in zip(nums[r-1],line1.split("."))) )
|
||||
# print([line2,line3,line4][(r%side==0)+(r%base==0)])
|
||||
|
||||
# print('=')
|
||||
# print(output)
|
||||
# for x in output:
|
||||
# print(x)
|
||||
# print('-')
|
||||
return output
|
||||
|
||||
|
||||
|
||||
def convert_to_image(sudoku):
|
||||
# sample text and font
|
||||
# unicode_text = u"Unicode Characters: \u00C6 \u00E6 \u00B2 \u00C4 \u00D1 \u220F"
|
||||
unicode_text = sudoku[0]
|
||||
verdana_font = ImageFont.truetype("IBMPlexMono-Medium.ttf", 16, encoding="unic")
|
||||
|
||||
# get the line size
|
||||
text_width, text_height = verdana_font.getsize(unicode_text)
|
||||
|
||||
# create a blank canvas with extra space between lines
|
||||
canvas = Image.new('RGB', (text_width + 10, (text_height*(len(sudoku)-1))), (255, 255, 255))
|
||||
# canvas.convert('L')
|
||||
|
||||
|
||||
# draw the text onto the text canvas, and use black as the text color
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# for x in sudoku:
|
||||
pos = 0
|
||||
for x in sudoku:
|
||||
unicode_text = x
|
||||
draw.text((5,pos), unicode_text, font = verdana_font, fill = "#000000")
|
||||
pos += (text_height-2)
|
||||
|
||||
# save the blank canvas to a file
|
||||
# fn = lambda x : 255 if x > 200 else 0
|
||||
# canvas.convert('L').point(fn, mode='1')
|
||||
canvas.save("sudoku.png", "PNG")
|
||||
# img = Image.new('RGB', (200, 100))
|
||||
# d = ImageDraw.Draw(img)
|
||||
# d.text((30, 20), sudoku[0], fill=(255, 0, 0))
|
||||
# text_width, text_height = d.textsize(sudoku[0])
|
||||
# print(text_width, text_height)
|
||||
|
||||
if __name__ == '__main__':
|
||||
su = generate_sudoku()
|
||||
for x in su:
|
||||
print(x)
|
||||
convert_to_image(su)
|
||||
print(su)
|
||||
# for x in generate_sudoku():
|
||||
# print(x)
|
||||
|
||||
|
||||
# print(board)
|
||||
|
||||
# import random
|
||||
# from itertools import islice
|
||||
# print([*islice(shortSudokuSolve(board),2)][0])
|
||||
# print([*islice(shortSudokuSolve(board),2)][1])
|
||||
# if [*islice(shortSudokuSolve(board),2)][0] == [*islice(shortSudokuSolve(board),2)][1]:
|
||||
# print('ay')
|
||||
# else:
|
||||
# print('er')
|
||||
|
||||
|
||||
# while True:
|
||||
# solved = [*islice(shortSudokuSolve(board),2)]
|
||||
# # print(len(solved))
|
||||
# # print(solved)
|
||||
# if len(solved)==1:
|
||||
# # print('yay!')
|
||||
# break
|
||||
# diffPos = [(r,c) for r in range(9) for c in range(9)
|
||||
# if solved[0][r][c] != solved[1][r][c] ]
|
||||
# r,c = random.choice(diffPos)
|
||||
# board[r][c] = solution[r][c]
|
||||
# # print(board)
|
||||
# print(r,c)
|
||||
# # print(board)
|
||||
|
||||
|
||||
# print(board)
|
||||
# print(solution)
|
||||
# print('ysy')
|
||||
|
||||
# def expandLine(line):
|
||||
# return line[0]+line[5:9].join([line[1:5]*(base-1)]*base)+line[9:13]
|
||||
# line0 = expandLine("╔═══╤═══╦═══╗")
|
||||
# line1 = expandLine("║ . │ . ║ . ║")
|
||||
# line2 = expandLine("╟───┼───╫───╢")
|
||||
# line3 = expandLine("╠═══╪═══╬═══╣")
|
||||
# line4 = expandLine("╚═══╧═══╩═══╝")
|
||||
|
||||
# symbol = " 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
# nums = [ [""]+[symbol[n] for n in row] for row in board ]
|
||||
# output = []
|
||||
# print(line0)
|
||||
# output.append(line0)
|
||||
# for r in range(1,side+1):
|
||||
# output.append("".join(n+s for n,s in zip(nums[r-1],line1.split("."))))
|
||||
# output.append([line2,line3,line4][(r%side==0)+(r%base==0)])
|
||||
# print( "".join(n+s for n,s in zip(nums[r-1],line1.split("."))) )
|
||||
# print([line2,line3,line4][(r%side==0)+(r%base==0)])
|
||||
|
||||
# print('==')
|
||||
# print(output)
|
||||
# for x in output:
|
||||
# print(x)
|
||||
# print('--')
|
||||
|
||||
# return output
|
||||
|
7
application/sync_calendar.py
Normal file
7
application/sync_calendar.py
Normal file
@ -0,0 +1,7 @@
|
||||
#TODO: All Google/iCloud Calendar syncing logic here
|
||||
|
||||
class SyncData():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
#TODO: Obtain calendar data and save to local database, parse into printable content
|
178
application/thermal_print.py
Normal file
178
application/thermal_print.py
Normal file
@ -0,0 +1,178 @@
|
||||
# from PyESCPOS.impl.epson import GenericESCPOS
|
||||
# from PyESCPOS.ifusb import USBConnection
|
||||
|
||||
# conn = USBConnection.create('5011:0416,interface=0,ep_out=3,ep_in=0')
|
||||
# printer = ElginRM22(conn)
|
||||
# printer.init()
|
||||
# printer.text('Hello World!')
|
||||
|
||||
|
||||
# p = Usb(0x5011, 0x0416, printer="simple")
|
||||
# p.text("Hello World\n")
|
||||
|
||||
# p = printer.Usb(0x0416, 0x5011, 4, 0x81, 0x02) #initalize printer on raspberry pi
|
||||
# p.text("hello world") #print this text
|
||||
# p.cut() #move paper up enough to tear off (probably a better way, but this works.)
|
||||
|
||||
# import falcon
|
||||
# from wsgiref import simple_server
|
||||
# import os
|
||||
|
||||
|
||||
# import os
|
||||
# import falcon
|
||||
|
||||
# WORKING_DIRECTORY = os.getcwd()
|
||||
# STATIC = 'static/'
|
||||
|
||||
# def apex(req, resp):
|
||||
# resp.content_type = 'text/html; charset=utf-8'
|
||||
# filename = os.path.abspath(os.path.join(WORKING_DIRECTORY, STATIC, 'index.html'))
|
||||
# with open(filename, 'rt') as f:
|
||||
# resp.body = f.read()
|
||||
from escpos.printer import Usb
|
||||
import usb
|
||||
import wonderwords
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from sudoku_generator import *
|
||||
from wordsearch_generator import WordSearch
|
||||
from database import TodoDatabase
|
||||
|
||||
|
||||
|
||||
class ThermalPrinter():
|
||||
def __init__(self, database):
|
||||
# self.p = printer.Usb(0x0416, 0x5011, 4, 0x81, 0x02) #initalize printer on raspberry pi
|
||||
self.database = database
|
||||
try:
|
||||
self.p = Usb(0x0416, 0x5011, 4, 0x81, 0x02) #initalize printer on raspberry pi
|
||||
# pass
|
||||
except usb.core.NoBackendError as e:
|
||||
print(e)
|
||||
print("Try running `export DYLD_LIBRARY_PATH=/opt/homebrew/lib` if on m1 mac. source: https://github.com/pyusb/pyusb/issues/355#issuecomment-1062798576")
|
||||
raise
|
||||
|
||||
def print_greeting(self):
|
||||
self.p.set(align="center")
|
||||
self.p.set(invert=True)
|
||||
self.p.text(datetime.today().strftime('%Y-%m-%d'))
|
||||
self.p.set(align="left")
|
||||
self.p.set(invert=False)
|
||||
# pass #TODO: add other greetings?
|
||||
|
||||
def print_todos(self, todos=[{"time": "1:00pm to 2:00pm", "text": "Read a book"}]):
|
||||
for x in self._parse_todos(todos):
|
||||
# print(x)
|
||||
self.p.set(align="left")
|
||||
self.p.text(x[0])
|
||||
self.p.set(align="right")
|
||||
self.p.textln(x[1])
|
||||
self.p.set(align="left")
|
||||
|
||||
def print_sudoku(self):
|
||||
self.p.close()
|
||||
self.p = Usb(0x0416, 0x5011, 4, 0x81, 0x02)
|
||||
convert_to_image(generate_sudoku())
|
||||
self.p.image("sudoku.png")
|
||||
|
||||
def print_random_quote(self):
|
||||
q = self.database.get_random_quote()
|
||||
self.p.set(align="left")
|
||||
self.p.text(q[0])
|
||||
self.p.set(align="right")
|
||||
self.p.text(q[1])
|
||||
|
||||
# pass #TODO: parse https://zenquotes.io/api/quotes (api limit is 5 req per 30 seconds)
|
||||
|
||||
def print_wordsearch(self):
|
||||
# words = ("gugu,gaga")
|
||||
words = r.random_words(10, include_parts_of_speech=["nouns", "verbs", "adjectives"])
|
||||
w = WordSearch(words, 32, 32)
|
||||
|
||||
for x in w.grid:
|
||||
self.p.set(align="center")
|
||||
self.p.text(x + "\n")
|
||||
|
||||
# Defaults.NOUNS: Represents a list of nouns
|
||||
# Defaults.VERBS: Represents a list of verbs
|
||||
# Defaults.ADJECTIVES: Represents a list of adjectives
|
||||
|
||||
|
||||
# def printGrid(grid):
|
||||
# for row in grid:
|
||||
# for column in row:
|
||||
# print("%s" % column, end='')
|
||||
# print()
|
||||
# printGrid(w.grid)
|
||||
# w.findWords(words.split(','))
|
||||
# print(w.wordPosition)
|
||||
|
||||
def print_default(self):
|
||||
self.print_greeting()
|
||||
self.print_todos()
|
||||
self.print_random_quote()
|
||||
self.print_sudoku()
|
||||
self.print_wordsearch()
|
||||
self.finished_printing()
|
||||
|
||||
def _parse_todos(self, data):
|
||||
out = []
|
||||
for x in data:
|
||||
out.append(["○ " + x["time"], x["text"]])
|
||||
return out
|
||||
|
||||
#Return: ["□ 1:00pm to 2:00pm", "Read a book"]
|
||||
|
||||
def finished_printing(self):
|
||||
self.p.cut() #move paper up enough to tear off (probably a better way, but this works.)
|
||||
|
||||
|
||||
# class SyncData():
|
||||
# def __init__(self):
|
||||
# pass
|
||||
|
||||
# #TODO: Obtain calendar data and save to local database, parse into printable content
|
||||
|
||||
|
||||
# class GetTodos:
|
||||
# def on_post(self, req, resp):
|
||||
# pass #Handle web interface requesting current todos
|
||||
|
||||
# class SaveTodos:
|
||||
# def on_post(self, req, resp):
|
||||
# pass #Handle web interface sending updated todo list
|
||||
|
||||
# class PrintTodos:
|
||||
# def on_post(self, req, resp):
|
||||
# pass #Handle web interface requesting a print action
|
||||
|
||||
|
||||
def main():
|
||||
data = TodoDatabase()
|
||||
|
||||
tp = ThermalPrinter(data)
|
||||
# tp.print_todos()
|
||||
tp.print_default()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# def main():
|
||||
# app = falcon.App()
|
||||
|
||||
# # app.add_route('/stories', things)
|
||||
# app.add_sink(apex, prefix='^/$')
|
||||
# app.add_static_route('/', os.path.abspath(os.path.join(WORKING_DIRECTORY, STATIC)))
|
||||
|
||||
# # print(os.path.abspath(''))
|
||||
# # app.add_static_route('/', os.path.abspath(''))
|
||||
|
||||
# httpd = simple_server.make_server('127.0.0.1', 8000, app)
|
||||
# httpd.serve_forever()
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# main()
|
BIN
application/unicode-text.png
Normal file
BIN
application/unicode-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
78
application/utils.py
Normal file
78
application/utils.py
Normal file
@ -0,0 +1,78 @@
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import SecurityScopes, HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from application.config import get_settings
|
||||
|
||||
|
||||
class UnauthorizedException(HTTPException):
|
||||
def __init__(self, detail: str, **kwargs):
|
||||
"""Returns HTTP 403"""
|
||||
super().__init__(status.HTTP_403_FORBIDDEN, detail=detail)
|
||||
|
||||
|
||||
class UnauthenticatedException(HTTPException):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Requires authentication"
|
||||
)
|
||||
|
||||
|
||||
class VerifyToken:
|
||||
"""Does all the token verification using PyJWT"""
|
||||
|
||||
def __init__(self):
|
||||
self.config = get_settings()
|
||||
|
||||
# This gets the JWKS from a given URL and does processing so you can
|
||||
# use any of the keys available
|
||||
jwks_url = f'https://{self.config.auth0_domain}/.well-known/jwks.json'
|
||||
self.jwks_client = jwt.PyJWKClient(jwks_url)
|
||||
|
||||
async def verify(self,
|
||||
security_scopes: SecurityScopes,
|
||||
token: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer())
|
||||
):
|
||||
if token is None:
|
||||
raise UnauthenticatedException
|
||||
|
||||
# This gets the 'kid' from the passed token
|
||||
try:
|
||||
signing_key = self.jwks_client.get_signing_key_from_jwt(
|
||||
token.credentials
|
||||
).key
|
||||
except jwt.exceptions.PyJWKClientError as error:
|
||||
raise UnauthorizedException(str(error))
|
||||
except jwt.exceptions.DecodeError as error:
|
||||
raise UnauthorizedException(str(error))
|
||||
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token.credentials,
|
||||
signing_key,
|
||||
algorithms=self.config.auth0_algorithms,
|
||||
audience=self.config.auth0_api_audience,
|
||||
issuer=self.config.auth0_issuer,
|
||||
)
|
||||
except Exception as error:
|
||||
raise UnauthorizedException(str(error))
|
||||
|
||||
if len(security_scopes.scopes) > 0:
|
||||
self._check_claims(payload, 'scope', security_scopes.scopes)
|
||||
|
||||
return payload
|
||||
|
||||
def _check_claims(self, payload, claim_name, expected_value):
|
||||
if claim_name not in payload:
|
||||
raise UnauthorizedException(detail=f'No claim "{claim_name}" found in token')
|
||||
|
||||
payload_claim = payload[claim_name]
|
||||
|
||||
if claim_name == 'scope':
|
||||
payload_claim = payload[claim_name].split(' ')
|
||||
|
||||
for value in expected_value:
|
||||
if value not in payload_claim:
|
||||
raise UnauthorizedException(detail=f'Missing "{claim_name}" scope')
|
213
application/wordsearch_generator.py
Normal file
213
application/wordsearch_generator.py
Normal file
@ -0,0 +1,213 @@
|
||||
#https://github.com/mast3rsoft/WordSearch/blob/master/WordSearch.py
|
||||
import random
|
||||
|
||||
class WordSearch():
|
||||
HORIZONTAL = 0
|
||||
VERTICAL = 1
|
||||
DIAGONAL = 2
|
||||
REVHORIZONTAL = 3
|
||||
REVVERTICAL = 4
|
||||
REVDIAGONAL = 5
|
||||
REVFLIPDIAGONA = 6
|
||||
FLIPDIAGONAL = 7
|
||||
DONTCARE = -100
|
||||
wordPosition = {}
|
||||
def __init__(self, searchWords, maxX = 20, maxY = 20):
|
||||
self.maxX = maxX
|
||||
self.maxY = maxY
|
||||
self.grid = [] # grid is a list of list of strings (characters)
|
||||
testWords = ['superhero', 'gugu','gaga','blah','vodka']
|
||||
searchWords = searchWords.split(",")
|
||||
if searchWords == ['']:
|
||||
searchWords = testWords
|
||||
self.searchWords = searchWords
|
||||
for row in range(0, self.maxY):
|
||||
self.grid.append([])
|
||||
for column in range(0, self.maxX):
|
||||
self.grid[row].append('*')
|
||||
for word in searchWords:
|
||||
DIR = random.randint(0, 7)
|
||||
while not self.engrave(word, self.DONTCARE , self.DONTCARE , DIR):
|
||||
pass
|
||||
self.obfusticate()
|
||||
def engrave(self, word, x, y, direction):
|
||||
if len(word) == 0:
|
||||
return True
|
||||
# word has length > 0
|
||||
# check if we need to choose random pos
|
||||
if x == self.DONTCARE or y == self.DONTCARE: # cannot have one random, one fixed
|
||||
while True:
|
||||
y = random.randint(0, self.maxY - 1)
|
||||
x = random.randint(0, self.maxX - 1)
|
||||
if self.grid[y][x] == '*':
|
||||
break
|
||||
# check if x & y are valid
|
||||
if x == self.maxX or x < 0:
|
||||
return False
|
||||
if y == self.maxY or y < 0:
|
||||
return False
|
||||
if not (self.grid[y][x] == "*" or self.grid[y][x] == word[0]):
|
||||
return False
|
||||
undovalue = self.grid[y][x]
|
||||
undox = x
|
||||
undoy = y
|
||||
self.grid[y][x] = word[0]
|
||||
# now need to write rest of the word
|
||||
if direction == self.HORIZONTAL:
|
||||
x += 1
|
||||
elif direction == self.VERTICAL:
|
||||
y += 1
|
||||
elif direction == self.DIAGONAL:
|
||||
y += 1
|
||||
x += 1
|
||||
elif direction == self.REVHORIZONTAL:
|
||||
x -= 1
|
||||
elif direction == self.REVVERTICAL:
|
||||
y -= 1
|
||||
elif direction == self.REVDIAGONAL:
|
||||
y -= 1
|
||||
x -= 1
|
||||
elif direction == self.FLIPDIAGONAL:
|
||||
x += 1
|
||||
y -= 1
|
||||
elif direction == self.REVFLIPDIAGONA:
|
||||
x -= 1
|
||||
y += 1
|
||||
else:
|
||||
print("This direction not implemented yet")
|
||||
if self.engrave(word[1:], x, y, direction):
|
||||
# we could do the rest, we are happy and done
|
||||
return True
|
||||
else:
|
||||
# grrh: something didn’t work, we need to undo now
|
||||
y = undoy
|
||||
x = undox
|
||||
self.grid[y][x] = undovalue
|
||||
return False
|
||||
def obfusticate(self):
|
||||
for row in self.grid:
|
||||
for i in range(len(row)):
|
||||
if row[i] == '*':
|
||||
row[i] = 'abcdefghijklmnopqrstuvwxyz0123456789'[random.randint(0,25)]
|
||||
def letter(self,x,y):
|
||||
return self.grid[x][y]
|
||||
def findWords(self, words):
|
||||
for word in words:
|
||||
firstLetter = word[0]
|
||||
positions = None
|
||||
y = 0; found = False
|
||||
while y < self.maxY and not found:
|
||||
x = 0
|
||||
while x < self.maxX and not found:
|
||||
if firstLetter == self.grid[y][x]:
|
||||
positions = self.wordIsHere(word, x, y)
|
||||
if positions:
|
||||
found = True
|
||||
break
|
||||
x += 1
|
||||
if not found:
|
||||
y += 1
|
||||
if found:
|
||||
self.wordPosition[word] = positions
|
||||
def wordIsHere(self,word, firstX, firstY):
|
||||
maxX = self.maxX
|
||||
maxY = self.maxY
|
||||
# horizontal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if x == maxX or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x += 1
|
||||
if found:
|
||||
return positions
|
||||
# vertical
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == maxY or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
y += 1
|
||||
if found:
|
||||
return positions
|
||||
# reverse horizontal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if x == -1 or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x -= 1
|
||||
if found:
|
||||
return positions
|
||||
# reverse vertical
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == -1 or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
y -= 1
|
||||
if found:
|
||||
return positions
|
||||
# diagonal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == maxY or x == maxX or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x += 1
|
||||
y += 1
|
||||
if found:
|
||||
return positions
|
||||
# reverse diagonal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == -1 or x == -1 or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x -= 1
|
||||
y -= 1
|
||||
if found:
|
||||
return positions
|
||||
# flip diagonal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == -1 or x == maxX or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x += 1
|
||||
y -= 1
|
||||
if found:
|
||||
return positions
|
||||
# reverse flip diagonal
|
||||
found = True; x = firstX; y = firstY; positions = []
|
||||
for letter in word:
|
||||
if y == maxY or x == -1 or letter != self.grid[y][x]:
|
||||
found = False
|
||||
break
|
||||
positions.append((y, x))
|
||||
x -= 1
|
||||
y += 1
|
||||
if found:
|
||||
return positions
|
||||
#
|
||||
return None
|
||||
|
||||
# Test Progamm for WordSearch Genertator
|
||||
if __name__ == '__main__':
|
||||
words = ("gugu,gaga")
|
||||
w = WordSearch(words, 10, 5)
|
||||
def printGrid(grid):
|
||||
for row in grid:
|
||||
for column in row:
|
||||
print("%s" % column, end='')
|
||||
print()
|
||||
printGrid(w.grid)
|
||||
w.findWords(words.split(','))
|
||||
print(w.wordPosition)
|
1
data.json
Normal file
1
data.json
Normal file
@ -0,0 +1 @@
|
||||
{"2023-02-23": [{"time": "1:00pm to 2:00pm", "task": "Read a book."}]}
|
20
requirements.txt
Normal file
20
requirements.txt
Normal file
@ -0,0 +1,20 @@
|
||||
falcon
|
||||
python-escpos
|
||||
annotated-types==0.5.0
|
||||
anyio==3.7.1
|
||||
asgiref==3.7.2
|
||||
cffi==1.15.1
|
||||
click==8.1.6
|
||||
cryptography==40.0.2
|
||||
fastapi==0.100.1
|
||||
h11==0.14.0
|
||||
idna==3.4
|
||||
pycparser==2.21
|
||||
pydantic==2.1.1
|
||||
pydantic-settings==2.0.2
|
||||
pydantic_core==2.4.0
|
||||
PyJWT==2.8.0
|
||||
python-dotenv==1.0.0
|
||||
sniffio==1.3.0
|
||||
typing_extensions==4.7.1
|
||||
uvicorn==0.23.2
|
Loading…
Reference in New Issue
Block a user