Module userland.scripts.messages.details_modal

Message details screen

Classes

class DetailsModal (*args,
reply_to: Message | None = None,
**kwargs)
Expand source code
class DetailsModal(ModalScreen):
    """Message details screen"""

    CSS = """
        DetailsModal {
            align: center middle;
            background: rgba(0, 0, 0, 0.5);
        }

        Label {
            margin-top: 1;
            width: 5
        }

        Input {
            width: 53;
        }

        SelectionList {
            height: 10;
        }

        Button {
            margin-top: 1;
            width: 50%;
        }

        #save {
            margin-right: 1;
            margin-top: 1;
        }

        #wrapper {
            background: $primary-background;
            height: 20;
            padding: 1;
            width: 60;
        }
    """

    reply_to: Message | None
    response: str

    def __init__(self, *args, reply_to: Message | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self.reply_to = reply_to

    def compose(self):
        yield Vertical(
            Horizontal(
                Label("Title", shrink=True),
                Input(
                    (
                        f"Re: {self.reply_to.title.lstrip('Re: ')}"
                        if self.reply_to
                        else ""
                    ),
                    id="title",
                    max_length=MAX_TITLE_LENGTH,
                    validators=[
                        validation.Length(
                            1, failure_description="Title is required"
                        )
                    ],
                    validate_on=("changed", "submitted"),
                ),
            ),
            SelectionList(id="tags"),
            Horizontal(
                Button("Save", variant="success", name="save", id="save"),
                Button("Cancel", variant="error", name="cancel"),
            ),
            id="wrapper",
        )

    async def on_mount(self) -> None:
        tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

        async with db_session() as db:
            if not self.reply_to:
                my_tags = []
            else:
                my_tags = (
                    await db.exec(
                        select(MessageTags.tag_name).where(
                            MessageTags.message_id == self.reply_to.id
                        )
                    )
                ).all()

            all_tags: Sequence[str] = (  # type: ignore
                await db.exec(
                    select(MessageTag.name).where(
                        and_(
                            col(MessageTag.name).is_not(None),
                            col(MessageTag.name).not_in(my_tags),
                        )
                    )
                )
            ).all()

        tags.add_options([Selection(t, t, True) for t in my_tags])
        tags.add_options([Selection(t, t, False) for t in all_tags])

    async def submit(self) -> None:
        app: XthuluApp = self.app  # type: ignore
        title: Input = self.get_widget_by_id("title")  # type: ignore
        title_validator = title.validate(title.value)

        if title_validator and not title_validator.is_valid:
            return

        content = self.app._background_screens[-1].query_one(TextArea)
        tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

        if len(tags.selected) == 0:
            return

        # create message
        async with db_session() as db:
            message = Message(
                author_id=app.context.user.id,
                title=title.value,
                content=content.text,
                parent_id=self.reply_to.id if self.reply_to else None,
            )
            db.add(message)
            await db.commit()
            await db.refresh(message)
            assert message.id

        # link tags to message
        for t in tags.selected:
            async with db_session() as db:
                db.add(MessageTags(message_id=message.id, tag_name=t))
                await db.commit()

        self.app.pop_screen()  # pop this modal
        self.app.pop_screen()  # pop the editor

    async def on_input_submitted(self, event: Input.Submitted) -> None:
        await self.submit()

    async def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.name == "cancel":
            self.app.pop_screen()  # pop this modal
            return

        await self.submit()

    async def key_escape(self, _):
        self.app.pop_screen()  # pop this modal

Message details screen

Initialize the screen.

Args

name
The name of the screen.
id
The ID of the screen in the DOM.
classes
The CSS classes for the screen.

Ancestors

  • textual.screen.ModalScreen
  • textual.screen.Screen
  • typing.Generic
  • textual.widget.Widget
  • textual.dom.DOMNode
  • textual.message_pump.MessagePump

Class variables

var CSS
var can_focus
var can_focus_children
var reply_toMessage | None
var response : str

Methods

def compose(self)
Expand source code
def compose(self):
    yield Vertical(
        Horizontal(
            Label("Title", shrink=True),
            Input(
                (
                    f"Re: {self.reply_to.title.lstrip('Re: ')}"
                    if self.reply_to
                    else ""
                ),
                id="title",
                max_length=MAX_TITLE_LENGTH,
                validators=[
                    validation.Length(
                        1, failure_description="Title is required"
                    )
                ],
                validate_on=("changed", "submitted"),
            ),
        ),
        SelectionList(id="tags"),
        Horizontal(
            Button("Save", variant="success", name="save", id="save"),
            Button("Cancel", variant="error", name="cancel"),
        ),
        id="wrapper",
    )

Called by Textual to create child widgets.

This method is called when a widget is mounted or by setting recompose=True when calling [refresh()][textual.widget.Widget.refresh].

Note that you don't typically need to explicitly call this method.

Example

def compose(self) -> ComposeResult:
    yield Header()
    yield Label("Press the button below:")
    yield Button()
    yield Footer()
async def key_escape(self, _)
Expand source code
async def key_escape(self, _):
    self.app.pop_screen()  # pop this modal
async def on_button_pressed(self, event: textual.widgets._button.Button.Pressed) ‑> None
Expand source code
async def on_button_pressed(self, event: Button.Pressed) -> None:
    if event.button.name == "cancel":
        self.app.pop_screen()  # pop this modal
        return

    await self.submit()
async def on_input_submitted(self, event: textual.widgets._input.Input.Submitted) ‑> None
Expand source code
async def on_input_submitted(self, event: Input.Submitted) -> None:
    await self.submit()
async def on_mount(self) ‑> None
Expand source code
async def on_mount(self) -> None:
    tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

    async with db_session() as db:
        if not self.reply_to:
            my_tags = []
        else:
            my_tags = (
                await db.exec(
                    select(MessageTags.tag_name).where(
                        MessageTags.message_id == self.reply_to.id
                    )
                )
            ).all()

        all_tags: Sequence[str] = (  # type: ignore
            await db.exec(
                select(MessageTag.name).where(
                    and_(
                        col(MessageTag.name).is_not(None),
                        col(MessageTag.name).not_in(my_tags),
                    )
                )
            )
        ).all()

    tags.add_options([Selection(t, t, True) for t in my_tags])
    tags.add_options([Selection(t, t, False) for t in all_tags])
async def submit(self) ‑> None
Expand source code
async def submit(self) -> None:
    app: XthuluApp = self.app  # type: ignore
    title: Input = self.get_widget_by_id("title")  # type: ignore
    title_validator = title.validate(title.value)

    if title_validator and not title_validator.is_valid:
        return

    content = self.app._background_screens[-1].query_one(TextArea)
    tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

    if len(tags.selected) == 0:
        return

    # create message
    async with db_session() as db:
        message = Message(
            author_id=app.context.user.id,
            title=title.value,
            content=content.text,
            parent_id=self.reply_to.id if self.reply_to else None,
        )
        db.add(message)
        await db.commit()
        await db.refresh(message)
        assert message.id

    # link tags to message
    for t in tags.selected:
        async with db_session() as db:
            db.add(MessageTags(message_id=message.id, tag_name=t))
            await db.commit()

    self.app.pop_screen()  # pop this modal
    self.app.pop_screen()  # pop the editor