diff --git a/.gitignore b/.gitignore index 3f804b3..4dc56bd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ CodeGarden.egg-info/ htmlcov/ *.db garden-repos/ +todos.json diff --git a/LICENSE.md b/LICENSE.md index 172986d..6c6221f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 C.N. Joseph +Copyright (c) 2024 C.N. Joseph Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/code_garden/config.py b/code_garden/config.py index a20f259..2e0847c 100644 --- a/code_garden/config.py +++ b/code_garden/config.py @@ -13,10 +13,7 @@ def get(): - return { - "HOME_DIR": str(HOME_DIR), - "PORT": PORT, - } + return dotenv.dotenv_values() def set(key, value): diff --git a/code_garden/database.py b/code_garden/database.py new file mode 100644 index 0000000..7ed5966 --- /dev/null +++ b/code_garden/database.py @@ -0,0 +1,23 @@ +import sqlite3 + +from code_garden import config + + +class Connector: + def __init__(self): + self.db = sqlite3.connect(config.HOME_DIR / "todos.db", check_same_thread=False) + + def write(self, stmt: str, params=()): + with self.db as db: + cursor = db.cursor() + cursor.execute(stmt, params) + + def read(self, stmt: str, params=()): + results = None + with self.db as db: + cursor = db.cursor() + cursor.execute(stmt, params) + + results = cursor.fetchall() + + return results diff --git a/code_garden/models.py b/code_garden/models.py index a850848..13a6fd1 100644 --- a/code_garden/models.py +++ b/code_garden/models.py @@ -154,7 +154,7 @@ def init(self, brief_descrip: str): Args: brief_descrip (str): Short description of what the Repository contains. """ - files = ["LICENSE.md", ".gitignore"] + files = ["LICENSE.md", ".gitignore", "todos.json"] self.path.mkdir() Readme(self.name, brief_descrip).write(self.path) for i in files: @@ -211,6 +211,25 @@ def export(self): with open(config.HOME_DIR / f"{self.name}.json", "w") as f: json.dump(self.to_dict(), f, indent=4) + def export_todos(self): + """Export all todos to single JSON file.""" + results = {"todos": [i.to_dict() for i in self.todos]} + json.dump(results, open(self.path / "todos.json", "w"), indent=4) + + def import_todos(self): + """Import todos from a JSON file.""" + results = json.load(open(self.path / "todos.json")).get("todos") + for i in results: + todo_ = Todo( + i.get("name"), + i.get("description"), + i.get("tag"), + datetime.datetime.now(), + i.get("status"), + self.name, + ) + todo_.add() + def to_dict(self): """Get a dict representation of the Repository object (for API usage).""" return dict( @@ -298,6 +317,16 @@ def __init__(self, repository, name, timestamp, abbrev_hash): self.timestamp = timestamp self.abbrev_hash = abbrev_hash + @classmethod + def get(cls, repository, abbrev_hash): + """Get more details about a specified commit.""" + info = ( + Repository(repository) + .run_command(["git", "show", "--stat", abbrev_hash]) + .splitlines()[4:] + ) + return "\n".join(info) + def to_dict(self): """Get a dict representation of this object (for API use).""" return dict( @@ -327,8 +356,14 @@ def path(self): @property def color(self): - choices = {"M": "orange", "A": "green", "D": "red", "R": "yellow", "?": "green"} - return choices.get(self.type_) + choices = { + "M": "#bf5408", + "A": "green", + "D": "red", + "R": "yellow", + "?": "green", + } + return choices.get(self.type_) or "green" def reset(self): """Reset this file to its original state in the most recent commit.""" diff --git a/code_garden/todos.py b/code_garden/todos.py index 2d630c6..f0f15d3 100644 --- a/code_garden/todos.py +++ b/code_garden/todos.py @@ -5,11 +5,10 @@ import click -from code_garden import config +from code_garden.database import Connector -db = sqlite3.connect(config.HOME_DIR / "todos.db", check_same_thread=False) -cursor = db.cursor() -cursor.execute( +conn = Connector() +conn.write( "CREATE TABLE IF NOT EXISTS todos (title TEXT, description TEXT, tag TEXT, date_added DATETIME, status TEXT, repo TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT)" ) @@ -36,7 +35,7 @@ def __init__( self.id = id def add(self): - cursor.execute( + conn.write( "INSERT INTO todos (title, description, tag, date_added, status, repo) VALUES (?,?,?,?,?,?)", ( self.title, @@ -47,41 +46,36 @@ def add(self): self.repo, ), ) - db.commit() @classmethod def get(cls, id): - cursor.execute( + result = conn.read( "SELECT title, description, tag, date_added, status, repo, id FROM todos WHERE id=?", (str(id),), - ) - result = cursor.fetchone() + )[0] return Todo( result[0], result[1], result[2], result[3], result[4], result[5], result[6] ) @classmethod def see_list(cls, repo): - cursor.execute( + results = conn.read( "SELECT title, description, tag, date_added, status, repo, id FROM todos WHERE repo=?", (repo,), ) - results = cursor.fetchall() return sorted( [Todo(i[0], i[1], i[2], i[3], i[4], i[5], i[6]) for i in results], key=lambda x: (x.status == "completed", x.status != "active", x.id), ) def edit(self): - cursor.execute( + conn.write( "UPDATE todos SET title=?, description=?, tag=?, status=? WHERE id=?", (self.title, self.description, self.tag, self.status, str(self.id)), ) - db.commit() def delete(self): - cursor.execute("DELETE FROM todos WHERE id=?", (str(self.id),)) - db.commit() + conn.write("DELETE FROM todos WHERE id=?", (str(self.id),)) def __str__(self): return "#{} {:40.40} ({})".format( diff --git a/code_garden/web/routes.py b/code_garden/web/routes.py index 3412bd0..c8cc74e 100644 --- a/code_garden/web/routes.py +++ b/code_garden/web/routes.py @@ -5,7 +5,7 @@ from code_garden.todos import Todo from .. import config -from ..models import Branch, DiffItem, IgnoreItem, Repository +from ..models import Branch, DiffItem, IgnoreItem, LogItem, Repository @current_app.get("/") @@ -15,10 +15,7 @@ def index(): @current_app.post("/settings") def settings(): - config_ = config.get() - config_.update({"debug": current_app.config.get("ENV") == "development"}) - - return config_ + return config.get() @current_app.post("/repositories") @@ -34,6 +31,15 @@ def commit(): return {"status": "done"} +@current_app.post("/get_commit") +def get_commit(): + return { + "details": LogItem.get( + request.json.get("name"), request.json.get("abbrev_hash") + ) + } + + @current_app.post("/create_repository") def create_repository(): repository_ = Repository(request.json.get("name")) @@ -171,6 +177,20 @@ def toggle_todo(): return {"status": "done"} +@current_app.post("/export_todos") +def export_todos(): + Repository(request.json.get("name")).export_todos() + + return {"status": "done"} + + +@current_app.post("/import_todos") +def import_todos(): + Repository(request.json.get("name")).import_todos() + + return {"status": "done"} + + @current_app.post("/commit_todo") def commit_todo(): todo_ = Todo.get(request.json.get("id")) diff --git a/code_garden/web/static/App.css b/code_garden/web/static/App.css index dcbeed0..9ce3b8f 100644 --- a/code_garden/web/static/App.css +++ b/code_garden/web/static/App.css @@ -81,7 +81,7 @@ body { .dropdown-menu { background-color: var(--primary-bg); color: var(--primary-txt); - border: 1px solid var(--primary-txt); + border: 1px solid var(--btn-color); } .dropdown-item { @@ -90,6 +90,11 @@ body { letter-spacing: 1px; } +.dropdown-item:hover { + background-color: var(--btn-color); + color: var(--btn-hover); +} + a, a:hover { color: inherit; @@ -173,3 +178,8 @@ a:hover { .desc:focus { outline: none !important; } + +.diff-item:hover, +.log-item:hover { + border-bottom: 1px dotted; +} diff --git a/code_garden/web/static/App.jsx b/code_garden/web/static/App.jsx index 7c146ac..47bf975 100644 --- a/code_garden/web/static/App.jsx +++ b/code_garden/web/static/App.jsx @@ -30,6 +30,7 @@ const ReposContext = React.createContext(); const CurrentRepoContext = React.createContext(); const LoadingContext = React.createContext(); const TabContext = React.createContext(); +const PageContext = React.createContext(); const tags = [ "misc", @@ -92,6 +93,78 @@ function BranchForm() { ); } +function CreateRepoForm() { + const [, setLoading] = React.useContext(LoadingContext); + const [, , getRepo] = React.useContext(CurrentRepoContext); + const [, , getRepos] = React.useContext(ReposContext); + const [name, setName] = React.useState(""); + const [description, setDescription] = React.useState(""); + const [page, setPage] = React.useContext(PageContext); + + const createRepo = (e) => { + e.preventDefault(); + setLoading(true); + apiCall( + "/create_repository", + { + name: name, + brief_descrip: description, + }, + (data) => { + getRepo(data.name); + setLoading(false); + setName(""); + setDescription(""); + setPage("repos"); + getRepos(); + } + ); + }; + + const onChangeName = (e) => { + setName(e.target.value); + }; + + const onChangeDescription = (e) => { + setDescription(e.target.value); + }; + + const generateName = () => { + apiCall("/generate_name", {}, (data) => { + setName(data.name); + setDescription(`Created on ${new Date().toDateString()}`); + }); + }; + + return ( +
+ ); +} + function BranchItem({ item }) { const [currentRepo, setCurrentRepo, getRepo] = React.useContext(CurrentRepoContext); @@ -148,22 +221,34 @@ function BranchItem({ item }) {