Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: user defined colors #623

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 1 addition & 44 deletions tagstudio/src/core/library/alchemy/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,53 +41,10 @@ def make_tables(engine: Engine) -> None:
conn.execute(text("INSERT INTO tags (id, name, color) VALUES (999, 'temp', 1)"))
conn.execute(text("DELETE FROM tags WHERE id = 999"))

conn.execute(
text(
"""INSERT INTO user_defined_colors (color, name, user_defined) VALUES
('#1e1e1e', 'DEFAULT', FALSE),
('#111018', 'BLACK', FALSE),
('#24232a', 'DARK_GRAY', FALSE),
('#53525a', 'GRAY', FALSE),
('#aaa9b0', 'LIGHT_GRAY', FALSE),
('#f2f1f8', 'WHITE', FALSE),
('#ff99c4', 'LIGHT_PINK', FALSE),
('#ff99c4', 'PINK', FALSE),
('#f6466f', 'MAGENTA', FALSE),
('#e22c3c', 'RED', FALSE),
('#e83726', 'RED_ORANGE', FALSE),
('#f65848', 'SALMON', FALSE),
('#ed6022', 'ORANGE', FALSE),
('#fa9a2c', 'YELLOW_ORANGE', FALSE),
('#ffd63d', 'YELLOW', FALSE),
('#4aed90', 'MINT', FALSE),
('#92e649', 'LIME', FALSE),
('#85ec76', 'LIGHT_GREEN', FALSE),
('#28bb48', 'GREEN', FALSE),
('#1ad9b2', 'TEAL', FALSE),
('#49e4d5', 'CYAN', FALSE),
('#55bbf6', 'LIGHT_BLUE', FALSE),
('#3b87f0', 'BLUE', FALSE),
('#5948f2', 'BLUE_VIOLET', FALSE),
('#874ff5', 'VIOLET', FALSE),
('#bb4ff0', 'PURPLE', FALSE),
('#f1c69c', 'PEACH', FALSE),
('#823216', 'BROWN', FALSE),
('#ad8eef', 'LAVENDER', FALSE),
('#efc664', 'BLONDE', FALSE),
('#a13220', 'AUBURN', FALSE),
('#be5b2d', 'LIGHT_BROWN', FALSE),
('#4c2315', 'DARK_BROWN', FALSE),
('#515768', 'COOL_GRAY', FALSE),
('#625550', 'WARM_GRAY', FALSE),
('#4c652e', 'OLIVE', FALSE),
('#9f2aa7', 'BERRY', FALSE)"""
)
)

