Projector System - OpenSlides/OpenSlides GitHub Wiki

General structure

The projector system is split into two parts:

  1. Controlling, what is projected. See Projection.
  2. Getting data for some projectors. This is done by the projector service

Projector service

TODO:

  • describe calculated field "projection/content"
  • how is it integrated in the autoupdate service and query format

Projections

For each projection a slide must be rendered. Which slide to render depends on the content_object_id and type of the projection.

There are multiple slides:

  • agenda_item_list (*)
  • assignment
  • current_list_of_speakers (*)
  • current_speaker_chyron (*)
  • list_of_speakers
  • mediafile
  • motion
  • motion_block
  • poll
  • projector_countdown
  • projector_message
  • topic
  • user

Given a content_object_id and type the collection of the content object has to be checked. If the collection is not meeting, the slide with the collection is chosen regardless of type.

The "special slides" marked with (*) belong to the collection meeting. The type is used to get the slide (E.g. if the projection is {...content_object_id: meeting/3, type: "agenda_item_list"...}) the agenda item list slide is chosen). The type must have one of the special slide names as the value and cannot be empty or a "normal" slide.

Slidedata

Given a projection, slidedata can be rendered for the projection. Depending on the content_object_id and type, see above, the fitting slide for the projection is determined. How to render each slide is described below.

Each slide-renderer has the underlying projection available as the input. In most cases only the content object id is needed. Sometimes (like the motion slide) the projection/option is needed, too.

See Projector-System#special-functions for some functions shared withing multiple slides to render data.

agenda_item_list

The slide contains a list of agenda items of the meeting.

{
    items: <agenda items as a flat tree>
}

The agenda (tree) is represented as a list (flat tree) of agenda items. An agenda item is represented by

{
    title_information: GetTitleInformation(<agenda_item/content_object_id>, <agenda_item/meeting_id>),
    depth: depth
}

The depth is the depth in the tree. root items have a depth of 0.

Not all agenda items are included in the flat tree:

  • If an agenda item is hidden (agenda_item/is_hidden) it is excluded.
  • If only_main_items is given in the projection options and is true, only agenda items without a parent (depth 0) are included in the flat tree.
  • If meeting/agenda_show_internal_items_on_projector is false, internal agenda items (agenda_item/is_internal) are excluded.

assignment

{
    title: <assignment/title>,
    description: <assignment/description>,
    number_poll_candidates: <assignment/number_poll_candidates>,
    candidates
}

candidates is a list of user representations. The assignment candidates (assignment/candidate_ids) are sorted by assignment_candidate/weight). For each candidate get UserRepresentation(<assignment_candidate/user_id>, <projection/meeting_id>) is called.

current_list_of_speakers

First, the current list of speakers must be found. Get the reference projector of the meeting (follow projector/meeting_idmeeting/reference_projector_id). Iterate over all current_projection_ids of the reference projector. If a projection/content_object_id has a list_of_speakers_id field with a valid id this one is the current list of speakers. The search is aborted on the first hit.

If no current list of speakers exists, return {}. Else use the same code as for the list_of_speakers slide to render the slide data for the current list of speakers.

current_speaker_chyron

{
    background_color: <the value of projector/chyron_background_color>,
    font_color: <the value of projector/chyron_font_color>,
    current_speaker_name: <user_id ? UserShortName(<user_id>) : ''>
    current_speaker_level: <user_id ? UserStructureLevel(<user_id>, <projection/meeting_id>) : ''>
}

To get the current list of speaker, see current_list_of_speakers. From this list get the current speaker (see List of speakers). The user_id of the current speaker is used to render the fields current_speaker_name and current_speaker_level. If there is no current list of speaker or no current speaker, leave the fields empty or omit them.

list_of_speakers

Given a list of speakers id this data must be rendered:

{
    waiting,
    current,
    finished,
    closed: <list_of_speakers/closed>,
    title_information: GetTitleInformation(<list_of_speakers/content_object_id>, <list_of_speakers/meeting_id>),
    number_of_waiting_speakers
}

waiting and finished hold an array of speakers as well as current contains an optional speaker (else null). A speaker (given by the id) is formatted in this way:

{
    user: UserRepresentation(<speaker/user_id>, <projection/meeting_id>),
    speech_state: <speaker/speech_state>,
    note: <speaker/note>,
    point_of_order: <speaker/point_of_order>
}

All speakers of the list of speakers are categorized into waiting, current and finished as described in List of speakers. The waiting speakers are sorted by their weight and the finished speakers by end_time. The amount of speaker in waiting must be limited to meeting/list_of_speakers_amount_next_on_projector, if meeting/list_of_speakers_amount_next_on_projector is >= 0 (-1 disables the limiting). The amount of finished must be limited to meeting/list_of_speakers_amount_last_on_projector, if meeting/list_of_speakers_amount_last_on_projector is >= 0 (-1 disables the limiting).

number_of_waiting_speakers: If meeting/list_of_speakers_show_amount_of_speakers_on_slide is false, omit this field. Else, provide the number of waiting speakers (without the restriction of meeting/list_of_speakers_amount_next_on_projector).

