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"""

    BINDINGS = [("escape", "app.pop_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: 23;
            padding: 1;
            width: 60;
        }
    """

    reply_to: Message | None
    response: str

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

    def compose(self):
        with Vertical():
            with Horizontal():
                yield Label("Title", shrink=True)
                yield Input(
                    (
                        f"Re: {self.reply_to.title.lstrip('Re: ')}"
                        if self.reply_to
                        else ""
                    ),
                    id="title",
                    max_length=Message.MAX_TITLE_LENGTH,
                    validators=[
                        validation.Length(
                            1, failure_description="Title is required"
                        )
                    ],
                    validate_on=("changed", "submitted"),
                )

            with Horizontal():
                yield Label("To", shrink=True)
                yield Input(
                    id="to",
                    max_length=User.MAX_NAME_LENGTH,
                )

            yield SelectionList(id="tags")

            with Horizontal(id="wrapper"):
                yield Button("Save", variant="success", name="save", id="save")
                yield Button("Cancel", variant="error", name="cancel")

    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_recipient = None
                my_tags = []
            else:
                my_recipient = (
                    await db.exec(
                        select(User).where(User.id == self.reply_to.author_id)
                    )
                ).one_or_none()
                my_tags = (
                    await db.exec(
                        select(MessageTags.tag_name).where(
                            MessageTags.message_id == self.reply_to.id
                        )
                    )
                ).all()

            to: Input = self.get_widget_by_id("to")  # type: ignore
            to.value = my_recipient.name if my_recipient else ""
            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)
        to: Input = self.get_widget_by_id("to")  # type: ignore
        tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

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

        async with db_session() as db:
            recipient: User | None = None

            # validate recipient
            if to.value != "":
                recipient = (
                    await db.exec(
                        select(User).where(
                            func.lower(User.name) == to.value.lower()
                        )
                    )
                ).one_or_none()

                if recipient is None:
                    to.value = "No such user!"
                    return

            # create message
            message = Message(
                author_id=app.context.user.id,
                title=title.value,
                content=content.text,
                recipient=recipient,
                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()

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 BINDINGS
var CSS
var can_focus
var can_focus_children
var reply_to : Message | None
var response : str

Methods

def compose(self)
Expand source code
def compose(self):
    with Vertical():
        with Horizontal():
            yield Label("Title", shrink=True)
            yield Input(
                (
                    f"Re: {self.reply_to.title.lstrip('Re: ')}"
                    if self.reply_to
                    else ""
                ),
                id="title",
                max_length=Message.MAX_TITLE_LENGTH,
                validators=[
                    validation.Length(
                        1, failure_description="Title is required"
                    )
                ],
                validate_on=("changed", "submitted"),
            )

        with Horizontal():
            yield Label("To", shrink=True)
            yield Input(
                id="to",
                max_length=User.MAX_NAME_LENGTH,
            )

        yield SelectionList(id="tags")

        with Horizontal(id="wrapper"):
            yield Button("Save", variant="success", name="save", id="save")
            yield Button("Cancel", variant="error", name="cancel")

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 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_recipient = None
            my_tags = []
        else:
            my_recipient = (
                await db.exec(
                    select(User).where(User.id == self.reply_to.author_id)
                )
            ).one_or_none()
            my_tags = (
                await db.exec(
                    select(MessageTags.tag_name).where(
                        MessageTags.message_id == self.reply_to.id
                    )
                )
            ).all()

        to: Input = self.get_widget_by_id("to")  # type: ignore
        to.value = my_recipient.name if my_recipient else ""
        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)
    to: Input = self.get_widget_by_id("to")  # type: ignore
    tags: SelectionList = self.get_widget_by_id("tags")  # type: ignore

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

    async with db_session() as db:
        recipient: User | None = None

        # validate recipient
        if to.value != "":
            recipient = (
                await db.exec(
                    select(User).where(
                        func.lower(User.name) == to.value.lower()
                    )
                )
            ).one_or_none()

            if recipient is None:
                to.value = "No such user!"
                return

        # create message
        message = Message(
            author_id=app.context.user.id,
            title=title.value,
            content=content.text,
            recipient=recipient,
            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