conn.execute(
text(
"""
CREATE TRIGGER delete_color BEFORE DELETE ON user_defined_colors
CREATE TRIGGER IF NOT EXISTS delete_color BEFORE DELETE ON colors
WHEN OLD.user_defined = FALSE
BEGIN
SELECT RAISE(ABORT, 'Cannot delete program-defined colors');
Expand Down
20 changes: 14 additions & 6 deletions tagstudio/src/core/library/alchemy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pathlib import Path


# TODO: REMOVE
class TagColor(enum.IntEnum):
DEFAULT = 1
BLACK = 2
Expand Down Expand Up @@ -42,12 +43,19 @@ class TagColor(enum.IntEnum):
COOL_GRAY = 36
OLIVE = 37

@staticmethod
def get_color_from_str(color_name: str) -> "TagColor":
for color in TagColor:
if color.name == color_name.upper().replace(" ", "_"):
return color
return TagColor.DEFAULT
#class TSStandardColors(enum.IntEnum):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you said you aren't a fan of enums, but I think it would be good to have an enum that just has the IDs of colors in the default tagstudio color pack (whatever we will call it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll concede to this ;)

# RED=1,
# YELLOW=2,
# CYAN=3,
# BLUE=4
# #etc..

# @staticmethod
# def get_color_from_str(color_name: str) -> "TagColor":
# for color in TagColor:
# if color.name == color_name.upper().replace(" ", "_"):
# return color
# return TagColor.DEFAULT


class SearchMode(enum.IntEnum):
Expand Down
69 changes: 68 additions & 1 deletion tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
_FieldID,
)
from .joins import TagField, TagSubtag
from .models import Entry, Folder, Preferences, Tag, TagAlias, ValueType
from .models import Entry, Folder, Preferences, Tag, TagAlias, ValueType, Color, ColorNamespace

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -91,6 +91,51 @@ def get_default_tags() -> tuple[Tag, ...]:
return archive_tag, favorite_tag


def get_default_colors(namespace: ColorNamespace) -> list[Color]:
colors: list[Color] = [
#TODO: reduce this to limited tagstudio standard color set
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if you have settled on a final list of colors you do want in the default pack, but the sooner I can have that the better

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I'll get that fleshed out for you

Color(id=1, hex_value="#1e1e1e", name="Default", user_defined=False, namespace_id=namespace.id),
Color(id=2, hex_value="#111018", name="BLACK", user_defined=False, namespace_id=namespace.id),
Color(id=3, hex_value="#24232a", name="DARK_GRAY", user_defined=False, namespace_id=namespace.id),
Color(id=4, hex_value="#53525a", name="GRAY", user_defined=False, namespace_id=namespace.id),
Color(id=5, hex_value="#aaa9b0", name="LIGHT_GRAY", user_defined=False, namespace_id=namespace.id),
Color(id=6, hex_value="#f2f1f8", name="WHITE", user_defined=False, namespace_id=namespace.id),
Color(id=7, hex_value="#ff99c4", name="LIGHT_PINK", user_defined=False, namespace_id=namespace.id),
Color(id=8, hex_value="#ff99c4", name="PINK", user_defined=False, namespace_id=namespace.id),
Color(id=9, hex_value="#f6466f", name="MAGENTA", user_defined=False, namespace_id=namespace.id),
Color(id=10, hex_value="#e22c3c", name="RED", user_defined=False, namespace_id=namespace.id),
Color(id=11, hex_value="#e83726", name="RED_ORANGE", user_defined=False, namespace_id=namespace.id),
Color(id=12, hex_value="#f65848", name="SALMON", user_defined=False, namespace_id=namespace.id),
Color(id=13, hex_value="#ed6022", name="ORANGE", user_defined=False, namespace_id=namespace.id),
Color(id=14, hex_value="#fa9a2c", name="YELLOW_ORANGE", user_defined=False, namespace_id=namespace.id),
Color(id=15, hex_value="#ffd63d", name="YELLOW", user_defined=False, namespace_id=namespace.id),
Color(id=16, hex_value="#4aed90", name="MINT", user_defined=False, namespace_id=namespace.id),
Color(id=17, hex_value="#92e649", name="LIME", user_defined=False, namespace_id=namespace.id),
Color(id=18, hex_value="#85ec76", name="LIGHT_GREEN", user_defined=False, namespace_id=namespace.id),
Color(id=19, hex_value="#28bb48", name="GREEN", user_defined=False, namespace_id=namespace.id),
Color(id=20, hex_value="#1ad9b2", name="TEAL", user_defined=False, namespace_id=namespace.id),
Color(id=21, hex_value="#49e4d5", name="CYAN", user_defined=False, namespace_id=namespace.id),
Color(id=22, hex_value="#55bbf6", name="LIGHT_BLUE", user_defined=False, namespace_id=namespace.id),
Color(id=23, hex_value="#3b87f0", name="BLUE", user_defined=False, namespace_id=namespace.id),
Color(id=24, hex_value="#5948f2", name="BLUE_VIOLET", user_defined=False, namespace_id=namespace.id),
Color(id=25, hex_value="#874ff5", name="VIOLET", user_defined=False, namespace_id=namespace.id),
Color(id=26, hex_value="#bb4ff0", name="PURPLE", user_defined=False, namespace_id=namespace.id),
Color(id=27, hex_value="#f1c69c", name="PEACH", user_defined=False, namespace_id=namespace.id),
Color(id=28, hex_value="#823216", name="BROWN", user_defined=False, namespace_id=namespace.id),
Color(id=29, hex_value="#ad8eef", name="LAVENDER", user_defined=False, namespace_id=namespace.id),
Color(id=31, hex_value="#efc664", name="BLONDE", user_defined=False, namespace_id=namespace.id),
Color(id=32, hex_value="#a13220", name="AUBURN", user_defined=False, namespace_id=namespace.id),
Color(id=33, hex_value="#be5b2d", name="LIGHT_BROWN", user_defined=False, namespace_id=namespace.id),
Color(id=34, hex_value="#4c2315", name="DARK_BROWN", user_defined=False, namespace_id=namespace.id),
Color(id=35, hex_value="#515768", name="COOL_GRAY", user_defined=False, namespace_id=namespace.id),
Color(id=36, hex_value="#625550", name="WARM_GRAY", user_defined=False, namespace_id=namespace.id),
Color(id=37, hex_value="#4c652e", name="OLIVE", user_defined=False, namespace_id=namespace.id),
Color(id=38, hex_value="#9f2aa7", name="BERRY", user_defined=False, namespace_id=namespace.id),
]

return colors


@dataclass(frozen=True)
class SearchResult:
"""Wrapper for search results.
Expand Down Expand Up @@ -256,13 +301,35 @@ def open_sqlite_library(

if add_default_data:
tags = get_default_tags()

try:
session.add_all(tags)
session.commit()
except IntegrityError:
# default tags may exist already
session.rollback()

#the default namespace shouldnt exist yet but we will check anyway
ts_std_namespace_name = "tagstudio_std" #TODO: move this elsewhere maybe
statement = select(ColorNamespace).where(ColorNamespace.name == ts_std_namespace_name)
ts_std_namespace = session.scalar(statement)

if ts_std_namespace is None:
ts_std_namespace = ColorNamespace(name=ts_std_namespace_name)
session.add(ts_std_namespace)
session.commit()

try:
colors = get_default_colors(ts_std_namespace)
session.add_all(
colors
)
session.commit()
except IntegrityError:
#colors already exist
session.rollback()


# dont check db version when creating new library
if not is_new:
db_version = session.scalar(
Expand Down
20 changes: 14 additions & 6 deletions tagstudio/src/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from sqlalchemy import JSON, ForeignKey, Integer, event
from sqlalchemy import JSON, ForeignKey, Integer, event, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship

from ...constants import TAG_ARCHIVED, TAG_FAVORITE
Expand Down Expand Up @@ -211,15 +211,23 @@ def remove_tag(self, tag: Tag, field: TagBoxField | None = None) -> None:
for tag_box_field in self.tag_box_fields:
tag_box_field.tags.remove(tag)

#TODO: maybe make this a general namespace to be used for colors, tags, etc?
class ColorNamespace(Base):
__tablename__ = "color_namespaces"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
colors: Mapped[list["Color"]] = relationship()

class Color(Base):
__tablename__ = "colors"

class UserDefinedColor(Base):
__tablename__ = "user_defined_colors"
id: Mapped[int] = mapped_column(primary=True)
color: Mapped[str] = mapped_column(unique=True)
id: Mapped[int] = mapped_column(primary_key=True)
namespace_id: Mapped[int] = mapped_column(ForeignKey("color_namespaces.id"), primary_key = True)

hex_value: Mapped[str] = mapped_column()
name: Mapped[str] = mapped_column()
user_defined: Mapped[bool] = mapped_column(default=True)


class ValueType(Base):
"""Define Field Types in the Library.

Expand Down
Loading
Loading