mediafile

{
    id: <mediafile/id>,
    mimetype: <mediafile/mimetype>
}

motion

{
    title: <motion/title>,
    number: <motion/number>,
    text,
    reason,
    modified_final_version,
    submitters,
    amendment_paragraphs,
    lead_motion,
    base_statute,
    change_recommendations,
    amendments,
    recommendation_referencing_motions,
    recommendation_label,
    recommendation_extension,
    recommendation_referenced_motions,
    recommender,
    show_sidebox: <motion/meeting_id -> meeting/motions_enable_sidebox_on_projector>,
    line_length: <motion/meeting_id -> meeting/motions_line_length>,
    preamble: <motion/meeting_id -> meeting/motions_preamble>,
    line_numbering: <motion/meeting_id -> meeting/motions_default_line_numbering>,
}
  • text: Set to motion/text if meeting/motions_enable_text_on_projector is true. Otherwise the key is omitted.
  • reason: Set to motion/reason if meeting/motions_enable_reason_on_projector is true. Otherwise the key is omitted.
  • modified_final_version: Set to motion/modified_final_version if the mode key in projection/options is "final". Note that the key is not required to exist. Otherwise the key is omitted.
  • amendment_paragraphs: A mapping of line numbers to the changed line (e.g.{"2": "A text", "5": "Another text"})
  • lead_motion:motion/lead_motion_id is set, set this field to
    {
        title: <motion/title>,
        number: <motion/number>,
        text: <motion/text>
    }
    of the lead motion. If there is not lead motion, this field must not be given.
    
  • base_statute: If motion/statute_paragraph_id is set, set this field to
    {
        title: <motion_statute_paragraph/title>,
        text: <motion_statute_paragraph/text>
    }
    
    This field must not be given if there is not statute paragraph.
  • change_recommendations: Map all non-internal (motion_change_recommendation/internal != true) change recommendations of motion/change_recommendation_ids to
    {
        id: <motion_change_recommendation/id>
        rejected: <motion_change_recommendation/rejected>,
        type: <motion_change_recommendation/type>,
        other_description: <motion_change_recommendation/other_description>,
        line_from: <motion_change_recommendation/line_from>,
        line_to: <motion_change_recommendation/line_to>,
        text: <motion_change_recommendation/text>,
        creation_time: <motion_change_recommendation/creation_time>
    }
    
  • amendments: Map each amendment id in motion/amendment_ids to
    {
        id: <motion/id>,
        title: <motion/title>,
        number: <motion/number>,
        amendment_paragraphs,
        change_recommendations,
        merge_amendment_into_final,
        merge_amendment_into_diff
    }
    
    with these fields:
    • amendment_paragraphs: Same as for the main motion: A mapping of line numbers to the changed line.
    • change_recommendations: Same as for the main motion: A list of all non-internal change recommendations.
    • merge_amendment_into_final: "do_merge", if the motion/state_id -> motion_state/merge_amendment_into_final is "do_merge", else "undefined".
    • merge_amendment_into_diff: The first match wins:
      • "undefined", if the motion/state_id -> motion_state/merge_amendment_into_final is "do_not_merge".
      • "do_merge", if the motion/state_id -> motion_state/merge_amendment_into_final is "do_merge".
      • "do_merge", if the motion/recommendation_id is set and motion/recommendation_id -> motion_state/merge_amendment_into_final is "do_merge".
      • "undefined".
  • submitters: Map motion/submitter_ids to submitter objects (user_id and weight? is required) and sort them by motion_submitter/weight. Map each submitter to UserRepresentation(<motion_submitter/user_id>, <projection/meeting_id>).
  • recommendation_referencing_motions: Must not be given, if meeting/motions_show_referring_motions is false. If it is true, map the list of motion ids in motion/referenced_in_motion_recommendation_extension_ids to a list of GetTitleInformation(motion/<id>, <motion/meeting_id>).
  • recommendation_label: If the motion has a recommendation (motion/recommendation_id) and meeting/motions_enable_recommendation_on_projector is true, the value is set to the recommendation's state/recommendation_label.
  • recommendation_extension: If there is a value for recommendation_label (see above) and the recommendation has state/show_recommendation_extension_field set to true, set recommendation_extension to motion/recommendation_extension.
  • recommendation_referenced_motions: If there is a value for recommendation_extension (see above), create an object {<fqid>: GetTitleInformation(<fqid>, <motion/meeting_id>)}. There is a key-value-pair for each fqid in motion/recommendation_extension_reference_ids.
  • recommender: If there is no value for recommendation_label omit this key. Else, if motion/statute_paragraph_id is set, set recommender to meeting/motions_statute_recommendations_by, otherwise set it to meeting/motions_recommendations_by.

motion_block

{
    title: <motion_block/title>,
    motions,
    referenced
}

motions is an array of motion representations. referenced maps fqids to the title information. For each motion_block/motion_ids create an entry in motions:

{
    title: <motion/title>,
    number: <motion/number>,
    agenda_item_number: <motion/agenda_item_id -> agenda_item/item_number>,
    recommendation,
    recommendation_extension
}

