diff --git a/src/main/python/plotlyst/core/client.py b/src/main/python/plotlyst/core/client.py index ba0e888b6..4c315f0f3 100644 --- a/src/main/python/plotlyst/core/client.py +++ b/src/main/python/plotlyst/core/client.py @@ -43,7 +43,8 @@ three_act_structure, SceneStoryBeat, Tag, default_general_tags, TagType, \ default_tag_types, LanguageSettings, ImportOrigin, NovelPreferences, Goal, CharacterPreferences, TagReference, \ ScenePlotReferenceData, MiceQuotient, SceneDrive, WorldBuilding, Board, \ - default_big_five_values, CharacterPlan, ManuscriptGoals + default_big_five_values, CharacterPlan, ManuscriptGoals, Diagram, DiagramData, default_events_map, \ + default_character_networks from src.main.python.plotlyst.core.template import Role, exclude_if_empty, exclude_if_black from src.main.python.plotlyst.env import app_env @@ -200,6 +201,8 @@ class NovelInfo: world: WorldBuilding = field(default_factory=WorldBuilding) board: Board = field(default_factory=Board) manuscript_goals: ManuscriptGoals = field(default_factory=ManuscriptGoals) + events_map: Diagram = field(default_factory=default_events_map) + character_networks: List[Diagram] = field(default_factory=default_character_networks) @dataclass @@ -287,6 +290,12 @@ def scenes_dir(self, novel: Optional[Union[Novel, NovelInfo]] = None) -> Path: scenes_dir_.mkdir() return scenes_dir_ + def diagrams_dir(self, novel: Novel): + diagrams_dir_ = self.novels_dir.joinpath(str(novel.id)).joinpath('diagrams') + if not diagrams_dir_.exists(): + diagrams_dir_.mkdir() + return diagrams_dir_ + def docs_dir(self, novel: Novel) -> Path: docs_dir_ = self.novels_dir.joinpath(str(novel.id)).joinpath('docs') if not docs_dir_.exists(): @@ -395,12 +404,23 @@ def load_manuscript(self, novel: Novel): if scene.manuscript and not scene.manuscript.loaded: self.load_document(novel, scene.manuscript) - def save_document(self, novel: Novel, document: Document): + def load_diagram(self, novel: Novel, diagram: Diagram): + if diagram.loaded: + return + + json_str = self.__load_diagram(novel, diagram.id) + diagram.data = DiagramData.from_json(json_str) + diagram.loaded = True + + def update_document(self, novel: Novel, document: Document): self.__persist_doc(novel, document) def delete_document(self, novel: Novel, document: Document): self.__delete_doc(novel, document) + def update_diagram(self, novel: Novel, diagram: Diagram): + self._persist_diagram(novel, diagram) + def fetch_novel(self, id: uuid.UUID) -> Novel: project_novel_info: ProjectNovelInfo = self._find_project_novel_info_or_fail(id) novel_info = self._read_novel_info(project_novel_info.id) @@ -527,7 +547,8 @@ def fetch_novel(self, id: uuid.UUID) -> Novel: story_structures=novel_info.story_structures, character_profiles=novel_info.character_profiles, conflicts=conflicts, goals=[x for x in novel_info.goals if str(x.id) in goal_ids], tags=tags_dict, documents=novel_info.documents, premise=novel_info.premise, synopsis=novel_info.synopsis, - prefs=novel_info.prefs, manuscript_goals=novel_info.manuscript_goals) + prefs=novel_info.prefs, manuscript_goals=novel_info.manuscript_goals, events_map=novel_info.events_map, + character_networks=novel_info.character_networks) world_path = self.novels_dir.joinpath(str(novel_info.id)).joinpath('world.json') if os.path.exists(world_path): @@ -565,7 +586,8 @@ def _persist_novel(self, novel: Novel): tag_types=list(novel.tags.keys()), documents=novel.documents, premise=novel.premise, synopsis=novel.synopsis, - version=LATEST_VERSION, prefs=novel.prefs, manuscript_goals=novel.manuscript_goals) + version=LATEST_VERSION, prefs=novel.prefs, manuscript_goals=novel.manuscript_goals, + events_map=novel.events_map, character_networks=novel.character_networks) self.__persist_info(self.novels_dir, novel_info) self._persist_world(novel.id, novel.world) @@ -609,6 +631,12 @@ def _persist_scene(self, scene: Scene, novel: Optional[Novel] = None): drive=scene.drive) self.__persist_info(self.scenes_dir(novel), info) + def _persist_diagram(self, novel: Novel, diagram: Diagram): + diagrams_dir = self.diagrams_dir(novel) + if not os.path.exists(str(diagrams_dir)): + os.mkdir(diagrams_dir) + self.__persist_info(diagrams_dir, diagram.data) + @staticmethod def __id_or_none(item): return item.id if item else None @@ -651,6 +679,14 @@ def __load_doc_data(self, novel: Novel, data_uuid: uuid.UUID) -> str: with open(path, encoding='utf8') as json_file: return json_file.read() + def __load_diagram(self, novel: Novel, diagram_uuid: uuid.UUID) -> str: + diagrams_dir = self.diagrams_dir(novel) + path = diagrams_dir.joinpath(self.__json_file(diagram_uuid)) + if not os.path.exists(path): + return '' + with open(path, encoding='utf8') as json_file: + return json_file.read() + def __persist_doc(self, novel: Novel, doc: Document): novel_doc_dir = self.docs_dir(novel).joinpath(str(novel.id)) if not os.path.exists(str(novel_doc_dir)): diff --git a/src/main/python/plotlyst/core/domain.py b/src/main/python/plotlyst/core/domain.py index c586af13c..dfbfede10 100644 --- a/src/main/python/plotlyst/core/domain.py +++ b/src/main/python/plotlyst/core/domain.py @@ -1727,13 +1727,40 @@ class CharacterNode(Node, CharacterBased): character_id: Optional[uuid.UUID] = None +@dataclass_json(undefined=Undefined.EXCLUDE) +@dataclass +class DiagramData: + nodes: List[Node] = field(default_factory=list) + + @dataclass class Diagram: title: str id: uuid.UUID = field(default_factory=uuid.uuid4) icon: str = field(default='', metadata=config(exclude=exclude_if_empty)) icon_color: str = field(default='black', metadata=config(exclude=exclude_if_black)) - nodes: List[Node] = field(default_factory=list) + + def __post_init__(self): + self.loaded: bool = False + self.data: Optional[DiagramData] = None + + @overrides + def __eq__(self, other: 'Diagram'): + if isinstance(other, Diagram): + return self.id == other.id + return False + + @overrides + def __hash__(self): + return hash(str(self.id)) + + +def default_events_map() -> Diagram: + return Diagram('Events', id=uuid.UUID('6c74e40f-d3de-4c83-bcd2-0ca5e626081d')) + + +def default_character_networks() -> List[Diagram]: + return [Diagram('Character relations', id=uuid.UUID('bfd1f2d3-cb33-48a6-a09e-b4332c3d1ed1'))] @dataclass @@ -1802,6 +1829,8 @@ class Novel(NovelDescriptor): world: WorldBuilding = field(default_factory=WorldBuilding) board: Board = field(default_factory=Board) manuscript_goals: ManuscriptGoals = field(default_factory=ManuscriptGoals) + events_map: Diagram = field(default_factory=default_events_map) + character_networks: List[Diagram] = field(default_factory=default_character_networks) def pov_characters(self) -> List[Character]: pov_ids = set() diff --git a/src/main/python/plotlyst/service/persistence.py b/src/main/python/plotlyst/service/persistence.py index 5cd12df4a..008fc23e5 100644 --- a/src/main/python/plotlyst/service/persistence.py +++ b/src/main/python/plotlyst/service/persistence.py @@ -148,6 +148,11 @@ def update_doc(self, novel: Novel, document: Document): self._operations.append(Operation(OperationType.UPDATE, novel=novel, doc=document)) self._persist_if_test_env() + def update_diagram(self, novel: Novel, diagram: Diagram): + if self._persistence_enabled: + self._operations.append(Operation(OperationType.UPDATE, novel=novel, diagram=diagram)) + self._persist_if_test_env() + def delete_doc(self, novel: Novel, document: Document): if self._persistence_enabled: self._operations.append(Operation(OperationType.DELETE, novel=novel, doc=document)) @@ -188,6 +193,7 @@ def _persist_operations(operations: List[Operation]): updated_novel_cache: Set[Novel] = set() updated_scene_cache: Set[Scene] = set() updated_character_cache: Set[Character] = set() + updated_diagram_cache: Set[Diagram] = set() for op in operations: # scenes @@ -210,13 +216,19 @@ def _persist_operations(operations: List[Operation]): elif op.character and op.novel and op.type == OperationType.DELETE: client.delete_character(op.novel, op.character) - # novel and document + # novel, document, diagram elif op.doc and op.type == OperationType.UPDATE: if op.doc not in updated_doc_cache: - json_client.save_document(op.novel, op.doc) + json_client.update_document(op.novel, op.doc) updated_doc_cache.add(op.doc) elif op.doc and op.type == OperationType.DELETE: json_client.delete_document(op.novel, op.doc) + + elif op.diagram and op.type == OperationType.UPDATE: + if op.diagram not in updated_diagram_cache: + json_client.update_diagram(op.novel, op.diagram) + updated_diagram_cache.add(op.diagram) + elif op.novel and op.type == OperationType.UPDATE: if op.novel not in updated_novel_cache: client.update_novel(op.novel)