diff --git a/st/sei_time/doctype/__init__.py b/st/sei_time/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/character/__init__.py b/st/sei_time/doctype/character/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/character/character.js b/st/sei_time/doctype/character/character.js new file mode 100644 index 0000000..a06012e --- /dev/null +++ b/st/sei_time/doctype/character/character.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Vassili and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Character", { +// refresh(frm) { + +// }, +// }); diff --git a/st/sei_time/doctype/character/character.json b/st/sei_time/doctype/character/character.json new file mode 100644 index 0000000..0acd6e7 --- /dev/null +++ b/st/sei_time/doctype/character/character.json @@ -0,0 +1,62 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:character_name", + "creation": "2026-03-24 18:50:46.373842", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "character_name", + "type", + "profile_picture" + ], + "fields": [ + { + "fieldname": "character_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "type", + "fieldtype": "Link", + "label": "Type", + "options": "Character Type" + }, + { + "fieldname": "profile_picture", + "fieldtype": "Attach Image", + "label": "Profile Picture" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-24 19:37:53.659927", + "modified_by": "Administrator", + "module": "Sei Time", + "name": "Character", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/st/sei_time/doctype/character/character.py b/st/sei_time/doctype/character/character.py new file mode 100644 index 0000000..b2bd970 --- /dev/null +++ b/st/sei_time/doctype/character/character.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class Character(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + character_name: DF.Data + profile_picture: DF.AttachImage | None + type: DF.Link | None + # end: auto-generated types + + pass diff --git a/st/sei_time/doctype/character/test_character.py b/st/sei_time/doctype/character/test_character.py new file mode 100644 index 0000000..5678499 --- /dev/null +++ b/st/sei_time/doctype/character/test_character.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestCharacter(IntegrationTestCase): + """ + Integration tests for Character. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/st/sei_time/doctype/character_type/__init__.py b/st/sei_time/doctype/character_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/character_type/character_type.js b/st/sei_time/doctype/character_type/character_type.js new file mode 100644 index 0000000..5aecff0 --- /dev/null +++ b/st/sei_time/doctype/character_type/character_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Vassili and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Character Type", { +// refresh(frm) { + +// }, +// }); diff --git a/st/sei_time/doctype/character_type/character_type.json b/st/sei_time/doctype/character_type/character_type.json new file mode 100644 index 0000000..13412ce --- /dev/null +++ b/st/sei_time/doctype/character_type/character_type.json @@ -0,0 +1,61 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:type", + "creation": "2026-03-24 18:52:40.900099", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "type", + "frame_image", + "background_image" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Type", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "frame_image", + "fieldtype": "Attach Image", + "label": "Frame Image" + }, + { + "fieldname": "background_image", + "fieldtype": "Attach Image", + "label": "Background Image" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-24 18:56:08.479742", + "modified_by": "Administrator", + "module": "Sei Time", + "name": "Character Type", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/st/sei_time/doctype/character_type/character_type.py b/st/sei_time/doctype/character_type/character_type.py new file mode 100644 index 0000000..31e3e3c --- /dev/null +++ b/st/sei_time/doctype/character_type/character_type.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CharacterType(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + background_image: DF.AttachImage | None + frame_image: DF.AttachImage | None + type: DF.Data + # end: auto-generated types + + pass diff --git a/st/sei_time/doctype/character_type/test_character_type.py b/st/sei_time/doctype/character_type/test_character_type.py new file mode 100644 index 0000000..434c919 --- /dev/null +++ b/st/sei_time/doctype/character_type/test_character_type.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestCharacterType(IntegrationTestCase): + """ + Integration tests for CharacterType. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/st/sei_time/doctype/game/__init__.py b/st/sei_time/doctype/game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/game/game.js b/st/sei_time/doctype/game/game.js new file mode 100644 index 0000000..d87b9f8 --- /dev/null +++ b/st/sei_time/doctype/game/game.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Vassili and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Game", { +// refresh(frm) { + +// }, +// }); diff --git a/st/sei_time/doctype/game/game.json b/st/sei_time/doctype/game/game.json new file mode 100644 index 0000000..2987d2d --- /dev/null +++ b/st/sei_time/doctype/game/game.json @@ -0,0 +1,92 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:title", + "creation": "2026-02-25 20:31:10.918253", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "title", + "description", + "section_break_nsqq", + "next_session", + "max_seats", + "type", + "select_vniu" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "section_break_nsqq", + "fieldtype": "Section Break" + }, + { + "fieldname": "next_session", + "fieldtype": "Datetime", + "label": "Next Session" + }, + { + "fieldname": "max_seats", + "fieldtype": "Int", + "label": "Max Seats" + }, + { + "fieldname": "type", + "fieldtype": "Link", + "label": "Type", + "options": "Game Type" + }, + { + "fieldname": "select_vniu", + "fieldtype": "Select", + "options": "January\nFebruary\nMarch" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-24 19:02:39.174748", + "modified_by": "Administrator", + "module": "Sei Time", + "name": "Game", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "next_session", + "sort_order": "DESC", + "states": [] +} diff --git a/st/sei_time/doctype/game/game.py b/st/sei_time/doctype/game/game.py new file mode 100644 index 0000000..ef303aa --- /dev/null +++ b/st/sei_time/doctype/game/game.py @@ -0,0 +1,48 @@ +# Copyright (c) 2026, Vassili and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.utils import get_datetime + + +class Game(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + description: DF.SmallText | None + max_seats: DF.Int + next_session: DF.Datetime | None + select_vniu: DF.Literal["January", "February", "March"] + title: DF.Data | None + type: DF.Link | None + # end: auto-generated types + + pass + +@frappe.whitelist() +def get_events(start, end, filters=None): + event_docs = frappe.get_all( + "Game", + fields=["name", "title", "next_session"], + filters=[ + ["next_session", 'between', [start, end]], + ] + ) + + events = [] + for doc in event_docs: + next_session = get_datetime(doc.next_session) + events.append({ + "name": doc.name, + "title": doc.title, + "start": next_session, + "end": next_session, + }) + + return events \ No newline at end of file diff --git a/st/sei_time/doctype/game/game_calendar.js b/st/sei_time/doctype/game/game_calendar.js new file mode 100644 index 0000000..9974cb4 --- /dev/null +++ b/st/sei_time/doctype/game/game_calendar.js @@ -0,0 +1,9 @@ +frappe.views.calendar["Game"] = { + field_map: { + "start": "next_session", + "end": "next_session", // Optional + "id": "name", + "title": "title" + }, + get_events_method: "st.sei_time.doctype.game.game.get_events" +}; \ No newline at end of file diff --git a/st/sei_time/doctype/game/test_game.py b/st/sei_time/doctype/game/test_game.py new file mode 100644 index 0000000..af9242a --- /dev/null +++ b/st/sei_time/doctype/game/test_game.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestGame(IntegrationTestCase): + """ + Integration tests for Game. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/st/sei_time/doctype/game_type/__init__.py b/st/sei_time/doctype/game_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/game_type/game_type.js b/st/sei_time/doctype/game_type/game_type.js new file mode 100644 index 0000000..a1c5952 --- /dev/null +++ b/st/sei_time/doctype/game_type/game_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Vassili and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Game Type", { +// refresh(frm) { + +// }, +// }); diff --git a/st/sei_time/doctype/game_type/game_type.json b/st/sei_time/doctype/game_type/game_type.json new file mode 100644 index 0000000..057c9f1 --- /dev/null +++ b/st/sei_time/doctype/game_type/game_type.json @@ -0,0 +1,47 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:type", + "creation": "2026-02-25 20:44:53.525128", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "type" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Data", + "label": "Type", + "unique": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-24 19:02:50.572719", + "modified_by": "Administrator", + "module": "Sei Time", + "name": "Game Type", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/st/sei_time/doctype/game_type/game_type.py b/st/sei_time/doctype/game_type/game_type.py new file mode 100644 index 0000000..f6bc3cf --- /dev/null +++ b/st/sei_time/doctype/game_type/game_type.py @@ -0,0 +1,20 @@ +# Copyright (c) 2026, Vassili and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class GameType(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + type: DF.Data | None + # end: auto-generated types + + pass diff --git a/st/sei_time/doctype/game_type/test_game_type.py b/st/sei_time/doctype/game_type/test_game_type.py new file mode 100644 index 0000000..702fd12 --- /dev/null +++ b/st/sei_time/doctype/game_type/test_game_type.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestGameType(IntegrationTestCase): + """ + Integration tests for GameType. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/st/sei_time/doctype/player_profile/__init__.py b/st/sei_time/doctype/player_profile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/doctype/player_profile/player_profile.js b/st/sei_time/doctype/player_profile/player_profile.js new file mode 100644 index 0000000..ad2aba7 --- /dev/null +++ b/st/sei_time/doctype/player_profile/player_profile.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Vassili and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Player Profile", { +// refresh(frm) { + +// }, +// }); diff --git a/st/sei_time/doctype/player_profile/player_profile.json b/st/sei_time/doctype/player_profile/player_profile.json new file mode 100644 index 0000000..7763e13 --- /dev/null +++ b/st/sei_time/doctype/player_profile/player_profile.json @@ -0,0 +1,86 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:user", + "creation": "2026-02-27 13:39:48.100441", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "user", + "display_name", + "discord_handle", + "pronouns", + "timezone", + "attach_image_guua" + ], + "fields": [ + { + "description": "User this profile belongs to.", + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "user", + "options": "User", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "display_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "display_name", + "reqd": 1 + }, + { + "description": "Discord username", + "fieldname": "discord_handle", + "fieldtype": "Data", + "label": "discord_handle" + }, + { + "fieldname": "pronouns", + "fieldtype": "Select", + "label": "pronouns", + "options": "They/Them\nHe/Him\nShe/Her\nHe/They\nShe/They\nAny\nPrefer not to say\nCustom" + }, + { + "fieldname": "timezone", + "fieldtype": "Select", + "label": "timezone", + "options": "America/Los_Angeles | Pacific Time\nAmerica/Denver | Mountain Time\nAmerica/Chicago | Central Time\nAmerica/New_York | Eastern Time\nAmerica/Anchorage | Alaska Time\nPacific/Honolulu | Hawaii Time\nAmerica/Phoenix | Arizona Time (no DST)\nUTC | UTC", + "reqd": 1 + }, + { + "fieldname": "attach_image_guua", + "fieldtype": "Attach Image" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-24 19:03:40.431867", + "modified_by": "Administrator", + "module": "Sei Time", + "name": "Player Profile", + "naming_rule": "By fieldname", + "owner": "admin@seitimegames.com", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/st/sei_time/doctype/player_profile/player_profile.py b/st/sei_time/doctype/player_profile/player_profile.py new file mode 100644 index 0000000..0ac3efe --- /dev/null +++ b/st/sei_time/doctype/player_profile/player_profile.py @@ -0,0 +1,25 @@ +# Copyright (c) 2026, Vassili and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PlayerProfile(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + attach_image_guua: DF.AttachImage | None + discord_handle: DF.Data | None + display_name: DF.Data + pronouns: DF.Literal["They/Them", "He/Him", "She/Her", "He/They", "She/They", "Any", "Prefer not to say", "Custom"] + timezone: DF.Literal["America/Los_Angeles | Pacific Time", "America/Denver | Mountain Time", "America/Chicago | Central Time", "America/New_York | Eastern Time", "America/Anchorage | Alaska Time", "Pacific/Honolulu | Hawaii Time", "America/Phoenix | Arizona Time (no DST)", "UTC | UTC"] + user: DF.Link + # end: auto-generated types + + pass diff --git a/st/sei_time/doctype/player_profile/test_player_profile.py b/st/sei_time/doctype/player_profile/test_player_profile.py new file mode 100644 index 0000000..63e09b4 --- /dev/null +++ b/st/sei_time/doctype/player_profile/test_player_profile.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Vassili and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestPlayerProfile(IntegrationTestCase): + """ + Integration tests for PlayerProfile. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/st/sei_time/page/__init__.py b/st/sei_time/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/page/characters/__init__.py b/st/sei_time/page/characters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/st/sei_time/page/characters/characters.css b/st/sei_time/page/characters/characters.css new file mode 100644 index 0000000..2aef4e3 --- /dev/null +++ b/st/sei_time/page/characters/characters.css @@ -0,0 +1,27 @@ +.image-stack { + position: relative; + width: 300px; + height: 360px; +} + +.image-stack .layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; +} + +.image-stack .bg { + z-index: 1; +} + +.image-stack .fg { + padding: 35px; + z-index: 2; +} + +.image-stack .frame { + z-index: 3; +} \ No newline at end of file diff --git a/st/sei_time/page/characters/characters.js b/st/sei_time/page/characters/characters.js new file mode 100644 index 0000000..8d19723 --- /dev/null +++ b/st/sei_time/page/characters/characters.js @@ -0,0 +1,41 @@ +frappe.pages['characters'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'My Characters', + single_column: true + }); + + frappe.db.get_list('Character', { + fields: ['name', 'type', 'profile_picture', 'type.frame_image', 'type.background_image'], + filters: { + owner: frappe.session.user + } + }).then(docs => { + console.log(docs); + render_cards(docs, wrapper); + }); +} + +function render_cards(docs, wrapper) { + const container = $('
'); + + docs.forEach(doc => { + const card = $(` +${doc.type}
+