If the motion has a recommendation (motion/recommendation_id is set):

  • Fill recommendation with
    {
        recommendation_label: <motion_state/recommendation_label>,
        css_class: <motion_state/css_class>
    }
    
  • If motion_state/show_recommendation_extension_field is true, set recommendation_extension to motion/recommendation_extension. Otherwise omit the key.
  • For each fqid in motion/recommendation_extension_reference_ids set referenced[<fqid>] to GetTitleInformation(<fqid>, <motion/meeting_id>).

poll

{
    content_object_id: <poll/content_object_id>,
    title_information: GetTitleInformation(<poll/content_object_id>, <poll/meeting_id>),
    title: <poll/title>,
    description: <poll/description>,
    type: <poll/type>,
    state: <poll/state>,
    global_yes: <poll/global_yes>,
    glboal_no: <poll/global_no>,
    global_abstain: <poll/global_abstain>,
    options,

// These keys are only available, if poll/state == "published"
    entitled_users_at_stop: <poll/entitled_users_at_stop>,
    is_pseudoanonymized: <poll/is_pseudoanonymized>,
    pollmethod: <poll/pollmethod>,
    onehundred_percent_base: <poll/onehundred_percent_base>,
    votesvalid: <poll/votesvalid>,
    votesinvalid: <poll/votesinvalid>,
    votescast: <poll/votescast>,
    global_option
}
  • options is a list of option representations. Sort all options from poll/option_ids by option/weight and format each option as

    {
        text: <option/text>,
        content_object: GetTitleInformation(<option/content_object_id>, <option/meeting_id>),
        yes: <option/yes>,
        no: <option/no>,
        abstain: <option/abstain>
    }
    

    yes, no and abstain are only available, if the poll/state is "published", otherwise the keys are omitted. text and content_object are only injected if the corresponding key is present in the option. This especially means that GetTitleInformation is not called if the content_object_id of the option is empty.

  • global_option: Use poll/global_option_id to fill this object:

    {
        yes: <option/yes>,
        no: <option/no>,
        abstain: <option/abstain>,
    }
    

    The key yes/no/abstain are only present if the corresponding poll/global_yes/poll/global_no/poll/global_abstain is true. Note: if everyone is false, it is just an empty object.

projector_countdown

{
    description: <projector_countdown/description>,
    running: <projector_countdown/running>,
    countdown_time: <projector_countdown/countdown_time>,
    warning_time: <projector_countdown/meeting_id -> meeting/projector_countdown_warning_time>
}

projector_message

{
    message: <projector_countdown/message>
}

topic

{
    title: <topic/title>,
    text: <topic/text>,
    agenda_item_number: <topic/agenda_item_id -> agenda_item/item_number>
}

user

{
    user: UserRepresentation(<id of projection/content_object_id>, <projection/meeting_id>)
}

Special functions

UserShortName(user_id)

Build a string short_name. As a basis, use user/first_name and user/last_name and concat them by a space if both are not empty. If just one is given use the given field. If both are empty, set short_name to user/username.

If user/title is not empty, prepend it to short_name with a space. Return short_name.

UserStructureLevel(user_id, meeting_id)

If meeting_user/structure_level of the meeting user defined by user_id and meeting_id is set, return this structure level. Else, return user/default_structure_level or an empty string if it not set.

UserRepresentation(user_id, meeting_id)

Get the sort_name and structure_level by calling UserShortName and UserStructureLevel. The string user_representation will be the final result. Set it to the short_name first.

If structure_level is not empty, append " (<structure_level>)" to user_representation.

Remove surrounding whitespaces (trim/strip method in most languages) and return user_representation.

GetTitleInformation(fqid, meeting_id)

The goal is to return an object with necessary information to present a title to the user in the client. The main content depends on the collection

The object always contains the key collection which must be set to the collection of the parameter fqid.

These collections provide this base object as title information (the collection needs to be merged into it later):

  • topic:
    {
        title: <topic/title>,
        agenda_item_number: <topic/agenda_item_id> -> <agenda_item/item_number>
    }
    
  • assignment:
    {
        title: <assignment/title>,
        agenda_item_number: <assignment/agenda_item_id> -> <agenda_item/item_number>
    }
    
  • motion:
    {
        title: <motion/title>,
        number: <motion/number>,
        agenda_item_number: <motion/agenda_item_id> -> <agenda_item/item_number>
    }
    
  • motion_block:
    {
        title: <motion_block/title>,
        agenda_item_number: <motion_block/agenda_item_id> -> <agenda_item/item_number>
    }
    
  • mediafile:
    {
        title: <mediafile/title>
    }
    
  • user:
    {
        username: UserRepresentation(<user/id>, meeting_id)
    }
    
    It is assumed, that the client just uses the "username", if no other attributes are provided.

More collections might be added, if needed. Note: If a title information object from an unknown collection is requested, raise an error, so it gets noticed.

Errors

If there are slide-related errors like:

  • invalid slide
  • invalid options in projection
  • errors during the calculation of slide data

the error must be presented as the slide data in as this object:

{
    error: "<the error string>"
}
⚠️ **GitHub.com Fallback** ⚠️