Projector System - OpenSlides/OpenSlides GitHub Wiki
The projector system is split into two parts:
- Controlling, what is projected. See Projection.
- Getting data for some projectors. This is done by the projector service
TODO:
- describe calculated field "projection/content"
- how is it integrated in the autoupdate service and query format
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.
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.
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 istrue
, 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.
{
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.
First, the current list of speakers must be found. Get the reference projector of the meeting (follow projector/meeting_id
→meeting/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.
{
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.
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
).
{
id: <mediafile/id>,
mimetype: <mediafile/mimetype>
}
{
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 tomotion/text
ifmeeting/motions_enable_text_on_projector
is true. Otherwise the key is omitted. -
reason
: Set tomotion/reason
ifmeeting/motions_enable_reason_on_projector
is true. Otherwise the key is omitted. -
modified_final_version
: Set tomotion/modified_final_version
if themode
key inprojection/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
: Ifmotion/statute_paragraph_id
is set, set this field toThis field must not be given if there is not statute paragraph.{ title: <motion_statute_paragraph/title>, text: <motion_statute_paragraph/text> }
-
change_recommendations
: Map all non-internal (motion_change_recommendation/internal != true
) change recommendations ofmotion/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 inmotion/amendment_ids
towith these fields:{ id: <motion/id>, title: <motion/title>, number: <motion/number>, amendment_paragraphs, change_recommendations, merge_amendment_into_final, merge_amendment_into_diff }
-
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 themotion/state_id -> motion_state/merge_amendment_into_final
is"do_merge"
, else"undefined"
. -
merge_amendment_into_diff
: The first match wins:-
"undefined"
, if themotion/state_id -> motion_state/merge_amendment_into_final
is"do_not_merge"
. -
"do_merge"
, if themotion/state_id -> motion_state/merge_amendment_into_final
is"do_merge"
. -
"do_merge"
, if themotion/recommendation_id
is set andmotion/recommendation_id -> motion_state/merge_amendment_into_final
is"do_merge"
. -
"undefined"
.
-
-
-
submitters
: Mapmotion/submitter_ids
to submitter objects (user_id
andweight?
is required) and sort them bymotion_submitter/weight
. Map each submitter toUserRepresentation(<motion_submitter/user_id>, <projection/meeting_id>)
. -
recommendation_referencing_motions
: Must not be given, ifmeeting/motions_show_referring_motions
is false. If it is true, map the list of motion ids inmotion/referenced_in_motion_recommendation_extension_ids
to a list ofGetTitleInformation(motion/<id>, <motion/meeting_id>)
. -
recommendation_label
: If the motion has a recommendation (motion/recommendation_id
) andmeeting/motions_enable_recommendation_on_projector
is true, the value is set to the recommendation'sstate/recommendation_label
. -
recommendation_extension
: If there is a value forrecommendation_label
(see above) and the recommendation hasstate/show_recommendation_extension_field
set to true, setrecommendation_extension
tomotion/recommendation_extension
. -
recommendation_referenced_motions
: If there is a value forrecommendation_extension
(see above), create an object{<fqid>: GetTitleInformation(<fqid>, <motion/meeting_id>)}
. There is a key-value-pair for each fqid inmotion/recommendation_extension_reference_ids
. -
recommender
: If there is no value forrecommendation_label
omit this key. Else, ifmotion/statute_paragraph_id
is set, setrecommender
tomeeting/motions_statute_recommendations_by
, otherwise set it tomeeting/motions_recommendations_by
.
{
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, setrecommendation_extension
tomotion/recommendation_extension
. Otherwise omit the key. - For each fqid in
motion/recommendation_extension_reference_ids
setreferenced[<fqid>]
toGetTitleInformation(<fqid>, <motion/meeting_id>)
.
{
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 frompoll/option_ids
byoption/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
andabstain
are only available, if thepoll/state
is"published"
, otherwise the keys are omitted.text
andcontent_object
are only injected if the corresponding key is present in the option. This especially means thatGetTitleInformation
is not called if thecontent_object_id
of the option is empty. -
global_option
: Usepoll/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 correspondingpoll/global_yes
/poll/global_no
/poll/global_abstain
is true. Note: if everyone is false, it is just an empty object.
{
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>
}
{
message: <projector_countdown/message>
}
{
title: <topic/title>,
text: <topic/text>,
agenda_item_number: <topic/agenda_item_id -> agenda_item/item_number>
}
{
user: UserRepresentation(<id of projection/content_object_id>, <projection/meeting_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
.
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.
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
.
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
:It is assumed, that the client just uses the "username", if no other attributes are provided.{ username: UserRepresentation(<user/id>, meeting_id) }
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.
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>"
}